blob: d14e11839618e92cead591160100aca3dc1e6b4d [file] [log] [blame]
/*
* ---------------------------------------------------------------------------
* 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;
}