cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

hermes_dld.c (13078B)


      1/*
      2 * Hermes download helper.
      3 *
      4 * This helper:
      5 *  - is capable of writing to the volatile area of the hermes device
      6 *  - is currently not capable of writing to non-volatile areas
      7 *  - provide helpers to identify and update plugin data
      8 *  - is not capable of interpreting a fw image directly. That is up to
      9 *    the main card driver.
     10 *  - deals with Hermes I devices. It can probably be modified to deal
     11 *    with Hermes II devices
     12 *
     13 * Copyright (C) 2007, David Kilroy
     14 *
     15 * Plug data code slightly modified from spectrum_cs driver
     16 *    Copyright (C) 2002-2005 Pavel Roskin <proski@gnu.org>
     17 * Portions based on information in wl_lkm_718 Agere driver
     18 *    COPYRIGHT (C) 2001-2004 by Agere Systems Inc. All Rights Reserved
     19 *
     20 * The contents of this file are subject to the Mozilla Public License
     21 * Version 1.1 (the "License"); you may not use this file except in
     22 * compliance with the License. You may obtain a copy of the License
     23 * at http://www.mozilla.org/MPL/
     24 *
     25 * Software distributed under the License is distributed on an "AS IS"
     26 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
     27 * the License for the specific language governing rights and
     28 * limitations under the License.
     29 *
     30 * Alternatively, the contents of this file may be used under the
     31 * terms of the GNU General Public License version 2 (the "GPL"), in
     32 * which case the provisions of the GPL are applicable instead of the
     33 * above.  If you wish to allow the use of your version of this file
     34 * only under the terms of the GPL and not to allow others to use your
     35 * version of this file under the MPL, indicate your decision by
     36 * deleting the provisions above and replace them with the notice and
     37 * other provisions required by the GPL.  If you do not delete the
     38 * provisions above, a recipient may use your version of this file
     39 * under either the MPL or the GPL.
     40 */
     41
     42#include <linux/module.h>
     43#include <linux/delay.h>
     44#include "hermes.h"
     45#include "hermes_dld.h"
     46
     47#define PFX "hermes_dld: "
     48
     49/* End markers used in dblocks */
     50#define PDI_END		0x00000000	/* End of PDA */
     51#define BLOCK_END	0xFFFFFFFF	/* Last image block */
     52#define TEXT_END	0x1A		/* End of text header */
     53
     54/*
     55 * The following structures have little-endian fields denoted by
     56 * the leading underscore.  Don't access them directly - use inline
     57 * functions defined below.
     58 */
     59
     60/*
     61 * The binary image to be downloaded consists of series of data blocks.
     62 * Each block has the following structure.
     63 */
     64struct dblock {
     65	__le32 addr;		/* adapter address where to write the block */
     66	__le16 len;		/* length of the data only, in bytes */
     67	char data[];		/* data to be written */
     68} __packed;
     69
     70/*
     71 * Plug Data References are located in the image after the last data
     72 * block.  They refer to areas in the adapter memory where the plug data
     73 * items with matching ID should be written.
     74 */
     75struct pdr {
     76	__le32 id;		/* record ID */
     77	__le32 addr;		/* adapter address where to write the data */
     78	__le32 len;		/* expected length of the data, in bytes */
     79	char next[];		/* next PDR starts here */
     80} __packed;
     81
     82/*
     83 * Plug Data Items are located in the EEPROM read from the adapter by
     84 * primary firmware.  They refer to the device-specific data that should
     85 * be plugged into the secondary firmware.
     86 */
     87struct pdi {
     88	__le16 len;		/* length of ID and data, in words */
     89	__le16 id;		/* record ID */
     90	char data[];		/* plug data */
     91} __packed;
     92
     93/*** FW data block access functions ***/
     94
     95static inline u32
     96dblock_addr(const struct dblock *blk)
     97{
     98	return le32_to_cpu(blk->addr);
     99}
    100
    101static inline u32
    102dblock_len(const struct dblock *blk)
    103{
    104	return le16_to_cpu(blk->len);
    105}
    106
    107/*** PDR Access functions ***/
    108
    109static inline u32
    110pdr_id(const struct pdr *pdr)
    111{
    112	return le32_to_cpu(pdr->id);
    113}
    114
    115static inline u32
    116pdr_addr(const struct pdr *pdr)
    117{
    118	return le32_to_cpu(pdr->addr);
    119}
    120
    121static inline u32
    122pdr_len(const struct pdr *pdr)
    123{
    124	return le32_to_cpu(pdr->len);
    125}
    126
    127/*** PDI Access functions ***/
    128
    129static inline u32
    130pdi_id(const struct pdi *pdi)
    131{
    132	return le16_to_cpu(pdi->id);
    133}
    134
    135/* Return length of the data only, in bytes */
    136static inline u32
    137pdi_len(const struct pdi *pdi)
    138{
    139	return 2 * (le16_to_cpu(pdi->len) - 1);
    140}
    141
    142/*** Plug Data Functions ***/
    143
    144/*
    145 * Scan PDR for the record with the specified RECORD_ID.
    146 * If it's not found, return NULL.
    147 */
    148static const struct pdr *
    149hermes_find_pdr(const struct pdr *first_pdr, u32 record_id, const void *end)
    150{
    151	const struct pdr *pdr = first_pdr;
    152
    153	end -= sizeof(struct pdr);
    154
    155	while (((void *) pdr <= end) &&
    156	       (pdr_id(pdr) != PDI_END)) {
    157		/*
    158		 * PDR area is currently not terminated by PDI_END.
    159		 * It's followed by CRC records, which have the type
    160		 * field where PDR has length.  The type can be 0 or 1.
    161		 */
    162		if (pdr_len(pdr) < 2)
    163			return NULL;
    164
    165		/* If the record ID matches, we are done */
    166		if (pdr_id(pdr) == record_id)
    167			return pdr;
    168
    169		pdr = (struct pdr *) pdr->next;
    170	}
    171	return NULL;
    172}
    173
    174/* Scan production data items for a particular entry */
    175static const struct pdi *
    176hermes_find_pdi(const struct pdi *first_pdi, u32 record_id, const void *end)
    177{
    178	const struct pdi *pdi = first_pdi;
    179
    180	end -= sizeof(struct pdi);
    181
    182	while (((void *) pdi <= end) &&
    183	       (pdi_id(pdi) != PDI_END)) {
    184
    185		/* If the record ID matches, we are done */
    186		if (pdi_id(pdi) == record_id)
    187			return pdi;
    188
    189		pdi = (struct pdi *) &pdi->data[pdi_len(pdi)];
    190	}
    191	return NULL;
    192}
    193
    194/* Process one Plug Data Item - find corresponding PDR and plug it */
    195static int
    196hermes_plug_pdi(struct hermes *hw, const struct pdr *first_pdr,
    197		const struct pdi *pdi, const void *pdr_end)
    198{
    199	const struct pdr *pdr;
    200
    201	/* Find the PDR corresponding to this PDI */
    202	pdr = hermes_find_pdr(first_pdr, pdi_id(pdi), pdr_end);
    203
    204	/* No match is found, safe to ignore */
    205	if (!pdr)
    206		return 0;
    207
    208	/* Lengths of the data in PDI and PDR must match */
    209	if (pdi_len(pdi) != pdr_len(pdr))
    210		return -EINVAL;
    211
    212	/* do the actual plugging */
    213	hw->ops->program(hw, pdi->data, pdr_addr(pdr), pdi_len(pdi));
    214
    215	return 0;
    216}
    217
    218/* Parse PDA and write the records into the adapter
    219 *
    220 * Attempt to write every records that is in the specified pda
    221 * which also has a valid production data record for the firmware.
    222 */
    223int hermes_apply_pda(struct hermes *hw,
    224		     const char *first_pdr,
    225		     const void *pdr_end,
    226		     const __le16 *pda,
    227		     const void *pda_end)
    228{
    229	int ret;
    230	const struct pdi *pdi;
    231	const struct pdr *pdr;
    232
    233	pdr = (const struct pdr *) first_pdr;
    234	pda_end -= sizeof(struct pdi);
    235
    236	/* Go through every PDI and plug them into the adapter */
    237	pdi = (const struct pdi *) (pda + 2);
    238	while (((void *) pdi <= pda_end) &&
    239	       (pdi_id(pdi) != PDI_END)) {
    240		ret = hermes_plug_pdi(hw, pdr, pdi, pdr_end);
    241		if (ret)
    242			return ret;
    243
    244		/* Increment to the next PDI */
    245		pdi = (const struct pdi *) &pdi->data[pdi_len(pdi)];
    246	}
    247	return 0;
    248}
    249
    250/* Identify the total number of bytes in all blocks
    251 * including the header data.
    252 */
    253size_t
    254hermes_blocks_length(const char *first_block, const void *end)
    255{
    256	const struct dblock *blk = (const struct dblock *) first_block;
    257	int total_len = 0;
    258	int len;
    259
    260	end -= sizeof(*blk);
    261
    262	/* Skip all blocks to locate Plug Data References
    263	 * (Spectrum CS) */
    264	while (((void *) blk <= end) &&
    265	       (dblock_addr(blk) != BLOCK_END)) {
    266		len = dblock_len(blk);
    267		total_len += sizeof(*blk) + len;
    268		blk = (struct dblock *) &blk->data[len];
    269	}
    270
    271	return total_len;
    272}
    273
    274/*** Hermes programming ***/
    275
    276/* Program the data blocks */
    277int hermes_program(struct hermes *hw, const char *first_block, const void *end)
    278{
    279	const struct dblock *blk;
    280	u32 blkaddr;
    281	u32 blklen;
    282	int err = 0;
    283
    284	blk = (const struct dblock *) first_block;
    285
    286	if ((void *) blk > (end - sizeof(*blk)))
    287		return -EIO;
    288
    289	blkaddr = dblock_addr(blk);
    290	blklen = dblock_len(blk);
    291
    292	while ((blkaddr != BLOCK_END) &&
    293	       (((void *) blk + blklen) <= end)) {
    294		pr_debug(PFX "Programming block of length %d "
    295			 "to address 0x%08x\n", blklen, blkaddr);
    296
    297		err = hw->ops->program(hw, blk->data, blkaddr, blklen);
    298		if (err)
    299			break;
    300
    301		blk = (const struct dblock *) &blk->data[blklen];
    302
    303		if ((void *) blk > (end - sizeof(*blk)))
    304			return -EIO;
    305
    306		blkaddr = dblock_addr(blk);
    307		blklen = dblock_len(blk);
    308	}
    309	return err;
    310}
    311
    312/*** Default plugging data for Hermes I ***/
    313/* Values from wl_lkm_718/hcf/dhf.c */
    314
    315#define DEFINE_DEFAULT_PDR(pid, length, data)				\
    316static const struct {							\
    317	__le16 len;							\
    318	__le16 id;							\
    319	u8 val[length];							\
    320} __packed default_pdr_data_##pid = {			\
    321	cpu_to_le16((sizeof(default_pdr_data_##pid)/			\
    322				sizeof(__le16)) - 1),			\
    323	cpu_to_le16(pid),						\
    324	data								\
    325}
    326
    327#define DEFAULT_PDR(pid) default_pdr_data_##pid
    328
    329/*  HWIF Compatibility */
    330DEFINE_DEFAULT_PDR(0x0005, 10, "\x00\x00\x06\x00\x01\x00\x01\x00\x01\x00");
    331
    332/* PPPPSign */
    333DEFINE_DEFAULT_PDR(0x0108, 4, "\x00\x00\x00\x00");
    334
    335/* PPPPProf */
    336DEFINE_DEFAULT_PDR(0x0109, 10, "\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00");
    337
    338/* Antenna diversity */
    339DEFINE_DEFAULT_PDR(0x0150, 2, "\x00\x3F");
    340
    341/* Modem VCO band Set-up */
    342DEFINE_DEFAULT_PDR(0x0160, 28,
    343		   "\x00\x00\x00\x00\x00\x00\x00\x00"
    344		   "\x00\x00\x00\x00\x00\x00\x00\x00"
    345		   "\x00\x00\x00\x00\x00\x00\x00\x00"
    346		   "\x00\x00\x00\x00");
    347
    348/* Modem Rx Gain Table Values */
    349DEFINE_DEFAULT_PDR(0x0161, 256,
    350		   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
    351		   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
    352		   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
    353		   "\x3F\x01\x3F\01\x3F\x01\x3F\x01"
    354		   "\x3F\x01\x3E\01\x3E\x01\x3D\x01"
    355		   "\x3D\x01\x3C\01\x3C\x01\x3B\x01"
    356		   "\x3B\x01\x3A\01\x3A\x01\x39\x01"
    357		   "\x39\x01\x38\01\x38\x01\x37\x01"
    358		   "\x37\x01\x36\01\x36\x01\x35\x01"
    359		   "\x35\x01\x34\01\x34\x01\x33\x01"
    360		   "\x33\x01\x32\x01\x32\x01\x31\x01"
    361		   "\x31\x01\x30\x01\x30\x01\x7B\x01"
    362		   "\x7B\x01\x7A\x01\x7A\x01\x79\x01"
    363		   "\x79\x01\x78\x01\x78\x01\x77\x01"
    364		   "\x77\x01\x76\x01\x76\x01\x75\x01"
    365		   "\x75\x01\x74\x01\x74\x01\x73\x01"
    366		   "\x73\x01\x72\x01\x72\x01\x71\x01"
    367		   "\x71\x01\x70\x01\x70\x01\x68\x01"
    368		   "\x68\x01\x67\x01\x67\x01\x66\x01"
    369		   "\x66\x01\x65\x01\x65\x01\x57\x01"
    370		   "\x57\x01\x56\x01\x56\x01\x55\x01"
    371		   "\x55\x01\x54\x01\x54\x01\x53\x01"
    372		   "\x53\x01\x52\x01\x52\x01\x51\x01"
    373		   "\x51\x01\x50\x01\x50\x01\x48\x01"
    374		   "\x48\x01\x47\x01\x47\x01\x46\x01"
    375		   "\x46\x01\x45\x01\x45\x01\x44\x01"
    376		   "\x44\x01\x43\x01\x43\x01\x42\x01"
    377		   "\x42\x01\x41\x01\x41\x01\x40\x01"
    378		   "\x40\x01\x40\x01\x40\x01\x40\x01"
    379		   "\x40\x01\x40\x01\x40\x01\x40\x01"
    380		   "\x40\x01\x40\x01\x40\x01\x40\x01"
    381		   "\x40\x01\x40\x01\x40\x01\x40\x01");
    382
    383/* Write PDA according to certain rules.
    384 *
    385 * For every production data record, look for a previous setting in
    386 * the pda, and use that.
    387 *
    388 * For certain records, use defaults if they are not found in pda.
    389 */
    390int hermes_apply_pda_with_defaults(struct hermes *hw,
    391				   const char *first_pdr,
    392				   const void *pdr_end,
    393				   const __le16 *pda,
    394				   const void *pda_end)
    395{
    396	const struct pdr *pdr = (const struct pdr *) first_pdr;
    397	const struct pdi *first_pdi = (const struct pdi *) &pda[2];
    398	const struct pdi *pdi;
    399	const struct pdi *default_pdi = NULL;
    400	const struct pdi *outdoor_pdi;
    401	int record_id;
    402
    403	pdr_end -= sizeof(struct pdr);
    404
    405	while (((void *) pdr <= pdr_end) &&
    406	       (pdr_id(pdr) != PDI_END)) {
    407		/*
    408		 * For spectrum_cs firmwares,
    409		 * PDR area is currently not terminated by PDI_END.
    410		 * It's followed by CRC records, which have the type
    411		 * field where PDR has length.  The type can be 0 or 1.
    412		 */
    413		if (pdr_len(pdr) < 2)
    414			break;
    415		record_id = pdr_id(pdr);
    416
    417		pdi = hermes_find_pdi(first_pdi, record_id, pda_end);
    418		if (pdi)
    419			pr_debug(PFX "Found record 0x%04x at %p\n",
    420				 record_id, pdi);
    421
    422		switch (record_id) {
    423		case 0x110: /* Modem REFDAC values */
    424		case 0x120: /* Modem VGDAC values */
    425			outdoor_pdi = hermes_find_pdi(first_pdi, record_id + 1,
    426						      pda_end);
    427			default_pdi = NULL;
    428			if (outdoor_pdi) {
    429				pdi = outdoor_pdi;
    430				pr_debug(PFX
    431					 "Using outdoor record 0x%04x at %p\n",
    432					 record_id + 1, pdi);
    433			}
    434			break;
    435		case 0x5: /*  HWIF Compatibility */
    436			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0005);
    437			break;
    438		case 0x108: /* PPPPSign */
    439			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0108);
    440			break;
    441		case 0x109: /* PPPPProf */
    442			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0109);
    443			break;
    444		case 0x150: /* Antenna diversity */
    445			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0150);
    446			break;
    447		case 0x160: /* Modem VCO band Set-up */
    448			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0160);
    449			break;
    450		case 0x161: /* Modem Rx Gain Table Values */
    451			default_pdi = (struct pdi *) &DEFAULT_PDR(0x0161);
    452			break;
    453		default:
    454			default_pdi = NULL;
    455			break;
    456		}
    457		if (!pdi && default_pdi) {
    458			/* Use default */
    459			pdi = default_pdi;
    460			pr_debug(PFX "Using default record 0x%04x at %p\n",
    461				 record_id, pdi);
    462		}
    463
    464		if (pdi) {
    465			/* Lengths of the data in PDI and PDR must match */
    466			if ((pdi_len(pdi) == pdr_len(pdr)) &&
    467			    ((void *) pdi->data + pdi_len(pdi) < pda_end)) {
    468				/* do the actual plugging */
    469				hw->ops->program(hw, pdi->data, pdr_addr(pdr),
    470						 pdi_len(pdi));
    471			}
    472		}
    473
    474		pdr++;
    475	}
    476	return 0;
    477}