| /***************************************************************************** |
| |
| (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 = 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; |
| } |
| |
| |