summaryrefslogtreecommitdiffstats
path: root/drivers/staging/csr/firmware.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/csr/firmware.c')
-rw-r--r--drivers/staging/csr/firmware.c413
1 files changed, 413 insertions, 0 deletions
diff --git a/drivers/staging/csr/firmware.c b/drivers/staging/csr/firmware.c
new file mode 100644
index 000000000000..d14e11839618
--- /dev/null
+++ b/drivers/staging/csr/firmware.c
@@ -0,0 +1,413 @@
+/*
+ * ---------------------------------------------------------------------------
+ * 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);
+
+ func_enter();
+
+ 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) {
+ func_exit();
+ return &priv->fw_sta;
+ }
+
+ } else {
+ unifi_error(priv, "downloading firmware... unknown request: %d\n", is_fw);
+ }
+
+ func_exit();
+ 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;
+ func_enter();
+
+ 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);
+ }
+
+ func_exit();
+} /* 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;
+ func_enter();
+
+ if (fwbuf == NULL) {
+ func_exit();
+ 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 */
+
+ func_exit();
+ 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 CONFIG_HOTPLUG
+
+#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, 0);
+
+ return r;
+#else
+ unifi_trace(priv, UDBG1, "Can't automatically download firmware because kernel does not have HOTPLUG\n");
+ return -1;
+#endif
+} /* 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) {
+ if (to_free->fw_desc != 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;
+}