mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 10:19:47 +00:00
![Greg Kroah-Hartman](/assets/img/avatar_default.png)
It's just calling kmalloc(, GFP_KERNEL), so call that instead. A few places should be calling kzalloc(), so do that, and remove the call to memset at the same time. Cc: Mikko Virkkilä <mikko.virkkila@bluegiga.com> Cc: Lauri Hintsala <Lauri.Hintsala@bluegiga.com> Cc: Riku Mettälä <riku.mettala@bluegiga.com> Cc: Veli-Pekka Peltola <veli-pekka.peltola@bluegiga.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1076 lines
32 KiB
C
1076 lines
32 KiB
C
/*****************************************************************************
|
|
|
|
(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;
|
|
}
|
|
|
|
|