Kernel  |  3.10

下载     查看原文件
C++程序  |  820行  |  24.56 KB
/*****************************************************************************

            (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");
            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);
            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");
        return r;
    }

    if (finger_print != SLUT_FINGERPRINT)
    {
        unifi_error(card->ospriv, "Failed to find SLUT fingerprint\n");
        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)
        {
            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)
        {
            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;
        }
    }

    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");
            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");
        }

        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;

    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);
    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;

    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");
        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");
        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
        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);

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