| /* | |
| * --------------------------------------------------------------------------- | |
| * FILE: firmware.c | |
| * | |
| * PURPOSE: | |
| * Implements the f/w related HIP core lib API. | |
| * It is part of the porting exercise in Linux. | |
| * | |
| * Also, it contains example code for reading the loader and f/w files | |
| * from the userspace and starting the SME in Linux. | |
| * | |
| * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd. | |
| * | |
| * Refer to LICENSE.txt included with this source code for details on | |
| * the license terms. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| #include <linux/kmod.h> | |
| #include <linux/vmalloc.h> | |
| #include <linux/firmware.h> | |
| #include <asm/uaccess.h> | |
| #include "csr_wifi_hip_unifi.h" | |
| #include "csr_wifi_hip_unifi_udi.h" | |
| #include "unifiio.h" | |
| #include "unifi_priv.h" | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * | |
| * F/W download. Part of the HIP core API | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_fw_read_start | |
| * | |
| * Returns a structure to be passed in unifi_fw_read(). | |
| * This structure is an OS specific description of the f/w file. | |
| * In the linux implementation it is a buffer with the f/w and its' length. | |
| * The HIP driver calls this functions to request for the loader or | |
| * the firmware file. | |
| * The structure pointer can be freed when unifi_fw_read_stop() is called. | |
| * | |
| * Arguments: | |
| * ospriv Pointer to driver context. | |
| * is_fw Type of firmware to retrieve | |
| * info Versions information. Can be used to determine | |
| * the appropriate f/w file to load. | |
| * | |
| * Returns: | |
| * O on success, non-zero otherwise. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void* | |
| unifi_fw_read_start(void *ospriv, s8 is_fw, const card_info_t *info) | |
| { | |
| unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
| CSR_UNUSED(info); | |
| if (is_fw == UNIFI_FW_STA) { | |
| /* F/w may have been released after a previous successful download. */ | |
| if (priv->fw_sta.dl_data == NULL) { | |
| unifi_trace(priv, UDBG2, "Attempt reload of sta f/w\n"); | |
| uf_request_firmware_files(priv, UNIFI_FW_STA); | |
| } | |
| /* Set up callback struct for readfunc() */ | |
| if (priv->fw_sta.dl_data != NULL) { | |
| return &priv->fw_sta; | |
| } | |
| } else { | |
| unifi_error(priv, "downloading firmware... unknown request: %d\n", is_fw); | |
| } | |
| return NULL; | |
| } /* unifi_fw_read_start() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_fw_read_stop | |
| * | |
| * Called when the HIP driver has finished using the loader or | |
| * the firmware file. | |
| * The firmware buffer may be released now. | |
| * | |
| * Arguments: | |
| * ospriv Pointer to driver context. | |
| * dlpriv The pointer returned by unifi_fw_read_start() | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void | |
| unifi_fw_read_stop(void *ospriv, void *dlpriv) | |
| { | |
| unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
| struct dlpriv *dl_struct = (struct dlpriv *)dlpriv; | |
| if (dl_struct != NULL) { | |
| if (dl_struct->dl_data != NULL) { | |
| unifi_trace(priv, UDBG2, "Release f/w buffer %p, %d bytes\n", | |
| dl_struct->dl_data, dl_struct->dl_len); | |
| } | |
| uf_release_firmware(priv, dl_struct); | |
| } | |
| } /* unifi_fw_read_stop() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_fw_open_buffer | |
| * | |
| * Returns a handle for a buffer dynamically allocated by the driver, | |
| * e.g. into which a firmware file may have been converted from another format | |
| * which is the case with some production test images. | |
| * | |
| * The handle may then be used by unifi_fw_read() to access the contents of | |
| * the buffer. | |
| * | |
| * Arguments: | |
| * ospriv Pointer to driver context. | |
| * fwbuf Buffer containing firmware image | |
| * len Length of buffer in bytes | |
| * | |
| * Returns | |
| * Handle for buffer, or NULL on error | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void * | |
| unifi_fw_open_buffer(void *ospriv, void *fwbuf, u32 len) | |
| { | |
| unifi_priv_t *priv = (unifi_priv_t*)ospriv; | |
| if (fwbuf == NULL) { | |
| return NULL; | |
| } | |
| priv->fw_conv.dl_data = fwbuf; | |
| priv->fw_conv.dl_len = len; | |
| priv->fw_conv.fw_desc = NULL; /* No OS f/w resource is associated */ | |
| return &priv->fw_conv; | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_fw_close_buffer | |
| * | |
| * Releases any handle for a buffer dynamically allocated by the driver, | |
| * e.g. into which a firmware file may have been converted from another format | |
| * which is the case with some production test images. | |
| * | |
| * | |
| * Arguments: | |
| * ospriv Pointer to driver context. | |
| * fwbuf Buffer containing firmware image | |
| * | |
| * Returns | |
| * Handle for buffer, or NULL on error | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void unifi_fw_close_buffer(void *ospriv, void *fwbuf) | |
| { | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_fw_read | |
| * | |
| * The HIP driver calls this function to ask for a part of the loader or | |
| * the firmware file. | |
| * | |
| * Arguments: | |
| * ospriv Pointer to driver context. | |
| * arg The pointer returned by unifi_fw_read_start(). | |
| * offset The offset in the file to return from. | |
| * buf A buffer to store the requested data. | |
| * len The size of the buf and the size of the requested data. | |
| * | |
| * Returns | |
| * The number of bytes read from the firmware image, or -ve on error | |
| * --------------------------------------------------------------------------- | |
| */ | |
| s32 | |
| unifi_fw_read(void *ospriv, void *arg, u32 offset, void *buf, u32 len) | |
| { | |
| const struct dlpriv *dlpriv = arg; | |
| if (offset >= dlpriv->dl_len) { | |
| /* at end of file */ | |
| return 0; | |
| } | |
| if ((offset + len) > dlpriv->dl_len) { | |
| /* attempt to read past end of file */ | |
| return -1; | |
| } | |
| memcpy(buf, dlpriv->dl_data+offset, len); | |
| return len; | |
| } /* unifi_fw_read() */ | |
| #define UNIFIHELPER_INIT_MODE_SMEUSER 2 | |
| #define UNIFIHELPER_INIT_MODE_NATIVE 1 | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_run_unifihelper | |
| * | |
| * Ask userspace to send us firmware for download by running | |
| * '/usr/sbin/unififw'. | |
| * The same script starts the SME userspace application. | |
| * Derived from net_run_sbin_hotplug(). | |
| * | |
| * Arguments: | |
| * priv Pointer to OS private struct. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| uf_run_unifihelper(unifi_priv_t *priv) | |
| { | |
| #ifdef ANDROID_BUILD | |
| char *prog = "/system/bin/unififw"; | |
| #else | |
| char *prog = "/usr/sbin/unififw"; | |
| #endif /* ANDROID_BUILD */ | |
| char *argv[6], *envp[4]; | |
| char inst_str[8]; | |
| char init_mode[8]; | |
| int i, r; | |
| #if (defined CSR_SME_USERSPACE) && (!defined CSR_SUPPORT_WEXT) | |
| unifi_trace(priv, UDBG1, "SME userspace build: run unifi_helper manually\n"); | |
| return 0; | |
| #endif | |
| unifi_trace(priv, UDBG1, "starting %s\n", prog); | |
| snprintf(inst_str, 8, "%d", priv->instance); | |
| #if (defined CSR_SME_USERSPACE) | |
| snprintf(init_mode, 8, "%d", UNIFIHELPER_INIT_MODE_SMEUSER); | |
| #else | |
| snprintf(init_mode, 8, "%d", UNIFIHELPER_INIT_MODE_NATIVE); | |
| #endif /* CSR_SME_USERSPACE */ | |
| i = 0; | |
| argv[i++] = prog; | |
| argv[i++] = inst_str; | |
| argv[i++] = init_mode; | |
| argv[i++] = 0; | |
| argv[i] = 0; | |
| /* Don't add more args without making argv bigger */ | |
| /* minimal command environment */ | |
| i = 0; | |
| envp[i++] = "HOME=/"; | |
| envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; | |
| envp[i] = 0; | |
| /* Don't add more without making envp bigger */ | |
| unifi_trace(priv, UDBG2, "running %s %s %s\n", argv[0], argv[1], argv[2]); | |
| r = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); | |
| return r; | |
| } /* uf_run_unifihelper() */ | |
| #ifdef CSR_WIFI_SPLIT_PATCH | |
| static u8 is_ap_mode(unifi_priv_t *priv) | |
| { | |
| if (priv == NULL || priv->interfacePriv[0] == NULL) | |
| { | |
| return FALSE; | |
| } | |
| /* Test for mode requiring AP patch */ | |
| return(CSR_WIFI_HIP_IS_AP_FW(priv->interfacePriv[0]->interfaceMode)); | |
| } | |
| #endif | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_request_firmware_files | |
| * | |
| * Get the firmware files from userspace. | |
| * | |
| * Arguments: | |
| * priv Pointer to OS private struct. | |
| * is_fw type of firmware to load (UNIFI_FW_STA/LOADER) | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int uf_request_firmware_files(unifi_priv_t *priv, int is_fw) | |
| { | |
| /* uses the default method to get the firmware */ | |
| const struct firmware *fw_entry; | |
| int postfix; | |
| #define UNIFI_MAX_FW_PATH_LEN 32 | |
| char fw_name[UNIFI_MAX_FW_PATH_LEN]; | |
| int r; | |
| #if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT) | |
| if (priv->mib_data.length) { | |
| vfree(priv->mib_data.data); | |
| priv->mib_data.data = NULL; | |
| priv->mib_data.length = 0; | |
| } | |
| #endif /* CSR_SUPPORT_SME && CSR_SUPPORT_WEXT*/ | |
| postfix = priv->instance; | |
| if (is_fw == UNIFI_FW_STA) { | |
| /* Free kernel buffer and reload */ | |
| uf_release_firmware(priv, &priv->fw_sta); | |
| #ifdef CSR_WIFI_SPLIT_PATCH | |
| scnprintf(fw_name, UNIFI_MAX_FW_PATH_LEN, "unifi-sdio-%d/%s", | |
| postfix, (is_ap_mode(priv) ? "ap.xbv" : "staonly.xbv") ); | |
| #else | |
| scnprintf(fw_name, UNIFI_MAX_FW_PATH_LEN, "unifi-sdio-%d/%s", | |
| postfix, "sta.xbv" ); | |
| #endif | |
| r = request_firmware(&fw_entry, fw_name, priv->unifi_device); | |
| if (r == 0) { | |
| priv->fw_sta.dl_data = fw_entry->data; | |
| priv->fw_sta.dl_len = fw_entry->size; | |
| priv->fw_sta.fw_desc = (void *)fw_entry; | |
| } else { | |
| unifi_trace(priv, UDBG2, "Firmware file not available\n"); | |
| } | |
| } | |
| return 0; | |
| } /* uf_request_firmware_files() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_release_firmware_files | |
| * | |
| * Release all buffers used to store firmware files | |
| * | |
| * Arguments: | |
| * priv Pointer to OS private struct. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int uf_release_firmware_files(unifi_priv_t *priv) | |
| { | |
| uf_release_firmware(priv, &priv->fw_sta); | |
| return 0; | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_release_firmware | |
| * | |
| * Release specific buffer used to store firmware | |
| * | |
| * Arguments: | |
| * priv Pointer to OS private struct. | |
| * to_free Pointer to specific buffer to release | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int uf_release_firmware(unifi_priv_t *priv, struct dlpriv *to_free) | |
| { | |
| if (to_free != NULL) { | |
| release_firmware((const struct firmware *)to_free->fw_desc); | |
| to_free->fw_desc = NULL; | |
| to_free->dl_data = NULL; | |
| to_free->dl_len = 0; | |
| } | |
| return 0; | |
| } |