412 lines
12 KiB
C
412 lines
12 KiB
C
/*
|
|
* ---------------------------------------------------------------------------
|
|
* 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) {
|
|
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;
|
|
}
|