| /***************************************************************************** |
| |
| (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() */ |
| |
| |