/***************************************************************************** (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 #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 = min_t(u32, 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; }