/* * --------------------------------------------------------------------------- * 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; }