diff options
Diffstat (limited to 'drivers/staging/csr/csr_wifi_hip_xbv.c')
-rw-r--r-- | drivers/staging/csr/csr_wifi_hip_xbv.c | 1076 |
1 files changed, 1076 insertions, 0 deletions
diff --git a/drivers/staging/csr/csr_wifi_hip_xbv.c b/drivers/staging/csr/csr_wifi_hip_xbv.c new file mode 100644 index 0000000..071f80a --- /dev/null +++ b/drivers/staging/csr/csr_wifi_hip_xbv.c @@ -0,0 +1,1076 @@ +/***************************************************************************** + + (c) Cambridge Silicon Radio Limited 2012 + All rights reserved and confidential information of CSR + + Refer to LICENSE.txt included with this source for details + on the license terms. + +*****************************************************************************/ + +/* + * --------------------------------------------------------------------------- + * FILE: csr_wifi_hip_xbv.c + * + * PURPOSE: + * Routines for downloading firmware to UniFi. + * + * UniFi firmware files use a nested TLV (Tag-Length-Value) format. + * + * --------------------------------------------------------------------------- + */ +#include <linux/slab.h> + +#ifdef CSR_WIFI_XBV_TEST +/* Standalone test harness */ +#include "unifi_xbv.h" +#include "csr_wifi_hip_unifihw.h" +#else +/* Normal driver build */ +#include "csr_wifi_hip_unifiversion.h" +#include "csr_wifi_hip_card.h" +#define DBG_TAG(t) +#endif + +#include "csr_wifi_hip_xbv.h" + +#define STREAM_CHECKSUM 0x6d34 /* Sum of uint16s in each patch stream */ + +/* XBV sizes used in patch conversion + */ +#define PTDL_MAX_SIZE 2048 /* Max bytes allowed per PTDL */ +#define PTDL_HDR_SIZE (4 + 2 + 6 + 2) /* sizeof(fw_id, sec_len, patch_cmd, csum) */ + +/* Struct to represent a buffer for reading firmware file */ + +typedef struct +{ + void *dlpriv; + s32 ioffset; + fwreadfn_t iread; +} ct_t; + +/* Struct to represent a TLV field */ +typedef struct +{ + char t_name[4]; + u32 t_len; +} tag_t; + + +#define TAG_EQ(i, v) (((i)[0] == (v)[0]) && \ + ((i)[1] == (v)[1]) && \ + ((i)[2] == (v)[2]) && \ + ((i)[3] == (v)[3])) + +/* We create a small stack on the stack that contains an enum + * indicating the containing list segments, and the offset at which + * those lists end. This enables a lot more error checking. */ +typedef enum +{ + xbv_xbv1, + /*xbv_info,*/ + xbv_fw, + xbv_vers, + xbv_vand, + xbv_ptch, + xbv_other +} xbv_container; + +#define XBV_STACK_SIZE 6 +#define XBV_MAX_OFFS 0x7fffffff + +typedef struct +{ + struct + { + xbv_container container; + s32 ioffset_end; + } s[XBV_STACK_SIZE]; + u32 ptr; +} xbv_stack_t; + +static s32 read_tag(card_t *card, ct_t *ct, tag_t *tag); +static s32 read_bytes(card_t *card, ct_t *ct, void *buf, u32 len); +static s32 read_uint(card_t *card, ct_t *ct, u32 *u, u32 len); +static s32 xbv_check(xbv1_t *fwinfo, const xbv_stack_t *stack, + xbv_mode new_mode, xbv_container old_cont); +static s32 xbv_push(xbv1_t *fwinfo, xbv_stack_t *stack, + xbv_mode new_mode, xbv_container old_cont, + xbv_container new_cont, u32 ioff); + +static u32 write_uint16(void *buf, const u32 offset, + const u16 val); +static u32 write_uint32(void *buf, const u32 offset, + const u32 val); +static u32 write_bytes(void *buf, const u32 offset, + const u8 *data, const u32 len); +static u32 write_tag(void *buf, const u32 offset, + const char *tag_str); +static u32 write_chunk(void *buf, const u32 offset, + const char *tag_str, + const u32 payload_len); +static u16 calc_checksum(void *buf, const u32 offset, + const u32 bytes_len); +static u32 calc_patch_size(const xbv1_t *fwinfo); + +static u32 write_xbv_header(void *buf, const u32 offset, + const u32 file_payload_length); +static u32 write_ptch_header(void *buf, const u32 offset, + const u32 fw_id); +static u32 write_patchcmd(void *buf, const u32 offset, + const u32 dst_genaddr, const u16 len); +static u32 write_reset_ptdl(void *buf, const u32 offset, + const xbv1_t *fwinfo, u32 fw_id); +static u32 write_fwdl_to_ptdl(void *buf, const u32 offset, + fwreadfn_t readfn, const struct FWDL *fwdl, + const void *fw_buf, const u32 fw_id, + void *rdbuf); + +/* + * --------------------------------------------------------------------------- + * parse_xbv1 + * + * Scan the firmware file to find the TLVs we are interested in. + * Actions performed: + * - check we support the file format version in VERF + * Store these TLVs if we have a firmware image: + * - SLTP Symbol Lookup Table Pointer + * - FWDL firmware download segments + * - FWOL firmware overlay segment + * - VMEQ Register probe tests to verify matching h/w + * Store these TLVs if we have a patch file: + * - FWID the firmware build ID that this file patches + * - PTDL The actual patches + * + * The structure pointed to by fwinfo is cleared and + * 'fwinfo->mode' is set to 'unknown'. The 'fwinfo->mode' + * variable is set to 'firmware' or 'patch' once we know which + * sort of XBV file we have. + * + * Arguments: + * readfn Pointer to function to call to read from the file. + * dlpriv Opaque pointer arg to pass to readfn. + * fwinfo Pointer to fwinfo struct to fill in. + * + * Returns: + * CSR_RESULT_SUCCESS on success, CSR error code on failure + * --------------------------------------------------------------------------- + */ +CsrResult xbv1_parse(card_t *card, fwreadfn_t readfn, void *dlpriv, xbv1_t *fwinfo) +{ + ct_t ct; + tag_t tag; + xbv_stack_t stack; + + ct.dlpriv = dlpriv; + ct.ioffset = 0; + ct.iread = readfn; + + memset(fwinfo, 0, sizeof(xbv1_t)); + fwinfo->mode = xbv_unknown; + + /* File must start with XBV1 triplet */ + if (read_tag(card, &ct, &tag) <= 0) + { + unifi_error(NULL, "File is not UniFi firmware\n"); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + DBG_TAG(tag.t_name); + + if (!TAG_EQ(tag.t_name, "XBV1")) + { + unifi_error(NULL, "File is not UniFi firmware (%s)\n", tag.t_name); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + stack.ptr = 0; + stack.s[stack.ptr].container = xbv_xbv1; + stack.s[stack.ptr].ioffset_end = XBV_MAX_OFFS; + + /* Now scan the file */ + while (1) + { + s32 n; + + n = read_tag(card, &ct, &tag); + if (n < 0) + { + unifi_error(NULL, "No tag\n"); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + if (n == 0) + { + /* End of file */ + break; + } + + DBG_TAG(tag.t_name); + + /* File format version */ + if (TAG_EQ(tag.t_name, "VERF")) + { + u32 version; + + if (xbv_check(fwinfo, &stack, xbv_unknown, xbv_xbv1) || + (tag.t_len != 2) || + read_uint(card, &ct, &version, 2)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + if (version != 0) + { + unifi_error(NULL, "Unsupported firmware file version: %d.%d\n", + version >> 8, version & 0xFF); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + } + else if (TAG_EQ(tag.t_name, "LIST")) + { + char name[4]; + u32 list_end; + + list_end = ct.ioffset + tag.t_len; + + if (read_bytes(card, &ct, name, 4)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + DBG_TAG(name); + if (TAG_EQ(name, "FW ")) + { + if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_xbv1, xbv_fw, list_end)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + } + else if (TAG_EQ(name, "VERS")) + { + if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_fw, xbv_vers, list_end) || + (fwinfo->vers.num_vand != 0)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + } + else if (TAG_EQ(name, "VAND")) + { + struct VAND *vand; + + if (xbv_push(fwinfo, &stack, xbv_firmware, xbv_vers, xbv_vand, list_end) || + (fwinfo->vers.num_vand >= MAX_VAND)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + /* Get a new VAND */ + vand = fwinfo->vand + fwinfo->vers.num_vand++; + + /* Fill it in */ + vand->first = fwinfo->num_vmeq; + vand->count = 0; + } + else if (TAG_EQ(name, "PTCH")) + { + if (xbv_push(fwinfo, &stack, xbv_patch, xbv_xbv1, xbv_ptch, list_end)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + } + else + { + /* Skip over any other lists. We dont bother to push + * the new list type now as we would only pop it at + * the end of the outer loop. */ + ct.ioffset += tag.t_len - 4; + } + } + else if (TAG_EQ(tag.t_name, "SLTP")) + { + u32 addr; + + if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) || + (tag.t_len != 4) || + (fwinfo->slut_addr != 0) || + read_uint(card, &ct, &addr, 4)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + fwinfo->slut_addr = addr; + } + else if (TAG_EQ(tag.t_name, "FWDL")) + { + u32 addr; + struct FWDL *fwdl; + + if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) || + (fwinfo->num_fwdl >= MAX_FWDL) || + (read_uint(card, &ct, &addr, 4))) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + fwdl = fwinfo->fwdl + fwinfo->num_fwdl++; + + fwdl->dl_size = tag.t_len - 4; + fwdl->dl_addr = addr; + fwdl->dl_offset = ct.ioffset; + + ct.ioffset += tag.t_len - 4; + } + else if (TAG_EQ(tag.t_name, "FWOV")) + { + if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_fw) || + (fwinfo->fwov.dl_size != 0) || + (fwinfo->fwov.dl_offset != 0)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + fwinfo->fwov.dl_size = tag.t_len; + fwinfo->fwov.dl_offset = ct.ioffset; + + ct.ioffset += tag.t_len; + } + else if (TAG_EQ(tag.t_name, "VMEQ")) + { + u32 temp[3]; + struct VAND *vand; + struct VMEQ *vmeq; + + if (xbv_check(fwinfo, &stack, xbv_firmware, xbv_vand) || + (fwinfo->num_vmeq >= MAX_VMEQ) || + (fwinfo->vers.num_vand == 0) || + (tag.t_len != 8) || + read_uint(card, &ct, &temp[0], 4) || + read_uint(card, &ct, &temp[1], 2) || + read_uint(card, &ct, &temp[2], 2)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + /* Get the last VAND */ + vand = fwinfo->vand + (fwinfo->vers.num_vand - 1); + + /* Get a new VMEQ */ + vmeq = fwinfo->vmeq + fwinfo->num_vmeq++; + + /* Note that this VAND contains another VMEQ */ + vand->count++; + + /* Fill in the VMEQ */ + vmeq->addr = temp[0]; + vmeq->mask = (u16)temp[1]; + vmeq->value = (u16)temp[2]; + } + else if (TAG_EQ(tag.t_name, "FWID")) + { + u32 build_id; + + if (xbv_check(fwinfo, &stack, xbv_patch, xbv_ptch) || + (tag.t_len != 4) || + (fwinfo->build_id != 0) || + read_uint(card, &ct, &build_id, 4)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + fwinfo->build_id = build_id; + } + else if (TAG_EQ(tag.t_name, "PTDL")) + { + struct PTDL *ptdl; + + if (xbv_check(fwinfo, &stack, xbv_patch, xbv_ptch) || + (fwinfo->num_ptdl >= MAX_PTDL)) + { + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + /* Allocate a new PTDL */ + ptdl = fwinfo->ptdl + fwinfo->num_ptdl++; + + ptdl->dl_size = tag.t_len; + ptdl->dl_offset = ct.ioffset; + + ct.ioffset += tag.t_len; + } + else + { + /* + * If we get here it is a tag we are not interested in, + * just skip over it. + */ + ct.ioffset += tag.t_len; + } + + /* Check to see if we are at the end of the currently stacked + * segment. We could finish more than one list at a time. */ + while (ct.ioffset >= stack.s[stack.ptr].ioffset_end) + { + if (ct.ioffset > stack.s[stack.ptr].ioffset_end) + { + unifi_error(NULL, + "XBV file has overrun stack'd segment %d (%d > %d)\n", + stack.ptr, ct.ioffset, stack.s[stack.ptr].ioffset_end); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + if (stack.ptr <= 0) + { + unifi_error(NULL, "XBV file has underrun stack pointer\n"); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + stack.ptr--; + } + } + + if (stack.ptr != 0) + { + unifi_error(NULL, "Last list of XBV is not complete.\n"); + return CSR_WIFI_HIP_RESULT_INVALID_VALUE; + } + + return CSR_RESULT_SUCCESS; +} /* xbv1_parse() */ + + +/* Check the the XBV file is of a consistant sort (either firmware or + * patch) and that we are in the correct containing list type. */ +static s32 xbv_check(xbv1_t *fwinfo, const xbv_stack_t *stack, + xbv_mode new_mode, xbv_container old_cont) +{ + /* If the new file mode is unknown the current packet could be in + * either (any) type of XBV file, and we cant make a decission at + * this time. */ + if (new_mode != xbv_unknown) + { + if (fwinfo->mode == xbv_unknown) + { + fwinfo->mode = new_mode; + } + else if (fwinfo->mode != new_mode) + { + return -1; + } + } + /* If the current stack top doesn't match what we expect then the + * file is corrupt. */ + if (stack->s[stack->ptr].container != old_cont) + { + return -1; + } + return 0; +} + + +/* Make checks as above and then enter a new list */ +static s32 xbv_push(xbv1_t *fwinfo, xbv_stack_t *stack, + xbv_mode new_mode, xbv_container old_cont, + xbv_container new_cont, u32 new_ioff) +{ + if (xbv_check(fwinfo, stack, new_mode, old_cont)) + { + return -1; + } + + /* Check that our stack won't overflow. */ + if (stack->ptr >= (XBV_STACK_SIZE - 1)) + { + return -1; + } + + /* Add the new list element to the top of the stack. */ + stack->ptr++; + stack->s[stack->ptr].container = new_cont; + stack->s[stack->ptr].ioffset_end = new_ioff; + + return 0; +} + + +static u32 xbv2uint(u8 *ptr, s32 len) +{ + u32 u = 0; + s16 i; + + for (i = 0; i < len; i++) + { + u32 b; + b = ptr[i]; + u += b << (i * 8); + } + return u; +} + + +static s32 read_tag(card_t *card, ct_t *ct, tag_t *tag) +{ + u8 buf[8]; + s32 n; + + n = (*ct->iread)(card->ospriv, ct->dlpriv, ct->ioffset, buf, 8); + if (n <= 0) + { + return n; + } + + /* read the tag and length */ + if (n != 8) + { + return -1; + } + + /* get section tag */ + memcpy(tag->t_name, buf, 4); + + /* get section length */ + tag->t_len = xbv2uint(buf + 4, 4); + + ct->ioffset += 8; + + return 8; +} /* read_tag() */ + + +static s32 read_bytes(card_t *card, ct_t *ct, void *buf, u32 len) +{ + /* read the tag value */ + if ((*ct->iread)(card->ospriv, ct->dlpriv, ct->ioffset, buf, len) != (s32)len) + { + return -1; + } + + ct->ioffset += len; + + return 0; +} /* read_bytes() */ + + +static s32 read_uint(card_t *card, ct_t *ct, u32 *u, u32 len) +{ + u8 buf[4]; + + /* Integer cannot be more than 4 bytes */ + if (len > 4) + { + return -1; + } + + if (read_bytes(card, ct, buf, len)) + { + return -1; + } + + *u = xbv2uint(buf, len); + + return 0; +} /* read_uint() */ + + +static u32 write_uint16(void *buf, const u32 offset, const u16 val) +{ + u8 *dst = (u8 *)buf + offset; + *dst++ = (u8)(val & 0xff); /* LSB first */ + *dst = (u8)(val >> 8); + return sizeof(u16); +} + + +static u32 write_uint32(void *buf, const u32 offset, const u32 val) +{ + (void)write_uint16(buf, offset + 0, (u16)(val & 0xffff)); + (void)write_uint16(buf, offset + 2, (u16)(val >> 16)); + return sizeof(u32); +} + + +static u32 write_bytes(void *buf, const u32 offset, const u8 *data, const u32 len) +{ + u32 i; + u8 *dst = (u8 *)buf + offset; + + for (i = 0; i < len; i++) + { + *dst++ = *((u8 *)data + i); + } + return len; +} + + +static u32 write_tag(void *buf, const u32 offset, const char *tag_str) +{ + u8 *dst = (u8 *)buf + offset; + memcpy(dst, tag_str, 4); + return 4; +} + + +static u32 write_chunk(void *buf, const u32 offset, const char *tag_str, const u32 payload_len) +{ + u32 written = 0; + written += write_tag(buf, offset, tag_str); + written += write_uint32(buf, written + offset, (u32)payload_len); + + return written; +} + + +static u16 calc_checksum(void *buf, const u32 offset, const u32 bytes_len) +{ + u32 i; + u8 *src = (u8 *)buf + offset; + u16 sum = 0; + u16 val; + + for (i = 0; i < bytes_len / 2; i++) + { + /* Contents copied to file is LE, host might not be */ + val = (u16) * src++; /* LSB */ + val += (u16)(*src++) << 8; /* MSB */ + sum += val; + } + + /* Total of uint16s in the stream plus the stored check value + * should equal STREAM_CHECKSUM when decoded. + */ + return (STREAM_CHECKSUM - sum); +} + + +#define PTDL_RESET_DATA_SIZE 20 /* Size of reset vectors PTDL */ + +static u32 calc_patch_size(const xbv1_t *fwinfo) +{ + s16 i; + u32 size = 0; + + /* + * Work out how big an equivalent patch format file must be for this image. + * This only needs to be approximate, so long as it's large enough. + */ + if (fwinfo->mode != xbv_firmware) + { + return 0; + } + + /* Payload (which will get put into a series of PTDLs) */ + for (i = 0; i < fwinfo->num_fwdl; i++) + { + size += fwinfo->fwdl[i].dl_size; + } + + /* Another PTDL at the end containing reset vectors */ + size += PTDL_RESET_DATA_SIZE; + + /* PTDL headers. Add one for remainder, one for reset vectors */ + size += ((fwinfo->num_fwdl / PTDL_MAX_SIZE) + 2) * PTDL_HDR_SIZE; + + /* Another 1K sufficient to cover miscellaneous headers */ + size += 1024; + + return size; +} + + +static u32 write_xbv_header(void *buf, const u32 offset, const u32 file_payload_length) +{ + u32 written = 0; + + /* The length value given to the XBV chunk is the length of all subsequent + * contents of the file, excluding the 8 byte size of the XBV1 header itself + * (The added 6 bytes thus accounts for the size of the VERF) + */ + written += write_chunk(buf, offset + written, (char *)"XBV1", file_payload_length + 6); + + written += write_chunk(buf, offset + written, (char *)"VERF", 2); + written += write_uint16(buf, offset + written, 0); /* File version */ + + return written; +} + + +static u32 write_ptch_header(void *buf, const u32 offset, const u32 fw_id) +{ + u32 written = 0; + + /* LIST is written with a zero length, to be updated later */ + written += write_chunk(buf, offset + written, (char *)"LIST", 0); + written += write_tag(buf, offset + written, (char *)"PTCH"); /* List type */ + + written += write_chunk(buf, offset + written, (char *)"FWID", 4); + written += write_uint32(buf, offset + written, fw_id); + + + return written; +} + + +#define UF_REGION_PHY 1 +#define UF_REGION_MAC 2 +#define UF_MEMPUT_MAC 0x0000 +#define UF_MEMPUT_PHY 0x1000 + +static u32 write_patchcmd(void *buf, const u32 offset, const u32 dst_genaddr, const u16 len) +{ + u32 written = 0; + u32 region = (dst_genaddr >> 28); + u16 cmd_and_len = UF_MEMPUT_MAC; + + if (region == UF_REGION_PHY) + { + cmd_and_len = UF_MEMPUT_PHY; + } + else if (region != UF_REGION_MAC) + { + return 0; /* invalid */ + } + + /* Write the command and data length */ + cmd_and_len |= len; + written += write_uint16(buf, offset + written, cmd_and_len); + + /* Write the destination generic address */ + written += write_uint16(buf, offset + written, (u16)(dst_genaddr >> 16)); + written += write_uint16(buf, offset + written, (u16)(dst_genaddr & 0xffff)); + + /* The data payload should be appended to the command */ + return written; +} + + +static u32 write_fwdl_to_ptdl(void *buf, const u32 offset, fwreadfn_t readfn, + const struct FWDL *fwdl, const void *dlpriv, + const u32 fw_id, void *fw_buf) +{ + u32 written = 0; + s16 chunks = 0; + u32 left = fwdl->dl_size; /* Bytes left in this fwdl */ + u32 dl_addr = fwdl->dl_addr; /* Target address of fwdl image on XAP */ + u32 dl_offs = fwdl->dl_offset; /* Offset of fwdl image data in source */ + u16 csum; + u32 csum_start_offs; /* first offset to include in checksum */ + u32 sec_data_len; /* section data byte count */ + u32 sec_len; /* section data + header byte count */ + + /* FWDL maps to one or more PTDLs, as max size for a PTDL is 1K words */ + while (left) + { + /* Calculate amount to be transferred */ + sec_data_len = CSRMIN(left, PTDL_MAX_SIZE - PTDL_HDR_SIZE); + sec_len = sec_data_len + PTDL_HDR_SIZE; + + /* Write PTDL header + entire PTDL size */ + written += write_chunk(buf, offset + written, (char *)"PTDL", sec_len); + /* bug digest implies 4 bytes of padding here, but that seems wrong */ + + /* Checksum starts here */ + csum_start_offs = offset + written; + + /* Patch-chunk header: fw_id. Note that this is in XAP word order */ + written += write_uint16(buf, offset + written, (u16)(fw_id >> 16)); + written += write_uint16(buf, offset + written, (u16)(fw_id & 0xffff)); + + /* Patch-chunk header: section length in uint16s */ + written += write_uint16(buf, offset + written, (u16)(sec_len / 2)); + + + /* Write the appropriate patch command for the data's destination ptr */ + written += write_patchcmd(buf, offset + written, dl_addr, (u16)(sec_data_len / 2)); + + /* Write the data itself (limited to the max chunk length) */ + if (readfn(NULL, (void *)dlpriv, dl_offs, fw_buf, sec_data_len) < 0) + { + return 0; + } + + written += write_bytes(buf, + offset + written, + fw_buf, + sec_data_len); + + /* u16 checksum calculated over data written */ + csum = calc_checksum(buf, csum_start_offs, written - (csum_start_offs - offset)); + written += write_uint16(buf, offset + written, csum); + + left -= sec_data_len; + dl_addr += sec_data_len; + dl_offs += sec_data_len; + chunks++; + } + + return written; +} + + +#define SEC_CMD_LEN ((4 + 2) * 2) /* sizeof(cmd, vector) per XAP */ +#define PTDL_VEC_HDR_SIZE (4 + 2 + 2) /* sizeof(fw_id, sec_len, csum) */ +#define UF_MAC_START_VEC 0x00c00000 /* Start address of image on MAC */ +#define UF_PHY_START_VEC 0x00c00000 /* Start address of image on PHY */ +#define UF_MAC_START_CMD 0x6000 /* MAC "Set start address" command */ +#define UF_PHY_START_CMD 0x7000 /* PHY "Set start address" command */ + +static u32 write_reset_ptdl(void *buf, const u32 offset, const xbv1_t *fwinfo, u32 fw_id) +{ + u32 written = 0; + u16 csum; + u32 csum_start_offs; /* first offset to include in checksum */ + u32 sec_len; /* section data + header byte count */ + + sec_len = SEC_CMD_LEN + PTDL_VEC_HDR_SIZE; /* Total section byte length */ + + /* Write PTDL header + entire PTDL size */ + written += write_chunk(buf, offset + written, (char *)"PTDL", sec_len); + + /* Checksum starts here */ + csum_start_offs = offset + written; + + /* Patch-chunk header: fw_id. Note that this is in XAP word order */ + written += write_uint16(buf, offset + written, (u16)(fw_id >> 16)); + written += write_uint16(buf, offset + written, (u16)(fw_id & 0xffff)); + + /* Patch-chunk header: section length in uint16s */ + written += write_uint16(buf, offset + written, (u16)(sec_len / 2)); + + /* + * Restart addresses to be executed on subsequent loader restart command. + */ + + /* Setup the MAC start address, note word ordering */ + written += write_uint16(buf, offset + written, UF_MAC_START_CMD); + written += write_uint16(buf, offset + written, (UF_MAC_START_VEC >> 16)); + written += write_uint16(buf, offset + written, (UF_MAC_START_VEC & 0xffff)); + + /* Setup the PHY start address, note word ordering */ + written += write_uint16(buf, offset + written, UF_PHY_START_CMD); + written += write_uint16(buf, offset + written, (UF_PHY_START_VEC >> 16)); + written += write_uint16(buf, offset + written, (UF_PHY_START_VEC & 0xffff)); + + /* u16 checksum calculated over data written */ + csum = calc_checksum(buf, csum_start_offs, written - (csum_start_offs - offset)); + written += write_uint16(buf, offset + written, csum); + + return written; +} + + +/* + * --------------------------------------------------------------------------- + * read_slut + * + * desc + * + * Arguments: + * readfn Pointer to function to call to read from the file. + * dlpriv Opaque pointer arg to pass to readfn. + * addr Offset into firmware image of SLUT. + * fwinfo Pointer to fwinfo struct to fill in. + * + * Returns: + * Number of SLUT entries in the f/w, or -1 if the image was corrupt. + * --------------------------------------------------------------------------- + */ +s32 xbv1_read_slut(card_t *card, fwreadfn_t readfn, void *dlpriv, xbv1_t *fwinfo, + symbol_t *slut, u32 slut_len) +{ + s16 i; + s32 offset; + u32 magic; + u32 count = 0; + ct_t ct; + + if (fwinfo->mode != xbv_firmware) + { + return -1; + } + + /* Find the d/l segment containing the SLUT */ + /* This relies on the SLUT being entirely contained in one segment */ + offset = -1; + for (i = 0; i < fwinfo->num_fwdl; i++) + { + if ((fwinfo->slut_addr >= fwinfo->fwdl[i].dl_addr) && + (fwinfo->slut_addr < (fwinfo->fwdl[i].dl_addr + fwinfo->fwdl[i].dl_size))) + { + offset = fwinfo->fwdl[i].dl_offset + + (fwinfo->slut_addr - fwinfo->fwdl[i].dl_addr); + } + } + if (offset < 0) + { + return -1; + } + + ct.dlpriv = dlpriv; + ct.ioffset = offset; + ct.iread = readfn; + + if (read_uint(card, &ct, &magic, 2)) + { + return -1; + } + if (magic != SLUT_FINGERPRINT) + { + return -1; + } + + while (count < slut_len) + { + u32 id, obj; + + /* Read Symbol Id */ + if (read_uint(card, &ct, &id, 2)) + { + return -1; + } + + /* Check for end of table marker */ + if (id == CSR_SLT_END) + { + break; + } + + /* Read Symbol Value */ + if (read_uint(card, &ct, &obj, 4)) + { + return -1; + } + + slut[count].id = (u16)id; + slut[count].obj = obj; + count++; + } + + return count; +} /* read_slut() */ + + +/* + * --------------------------------------------------------------------------- + * xbv_to_patch + * + * Convert (the relevant parts of) a firmware xbv file into a patch xbv + * + * Arguments: + * card + * fw_buf - pointer to xbv firmware image + * fwinfo - structure describing the firmware image + * size - pointer to location into which size of f/w is written. + * + * Returns: + * Pointer to firmware image, or NULL on error. Caller must free this + * buffer via kfree() once it's finished with. + * + * Notes: + * The input fw_buf should have been checked via xbv1_parse prior to + * calling this function, so the input image is assumed valid. + * --------------------------------------------------------------------------- + */ +#define PTCH_LIST_SIZE 16 /* sizeof PTCH+FWID chunk in LIST header */ + +void* xbv_to_patch(card_t *card, fwreadfn_t readfn, + const void *fw_buf, const xbv1_t *fwinfo, u32 *size) +{ + void *patch_buf = NULL; + u32 patch_buf_size; + u32 payload_offs = 0; /* Start of XBV payload */ + s16 i; + u32 patch_offs = 0; + u32 list_len_offs = 0; /* Offset of PTDL LIST length parameter */ + u32 ptdl_start_offs = 0; /* Offset of first PTDL chunk */ + u32 fw_id; + void *rdbuf; + + if (!fw_buf || !fwinfo || !card) + { + return NULL; + } + + if (fwinfo->mode != xbv_firmware) + { + unifi_error(NULL, "Not a firmware file\n"); + return NULL; + } + + /* Pre-allocate read buffer for chunk conversion */ + rdbuf = kmalloc(PTDL_MAX_SIZE, GFP_KERNEL); + if (!rdbuf) + { + unifi_error(card, "Couldn't alloc conversion buffer\n"); + return NULL; + } + + /* Loader requires patch file's build ID to match the running firmware's */ + fw_id = card->build_id; + + /* Firmware XBV1 contains VERF, optional INFO, SLUT(s), FWDL(s) */ + /* Other chunks should get skipped. */ + /* VERF should be sanity-checked against chip version */ + + /* Patch XBV1 contains VERF, optional INFO, PTCH */ + /* PTCH contains FWID, optional INFO, PTDL(s), PTDL(start_vec) */ + /* Each FWDL is split into PTDLs (each is 1024 XAP words max) */ + /* Each PTDL contains running ROM f/w version, and checksum */ + /* MAC/PHY reset addresses (known) are added into a final PTDL */ + + /* The input image has already been parsed, and loaded into fwinfo, so we + * can use that to build the output image + */ + patch_buf_size = calc_patch_size(fwinfo); + + patch_buf = kmalloc(patch_buf_size, GFP_KERNEL); + if (!patch_buf) + { + kfree(rdbuf); + unifi_error(NULL, "Can't malloc buffer for patch conversion\n"); + return NULL; + } + + memset(patch_buf, 0xdd, patch_buf_size); + + /* Write XBV + VERF headers */ + patch_offs += write_xbv_header(patch_buf, patch_offs, 0); + payload_offs = patch_offs; + + /* Write patch (LIST) header */ + list_len_offs = patch_offs + 4; /* Save LIST.length offset for later update */ + patch_offs += write_ptch_header(patch_buf, patch_offs, fw_id); + + /* Save start offset of the PTDL chunks */ + ptdl_start_offs = patch_offs; + + /* Write LIST of firmware PTDL blocks */ + for (i = 0; i < fwinfo->num_fwdl; i++) + { + patch_offs += write_fwdl_to_ptdl(patch_buf, + patch_offs, + readfn, + &fwinfo->fwdl[i], + fw_buf, + fw_id, + rdbuf); + } + + /* Write restart-vector PTDL last */ + patch_offs += write_reset_ptdl(patch_buf, patch_offs, fwinfo, fw_id); + + /* Now the length is known, update the LIST.length */ + (void)write_uint32(patch_buf, list_len_offs, + (patch_offs - ptdl_start_offs) + PTCH_LIST_SIZE); + + /* Re write XBV headers just to fill in the correct file size */ + (void)write_xbv_header(patch_buf, 0, (patch_offs - payload_offs)); + + unifi_trace(card->ospriv, UDBG1, "XBV:PTCH size %u, fw_id %u\n", + patch_offs, fw_id); + if (size) + { + *size = patch_offs; + } + kfree(rdbuf); + + return patch_buf; +} + + |