838 lines
25 KiB
C
838 lines
25 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_download.c
|
|
*
|
|
* PURPOSE:
|
|
* Routines for downloading firmware to UniFi.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
#include <linux/slab.h>
|
|
#include "csr_wifi_hip_unifi.h"
|
|
#include "csr_wifi_hip_unifiversion.h"
|
|
#include "csr_wifi_hip_card.h"
|
|
#include "csr_wifi_hip_xbv.h"
|
|
|
|
#undef CSR_WIFI_IGNORE_PATCH_VERSION_MISMATCH
|
|
|
|
static CsrResult do_patch_download(card_t *card, void *dlpriv,
|
|
xbv1_t *pfwinfo, u32 boot_ctrl_addr);
|
|
|
|
static CsrResult do_patch_convert_download(card_t *card,
|
|
void *dlpriv, xbv1_t *pfwinfo);
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* _find_in_slut
|
|
*
|
|
* Find the offset of the appropriate object in the SLUT of a card
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card struct
|
|
* psym Pointer to symbol object.
|
|
* id set up by caller
|
|
* obj will be set up by this function
|
|
* pslut Pointer to SLUT address, if 0xffffffff then it must be
|
|
* read from the chip.
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success
|
|
* Non-zero on error,
|
|
* CSR_WIFI_HIP_RESULT_NOT_FOUND if not found
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
static CsrResult _find_in_slut(card_t *card, symbol_t *psym, u32 *pslut)
|
|
{
|
|
u32 slut_address;
|
|
u16 finger_print;
|
|
CsrResult r;
|
|
CsrResult csrResult;
|
|
|
|
/* Get SLUT address */
|
|
if (*pslut == 0xffffffff)
|
|
{
|
|
r = card_wait_for_firmware_to_start(card, &slut_address);
|
|
if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
|
|
{
|
|
return r;
|
|
}
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Firmware hasn't started\n");
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
*pslut = slut_address;
|
|
|
|
/*
|
|
* Firmware has started so set the SDIO bus clock to the initial speed,
|
|
* faster than UNIFI_SDIO_CLOCK_SAFE_HZ, to speed up the f/w download.
|
|
*/
|
|
csrResult = CsrSdioMaxBusClockFrequencySet(card->sdio_if, UNIFI_SDIO_CLOCK_INIT_HZ);
|
|
if (csrResult != CSR_RESULT_SUCCESS)
|
|
{
|
|
r = ConvertCsrSdioToCsrHipResult(card, csrResult);
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
card->sdio_clock_speed = UNIFI_SDIO_CLOCK_INIT_HZ;
|
|
}
|
|
else
|
|
{
|
|
slut_address = *pslut; /* Use previously discovered address */
|
|
}
|
|
unifi_trace(card->ospriv, UDBG4, "SLUT addr: 0x%lX\n", slut_address);
|
|
|
|
/*
|
|
* Check the SLUT fingerprint.
|
|
* The slut_address is a generic pointer so we must use unifi_card_read16().
|
|
*/
|
|
unifi_trace(card->ospriv, UDBG4, "Looking for SLUT finger print\n");
|
|
finger_print = 0;
|
|
r = unifi_card_read16(card, slut_address, &finger_print);
|
|
if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
|
|
{
|
|
return r;
|
|
}
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to read SLUT finger print\n");
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
|
|
if (finger_print != SLUT_FINGERPRINT)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to find SLUT fingerprint\n");
|
|
func_exit_r(CSR_RESULT_FAILURE);
|
|
return CSR_RESULT_FAILURE;
|
|
}
|
|
|
|
/* Symbol table starts imedately after the fingerprint */
|
|
slut_address += 2;
|
|
|
|
while (1)
|
|
{
|
|
u16 id;
|
|
u32 obj;
|
|
|
|
r = unifi_card_read16(card, slut_address, &id);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
slut_address += 2;
|
|
|
|
if (id == CSR_SLT_END)
|
|
{
|
|
/* End of table reached: not found */
|
|
r = CSR_WIFI_HIP_RESULT_RANGE;
|
|
break;
|
|
}
|
|
|
|
r = unifi_read32(card, slut_address, &obj);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
slut_address += 4;
|
|
|
|
unifi_trace(card->ospriv, UDBG3, " found SLUT id %02d.%08lx\n", id, obj);
|
|
|
|
r = CSR_WIFI_HIP_RESULT_NOT_FOUND;
|
|
/* Found search term? */
|
|
if (id == psym->id)
|
|
{
|
|
unifi_trace(card->ospriv, UDBG1, " matched SLUT id %02d.%08lx\n", id, obj);
|
|
psym->obj = obj;
|
|
r = CSR_RESULT_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* do_patch_convert_download
|
|
*
|
|
* Download the given firmware image to the UniFi, converting from FWDL
|
|
* to PTDL XBV format.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card struct
|
|
* dlpriv Pointer to source firmware image
|
|
* fwinfo Pointer to source firmware info struct
|
|
*
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success, CSR error code on error
|
|
*
|
|
* Notes:
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
static CsrResult do_patch_convert_download(card_t *card, void *dlpriv, xbv1_t *pfwinfo)
|
|
{
|
|
CsrResult r;
|
|
u32 slut_base = 0xffffffff;
|
|
void *pfw;
|
|
u32 psize;
|
|
symbol_t sym;
|
|
|
|
/* Reset the chip to guarantee that the ROM loader is running */
|
|
r = unifi_init(card);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv,
|
|
"do_patch_convert_download: failed to re-init UniFi\n");
|
|
return r;
|
|
}
|
|
|
|
/* If no unifi_helper is running, the firmware version must be read */
|
|
if (card->build_id == 0)
|
|
{
|
|
u32 ver = 0;
|
|
sym.id = CSR_SLT_BUILD_ID_NUMBER;
|
|
sym.obj = 0; /* To be updated by _find_in_slut() */
|
|
|
|
unifi_trace(card->ospriv, UDBG1, "Need f/w version\n");
|
|
|
|
/* Find chip build id entry in SLUT */
|
|
r = _find_in_slut(card, &sym, &slut_base);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to find CSR_SLT_BUILD_ID_NUMBER\n");
|
|
return CSR_RESULT_FAILURE;
|
|
}
|
|
|
|
/* Read running f/w version */
|
|
r = unifi_read32(card, sym.obj, &ver);
|
|
if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
|
|
{
|
|
return r;
|
|
}
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to read f/w id\n");
|
|
return CSR_RESULT_FAILURE;
|
|
}
|
|
card->build_id = ver;
|
|
}
|
|
|
|
/* Convert the ptest firmware to a patch against the running firmware */
|
|
pfw = xbv_to_patch(card, unifi_fw_read, dlpriv, pfwinfo, &psize);
|
|
if (!pfw)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to convert f/w to patch");
|
|
return CSR_WIFI_HIP_RESULT_NO_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
void *desc;
|
|
sym.id = CSR_SLT_BOOT_LOADER_CONTROL;
|
|
sym.obj = 0; /* To be updated by _find_in_slut() */
|
|
|
|
/* Find boot loader control entry in SLUT */
|
|
r = _find_in_slut(card, &sym, &slut_base);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to find BOOT_LOADER_CONTROL\n");
|
|
kfree(pfw);
|
|
return CSR_RESULT_FAILURE;
|
|
}
|
|
|
|
r = unifi_set_host_state(card, UNIFI_HOST_STATE_AWAKE);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to wake UniFi\n");
|
|
}
|
|
|
|
/* Get a dlpriv for the patch buffer so that unifi_fw_read() can
|
|
* access it.
|
|
*/
|
|
desc = unifi_fw_open_buffer(card->ospriv, pfw, psize);
|
|
if (!desc)
|
|
{
|
|
kfree(pfw);
|
|
return CSR_WIFI_HIP_RESULT_NO_MEMORY;
|
|
}
|
|
|
|
/* Download the patch */
|
|
unifi_info(card->ospriv, "Downloading converted f/w as patch\n");
|
|
r = unifi_dl_patch(card, desc, sym.obj);
|
|
kfree(pfw);
|
|
unifi_fw_close_buffer(card->ospriv, desc);
|
|
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Converted patch download failed\n");
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
unifi_trace(card->ospriv, UDBG1, "Converted patch downloaded\n");
|
|
}
|
|
|
|
/* This command starts the firmware */
|
|
r = unifi_do_loader_op(card, sym.obj + 6, UNIFI_BOOT_LOADER_RESTART);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to write loader restart cmd\n");
|
|
}
|
|
|
|
func_exit_r(r);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* unifi_dl_firmware
|
|
*
|
|
* Download the given firmware image to the UniFi.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card struct
|
|
* dlpriv A context pointer from the calling function to be
|
|
* passed when calling unifi_fw_read().
|
|
*
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success,
|
|
* CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
|
|
* CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
|
|
* CSR_RESULT_FAILURE SDIO error
|
|
*
|
|
* Notes:
|
|
* Stops and resets the chip, does the download and runs the new
|
|
* firmware.
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
CsrResult unifi_dl_firmware(card_t *card, void *dlpriv)
|
|
{
|
|
xbv1_t *fwinfo;
|
|
CsrResult r;
|
|
|
|
func_enter();
|
|
|
|
fwinfo = kmalloc(sizeof(xbv1_t), GFP_KERNEL);
|
|
if (fwinfo == NULL)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to allocate memory for firmware\n");
|
|
return CSR_WIFI_HIP_RESULT_NO_MEMORY;
|
|
}
|
|
|
|
/*
|
|
* Scan the firmware file to find the TLVs we are interested in.
|
|
* These are:
|
|
* - check we support the file format version in VERF
|
|
* - SLTP Symbol Lookup Table Pointer
|
|
* - FWDL firmware download segments
|
|
* - FWOV firmware overlay segment
|
|
* - VMEQ Register probe tests to verify matching h/w
|
|
*/
|
|
r = xbv1_parse(card, unifi_fw_read, dlpriv, fwinfo);
|
|
if (r != CSR_RESULT_SUCCESS || fwinfo->mode != xbv_firmware)
|
|
{
|
|
unifi_error(card->ospriv, "File type is %s, expected firmware.\n",
|
|
fwinfo->mode == xbv_patch?"patch" : "unknown");
|
|
kfree(fwinfo);
|
|
return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
/* UF6xxx doesn't accept firmware, only patches. Therefore we convert
|
|
* the file to patch format with version numbers matching the current
|
|
* running firmware, and then download via the patch mechanism.
|
|
* The sole purpose of this is to support production test firmware across
|
|
* different ROM releases, the test firmware being provided in non-patch
|
|
* format.
|
|
*/
|
|
if (card->chip_id > SDIO_CARD_ID_UNIFI_2)
|
|
{
|
|
unifi_info(card->ospriv, "Must convert f/w to patch format\n");
|
|
r = do_patch_convert_download(card, dlpriv, fwinfo);
|
|
}
|
|
else
|
|
{
|
|
/* Older UniFi chips allowed firmware to be directly loaded onto the
|
|
* chip, which is no longer supported.
|
|
*/
|
|
unifi_error(card->ospriv, "Only patch downloading supported\n");
|
|
r = CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
kfree(fwinfo);
|
|
func_exit_r(r);
|
|
return r;
|
|
} /* unifi_dl_firmware() */
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* unifi_dl_patch
|
|
*
|
|
* Load the given patch set into UniFi.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card struct
|
|
* dlpriv The os specific handle to the firmware file.
|
|
* boot_ctrl The address of the boot loader control structure.
|
|
*
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success,
|
|
* CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
|
|
* CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
|
|
* CSR_RESULT_FAILURE SDIO error
|
|
*
|
|
* Notes:
|
|
* This ends up telling UniFi to restart.
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
CsrResult unifi_dl_patch(card_t *card, void *dlpriv, u32 boot_ctrl)
|
|
{
|
|
xbv1_t *fwinfo;
|
|
CsrResult r;
|
|
|
|
func_enter();
|
|
|
|
unifi_info(card->ospriv, "unifi_dl_patch %p %08x\n", dlpriv, boot_ctrl);
|
|
|
|
fwinfo = kmalloc(sizeof(xbv1_t), GFP_KERNEL);
|
|
if (fwinfo == NULL)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to allocate memory for patches\n");
|
|
func_exit();
|
|
return CSR_WIFI_HIP_RESULT_NO_MEMORY;
|
|
}
|
|
|
|
/*
|
|
* Scan the firmware file to find the TLVs we are interested in.
|
|
* These are:
|
|
* - check we support the file format version in VERF
|
|
* - FWID The build ID of the ROM that we can patch
|
|
* - PTDL patch download segments
|
|
*/
|
|
r = xbv1_parse(card, unifi_fw_read, dlpriv, fwinfo);
|
|
if (r != CSR_RESULT_SUCCESS || fwinfo->mode != xbv_patch)
|
|
{
|
|
kfree(fwinfo);
|
|
unifi_error(card->ospriv, "Failed to read in patch file\n");
|
|
func_exit();
|
|
return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
/*
|
|
* We have to check the build id read from the SLUT against that
|
|
* for the patch file. They have to match exactly.
|
|
* "card->build_id" == XBV1.PTCH.FWID
|
|
*/
|
|
if (card->build_id != fwinfo->build_id)
|
|
{
|
|
unifi_error(card->ospriv, "Wrong patch file for chip (chip = %lu, file = %lu)\n",
|
|
card->build_id, fwinfo->build_id);
|
|
kfree(fwinfo);
|
|
#ifndef CSR_WIFI_IGNORE_PATCH_VERSION_MISMATCH
|
|
func_exit();
|
|
return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
#else
|
|
fwinfo = NULL;
|
|
dlpriv = NULL;
|
|
return CSR_RESULT_SUCCESS;
|
|
#endif
|
|
}
|
|
|
|
r = do_patch_download(card, dlpriv, fwinfo, boot_ctrl);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to patch image\n");
|
|
}
|
|
|
|
kfree(fwinfo);
|
|
|
|
func_exit_r(r);
|
|
return r;
|
|
} /* unifi_dl_patch() */
|
|
|
|
|
|
void* unifi_dl_fw_read_start(card_t *card, s8 is_fw)
|
|
{
|
|
card_info_t card_info;
|
|
|
|
unifi_card_info(card, &card_info);
|
|
unifi_trace(card->ospriv, UDBG5,
|
|
"id=%d, ver=0x%x, fw_build=%u, fw_hip=0x%x, block_size=%d\n",
|
|
card_info.chip_id, card_info.chip_version,
|
|
card_info.fw_build, card_info.fw_hip_version,
|
|
card_info.sdio_block_size);
|
|
|
|
return unifi_fw_read_start(card->ospriv, is_fw, &card_info);
|
|
}
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* safe_read_shared_location
|
|
*
|
|
* Read a shared memory location repeatedly until we get two readings
|
|
* the same.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card context struct.
|
|
* unifi_addr UniFi shared-data-memory address to access.
|
|
* pdata Pointer to a byte variable for the value read.
|
|
*
|
|
*
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success, CSR error code on failure
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
static CsrResult safe_read_shared_location(card_t *card, u32 address, u8 *pdata)
|
|
{
|
|
CsrResult r;
|
|
u16 limit = 1000;
|
|
u8 b, b2;
|
|
|
|
*pdata = 0;
|
|
|
|
r = unifi_read_8_or_16(card, address, &b);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
return r;
|
|
}
|
|
|
|
while (limit--)
|
|
{
|
|
r = unifi_read_8_or_16(card, address, &b2);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
return r;
|
|
}
|
|
|
|
/* When we have a stable value, return it */
|
|
if (b == b2)
|
|
{
|
|
*pdata = b;
|
|
return CSR_RESULT_SUCCESS;
|
|
}
|
|
|
|
b = b2;
|
|
}
|
|
|
|
return CSR_RESULT_FAILURE;
|
|
} /* safe_read_shared_location() */
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* unifi_do_loader_op
|
|
*
|
|
* Send a loader / boot_loader command to the UniFi and wait for
|
|
* it to complete.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card context struct.
|
|
* op_addr The address of the loader operation control word.
|
|
* opcode The operation to perform.
|
|
*
|
|
* Returns:
|
|
* CSR_RESULT_SUCCESS on success
|
|
* CSR_RESULT_FAILURE SDIO error or SDIO/XAP timeout
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* Ideally instead of sleeping, we want to busy wait.
|
|
* Currently there is no framework API to do this. When it becomes available,
|
|
* we can use it to busy wait using usecs
|
|
*/
|
|
#define OPERATION_TIMEOUT_LOOPS (100) /* when OPERATION_TIMEOUT_DELAY==1, (500) otherwise */
|
|
#define OPERATION_TIMEOUT_DELAY 1 /* msec, or 200usecs */
|
|
|
|
CsrResult unifi_do_loader_op(card_t *card, u32 op_addr, u8 opcode)
|
|
{
|
|
CsrResult r;
|
|
s16 op_retries;
|
|
|
|
unifi_trace(card->ospriv, UDBG4, "Loader cmd 0x%0x -> 0x%08x\n", opcode, op_addr);
|
|
|
|
/* Set the Operation command byte to the opcode */
|
|
r = unifi_write_8_or_16(card, op_addr, opcode);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to write loader copy command\n");
|
|
return r;
|
|
}
|
|
|
|
/* Wait for Operation command byte to be Idle */
|
|
/* Typically takes ~100us */
|
|
op_retries = 0;
|
|
r = CSR_RESULT_SUCCESS;
|
|
while (1)
|
|
{
|
|
u8 op;
|
|
|
|
/*
|
|
* Read the memory location until two successive reads give
|
|
* the same value.
|
|
* Then handle it.
|
|
*/
|
|
r = safe_read_shared_location(card, op_addr, &op);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to read loader status\n");
|
|
break;
|
|
}
|
|
|
|
if (op == UNIFI_LOADER_IDLE)
|
|
{
|
|
/* Success */
|
|
break;
|
|
}
|
|
|
|
if (op != opcode)
|
|
{
|
|
unifi_error(card->ospriv, "Error reported by loader: 0x%X\n", op);
|
|
r = CSR_RESULT_FAILURE;
|
|
break;
|
|
}
|
|
|
|
/* Allow 500us timeout */
|
|
if (++op_retries >= OPERATION_TIMEOUT_LOOPS)
|
|
{
|
|
unifi_error(card->ospriv, "Timeout waiting for loader to ack transfer\n");
|
|
/* Stop XAPs to aid post-mortem */
|
|
r = unifi_card_stop_processor(card, UNIFI_PROC_BOTH);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to stop UniFi processors\n");
|
|
}
|
|
else
|
|
{
|
|
r = CSR_RESULT_FAILURE;
|
|
}
|
|
break;
|
|
}
|
|
CsrThreadSleep(OPERATION_TIMEOUT_DELAY);
|
|
} /* Loop exits with r != CSR_RESULT_SUCCESS on error */
|
|
|
|
return r;
|
|
} /* unifi_do_loader_op() */
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* send_ptdl_to_unifi
|
|
*
|
|
* Copy a patch block from userland to the UniFi.
|
|
* This function reads data, 2K at a time, from userland and writes
|
|
* it to the UniFi.
|
|
*
|
|
* Arguments:
|
|
* card A pointer to the card structure
|
|
* dlpriv The os specific handle for the firmware file
|
|
* ptdl A pointer ot the PTDL block
|
|
* handle The buffer handle to use for the xfer
|
|
* op_addr The address of the loader operation control word
|
|
*
|
|
* Returns:
|
|
* Number of bytes sent (Positive) or negative value indicating
|
|
* error code:
|
|
* CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
|
|
* CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
|
|
* CSR_RESULT_FAILURE SDIO error
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
static CsrResult send_ptdl_to_unifi(card_t *card, void *dlpriv,
|
|
const struct PTDL *ptdl, u32 handle,
|
|
u32 op_addr)
|
|
{
|
|
u32 offset;
|
|
u8 *buf;
|
|
s32 data_len;
|
|
u32 write_len;
|
|
CsrResult r;
|
|
const u16 buf_size = 2 * 1024;
|
|
|
|
offset = ptdl->dl_offset;
|
|
data_len = ptdl->dl_size;
|
|
|
|
if (data_len > buf_size)
|
|
{
|
|
unifi_error(card->ospriv, "PTDL block is too large (%u)\n",
|
|
ptdl->dl_size);
|
|
return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
buf = kmalloc(buf_size, GFP_KERNEL);
|
|
if (buf == NULL)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to allocate transfer buffer for firmware download\n");
|
|
return CSR_WIFI_HIP_RESULT_NO_MEMORY;
|
|
}
|
|
|
|
r = CSR_RESULT_SUCCESS;
|
|
|
|
if (unifi_fw_read(card->ospriv, dlpriv, offset, buf, data_len) != data_len)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to read from file\n");
|
|
}
|
|
else
|
|
{
|
|
/* We can always round these if the host wants to */
|
|
if (card->sdio_io_block_pad)
|
|
{
|
|
write_len = (data_len + (card->sdio_io_block_size - 1)) &
|
|
~(card->sdio_io_block_size - 1);
|
|
|
|
/* Zero out the rest of the buffer (This isn't needed, but it
|
|
* makes debugging things later much easier). */
|
|
memset(buf + data_len, 0, write_len - data_len);
|
|
}
|
|
else
|
|
{
|
|
write_len = data_len;
|
|
}
|
|
|
|
r = unifi_bulk_rw_noretry(card, handle, buf, write_len, UNIFI_SDIO_WRITE);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "CMD53 failed writing %d bytes to handle %ld\n",
|
|
data_len, handle);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Can change the order of things to overlap read from file
|
|
* with copy to unifi
|
|
*/
|
|
r = unifi_do_loader_op(card, op_addr, UNIFI_BOOT_LOADER_PATCH);
|
|
}
|
|
}
|
|
|
|
kfree(buf);
|
|
|
|
if (r != CSR_RESULT_SUCCESS && r != CSR_WIFI_HIP_RESULT_NO_DEVICE)
|
|
{
|
|
unifi_error(card->ospriv, "Failed to copy block of %u bytes to UniFi\n",
|
|
ptdl->dl_size);
|
|
}
|
|
|
|
return r;
|
|
} /* send_ptdl_to_unifi() */
|
|
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* do_patch_download
|
|
*
|
|
* This function downloads a set of patches to UniFi and then
|
|
* causes it to restart.
|
|
*
|
|
* Arguments:
|
|
* card Pointer to card struct.
|
|
* dlpriv A context pointer from the calling function to be
|
|
* used when reading the XBV file. This can be NULL
|
|
* in which case not patches are applied.
|
|
* pfwinfo Pointer to a fwinfo struct describing the f/w
|
|
* XBV file.
|
|
* boot_ctrl_addr The address of the boot loader control structure.
|
|
*
|
|
* Returns:
|
|
* 0 on success, or an error code
|
|
* CSR_WIFI_HIP_RESULT_INVALID_VALUE for a bad laoader version number
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
static CsrResult do_patch_download(card_t *card, void *dlpriv, xbv1_t *pfwinfo, u32 boot_ctrl_addr)
|
|
{
|
|
CsrResult r;
|
|
s32 i;
|
|
u16 loader_version;
|
|
u16 handle;
|
|
u32 total_bytes;
|
|
|
|
/*
|
|
* Read info from the SDIO Loader Control Data Structure
|
|
*/
|
|
/* Check the loader version */
|
|
r = unifi_card_read16(card, boot_ctrl_addr, &loader_version);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Patch download: Failed to read loader version\n");
|
|
return r;
|
|
}
|
|
unifi_trace(card->ospriv, UDBG2, "Patch download: boot loader version 0x%04X\n", loader_version);
|
|
switch (loader_version)
|
|
{
|
|
case 0x0000:
|
|
break;
|
|
|
|
default:
|
|
unifi_error(card->ospriv, "Patch loader version (0x%04X) is not supported by this driver\n",
|
|
loader_version);
|
|
return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
|
|
}
|
|
|
|
/* Retrieve the handle to use with CMD53 */
|
|
r = unifi_card_read16(card, boot_ctrl_addr + 4, &handle);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Patch download: Failed to read loader handle\n");
|
|
return r;
|
|
}
|
|
|
|
/* Set the mask of LEDs to flash */
|
|
if (card->loader_led_mask)
|
|
{
|
|
r = unifi_card_write16(card, boot_ctrl_addr + 2,
|
|
(u16)card->loader_led_mask);
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Patch download: Failed to write LED mask\n");
|
|
return r;
|
|
}
|
|
}
|
|
|
|
total_bytes = 0;
|
|
|
|
/* Copy download data to UniFi memory */
|
|
for (i = 0; i < pfwinfo->num_ptdl; i++)
|
|
{
|
|
unifi_trace(card->ospriv, UDBG3, "Patch download: %d Downloading for %d from offset %d\n",
|
|
i,
|
|
pfwinfo->ptdl[i].dl_size,
|
|
pfwinfo->ptdl[i].dl_offset);
|
|
|
|
r = send_ptdl_to_unifi(card, dlpriv, &pfwinfo->ptdl[i],
|
|
handle, boot_ctrl_addr + 6);
|
|
if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
|
|
{
|
|
return r;
|
|
}
|
|
if (r != CSR_RESULT_SUCCESS)
|
|
{
|
|
unifi_error(card->ospriv, "Patch failed after %u bytes\n",
|
|
total_bytes);
|
|
return r;
|
|
}
|
|
total_bytes += pfwinfo->ptdl[i].dl_size;
|
|
}
|
|
|
|
return CSR_RESULT_SUCCESS;
|
|
} /* do_patch_download() */
|
|
|
|
|