summaryrefslogtreecommitdiff
path: root/drivers/staging/csr/drv.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
commitb13bc8dda81c54a66a1c84e66f60b8feba659f28 (patch)
tree100a26eada424fa5d9b0e5eaaf4e23b8fa036fc8 /drivers/staging/csr/drv.c
parent9fc377799bc9bfd8d5cb35d0d1ea2e2458cbdbb3 (diff)
parent419e9266884fa853179ab726c27a63a9d3ae46e3 (diff)
downloadlinux-fsl-qoriq-b13bc8dda81c54a66a1c84e66f60b8feba659f28.tar.xz
Merge tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging tree patches from Greg Kroah-Hartman: "Here's the big staging tree merge for the 3.6-rc1 merge window. There are some patches in here outside of drivers/staging/, notibly the iio code (which is still stradeling the staging / not staging boundry), the pstore code, and the tracing code. All of these have gotten acks from the various subsystem maintainers to be included in this tree. The pstore and tracing patches are related, and are coming here as they replace one of the android staging drivers. Otherwise, the normal staging mess. Lots of cleanups and a few new drivers (some iio drivers, and the large csr wireless driver abomination.) Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" Fixed up trivial conflicts in drivers/staging/comedi/drivers/s626.h and drivers/staging/gdm72xx/netlink_k.c * tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (1108 commits) staging: csr: delete a bunch of unused library functions staging: csr: remove csr_utf16.c staging: csr: remove csr_pmem.h staging: csr: remove CsrPmemAlloc staging: csr: remove CsrPmemFree() staging: csr: remove CsrMemAllocDma() staging: csr: remove CsrMemCalloc() staging: csr: remove CsrMemAlloc() staging: csr: remove CsrMemFree() and CsrMemFreeDma() staging: csr: remove csr_util.h staging: csr: remove CsrOffSetOf() stating: csr: remove unneeded #includes in csr_util.c staging: csr: make CsrUInt16ToHex static staging: csr: remove CsrMemCpy() staging: csr: remove CsrStrLen() staging: csr: remove CsrVsnprintf() staging: csr: remove CsrStrDup staging: csr: remove CsrStrChr() staging: csr: remove CsrStrNCmp staging: csr: remove CsrStrCmp ...
Diffstat (limited to 'drivers/staging/csr/drv.c')
-rw-r--r--drivers/staging/csr/drv.c2262
1 files changed, 2262 insertions, 0 deletions
diff --git a/drivers/staging/csr/drv.c b/drivers/staging/csr/drv.c
new file mode 100644
index 0000000..b2c27f4
--- /dev/null
+++ b/drivers/staging/csr/drv.c
@@ -0,0 +1,2262 @@
+/*
+ * ---------------------------------------------------------------------------
+ * FILE: drv.c
+ *
+ * PURPOSE:
+ * Conventional device interface for debugging/monitoring of the
+ * driver and h/w using unicli. This interface is also being used
+ * by the SME linux implementation and the helper apps.
+ *
+ * Copyright (C) 2005-2009 by Cambridge Silicon Radio Ltd.
+ *
+ * Refer to LICENSE.txt included with this source code for details on
+ * the license terms.
+ *
+ * ---------------------------------------------------------------------------
+ */
+
+
+
+/*
+ * Porting Notes:
+ * Part of this file contains an example for how to glue the OS layer
+ * with the HIP core lib, the SDIO glue layer, and the SME.
+ *
+ * When the unifi_sdio.ko modules loads, the linux kernel calls unifi_load().
+ * unifi_load() calls uf_sdio_load() which is exported by the SDIO glue
+ * layer. uf_sdio_load() registers this driver with the underlying SDIO driver.
+ * When a card is detected, the SDIO glue layer calls register_unifi_sdio()
+ * to pass the SDIO function context and ask the OS layer to initialise
+ * the card. register_unifi_sdio() allocates all the private data of the OS
+ * layer and calls uf_run_unifihelper() to start the SME. The SME calls
+ * unifi_sys_wifi_on_req() which uses the HIP core lib to initialise the card.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <asm/uaccess.h>
+#include <linux/jiffies.h>
+
+#include "csr_wifi_hip_unifiversion.h"
+#include "unifi_priv.h"
+#include "csr_wifi_hip_conversions.h"
+#include "unifi_native.h"
+
+/* Module parameter variables */
+int buswidth = 0; /* 0 means use default, values 1,4 */
+int sdio_clock = 50000; /* kHz */
+int unifi_debug = 0;
+/* fw_init prevents f/w initialisation on error. */
+int fw_init[MAX_UNIFI_DEVS] = {-1, -1};
+int use_5g = 0;
+int led_mask = 0; /* 0x0c00 for dev-pc-1503c, dev-pc-1528a */
+int disable_hw_reset = 0;
+int disable_power_control = 0;
+int enable_wol = UNIFI_WOL_OFF; /* 0 for none, 1 for SDIO IRQ, 2 for PIO */
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+int tl_80211d = (int)CSR_WIFI_SME_80211D_TRUST_LEVEL_MIB;
+#endif
+int sdio_block_size = -1; /* Override SDIO block size */
+int sdio_byte_mode = 0; /* 0 for block mode + padding, 1 for byte mode */
+int coredump_max = CSR_WIFI_HIP_NUM_COREDUMP_BUFFERS;
+int run_bh_once = -1; /* Set for scheduled interrupt mode, -1 = default */
+int bh_priority = -1;
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+#define UNIFI_LOG_HIP_SIGNALS_FILTER_BULKDATA (1 << 1)
+#define UNIFI_LOG_HIP_SIGNALS_FILTER_TIMESTAMP (1 << 2)
+int log_hip_signals = 0;
+#endif
+
+MODULE_DESCRIPTION("CSR UniFi (SDIO)");
+
+module_param(buswidth, int, S_IRUGO|S_IWUSR);
+module_param(sdio_clock, int, S_IRUGO|S_IWUSR);
+module_param(unifi_debug, int, S_IRUGO|S_IWUSR);
+module_param_array(fw_init, int, NULL, S_IRUGO|S_IWUSR);
+module_param(use_5g, int, S_IRUGO|S_IWUSR);
+module_param(led_mask, int, S_IRUGO|S_IWUSR);
+module_param(disable_hw_reset, int, S_IRUGO|S_IWUSR);
+module_param(disable_power_control, int, S_IRUGO|S_IWUSR);
+module_param(enable_wol, int, S_IRUGO|S_IWUSR);
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+module_param(tl_80211d, int, S_IRUGO|S_IWUSR);
+#endif
+module_param(sdio_block_size, int, S_IRUGO|S_IWUSR);
+module_param(sdio_byte_mode, int, S_IRUGO|S_IWUSR);
+module_param(coredump_max, int, S_IRUGO|S_IWUSR);
+module_param(run_bh_once, int, S_IRUGO|S_IWUSR);
+module_param(bh_priority, int, S_IRUGO|S_IWUSR);
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+module_param(log_hip_signals, int, S_IRUGO|S_IWUSR);
+#endif
+
+MODULE_PARM_DESC(buswidth, "SDIO bus width (0=default), set 1 for 1-bit or 4 for 4-bit mode");
+MODULE_PARM_DESC(sdio_clock, "SDIO bus frequency in kHz, (default = 50 MHz)");
+MODULE_PARM_DESC(unifi_debug, "Diagnostic reporting level");
+MODULE_PARM_DESC(fw_init, "Set to 0 to prevent f/w initialization on error");
+MODULE_PARM_DESC(use_5g, "Use the 5G (802.11a) radio band");
+MODULE_PARM_DESC(led_mask, "LED mask flags");
+MODULE_PARM_DESC(disable_hw_reset, "Set to 1 to disable hardware reset");
+MODULE_PARM_DESC(disable_power_control, "Set to 1 to disable SDIO power control");
+MODULE_PARM_DESC(enable_wol, "Enable wake-on-wlan function 0=off, 1=SDIO, 2=PIO");
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+MODULE_PARM_DESC(tl_80211d, "802.11d Trust Level (1-6, default = 5)");
+#endif
+MODULE_PARM_DESC(sdio_block_size, "Set to override SDIO block size");
+MODULE_PARM_DESC(sdio_byte_mode, "Set to 1 for byte mode SDIO");
+MODULE_PARM_DESC(coredump_max, "Number of chip mini-coredump buffers to allocate");
+MODULE_PARM_DESC(run_bh_once, "Run BH only when firmware interrupts");
+MODULE_PARM_DESC(bh_priority, "Modify the BH thread priority");
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+MODULE_PARM_DESC(log_hip_signals, "Set to 1 to enable HIP signal offline logging");
+#endif
+
+
+/* Callback for event logging to UDI clients */
+static void udi_log_event(ul_client_t *client,
+ const u8 *signal, int signal_len,
+ const bulk_data_param_t *bulkdata,
+ int dir);
+
+static void udi_set_log_filter(ul_client_t *pcli,
+ unifiio_filter_t *udi_filter);
+
+
+/* Mutex to protect access to priv->sme_cli */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
+DEFINE_SEMAPHORE(udi_mutex);
+#else
+DECLARE_MUTEX(udi_mutex);
+#endif
+
+s32 CsrHipResultToStatus(CsrResult csrResult)
+{
+ s32 r = -EIO;
+
+ switch (csrResult)
+ {
+ case CSR_RESULT_SUCCESS:
+ r = 0;
+ break;
+ case CSR_WIFI_HIP_RESULT_RANGE:
+ r = -ERANGE;
+ break;
+ case CSR_WIFI_HIP_RESULT_NO_DEVICE:
+ r = -ENODEV;
+ break;
+ case CSR_WIFI_HIP_RESULT_INVALID_VALUE:
+ r = -EINVAL;
+ break;
+ case CSR_WIFI_HIP_RESULT_NOT_FOUND:
+ r = -ENOENT;
+ break;
+ case CSR_WIFI_HIP_RESULT_NO_SPACE:
+ r = -ENOSPC;
+ break;
+ case CSR_WIFI_HIP_RESULT_NO_MEMORY:
+ r = -ENOMEM;
+ break;
+ case CSR_RESULT_FAILURE:
+ r = -EIO;
+ break;
+ default:
+ /*unifi_warning(card->ospriv, "CsrHipResultToStatus: Unrecognised csrResult error code: %d\n", csrResult);*/
+ r = -EIO;
+ }
+ return r;
+}
+
+
+static const char*
+trace_putest_cmdid(unifi_putest_command_t putest_cmd)
+{
+ switch (putest_cmd)
+ {
+ case UNIFI_PUTEST_START:
+ return "START";
+ case UNIFI_PUTEST_STOP:
+ return "STOP";
+ case UNIFI_PUTEST_SET_SDIO_CLOCK:
+ return "SET CLOCK";
+ case UNIFI_PUTEST_CMD52_READ:
+ return "CMD52R";
+ case UNIFI_PUTEST_CMD52_BLOCK_READ:
+ return "CMD52BR";
+ case UNIFI_PUTEST_CMD52_WRITE:
+ return "CMD52W";
+ case UNIFI_PUTEST_DL_FW:
+ return "D/L FW";
+ case UNIFI_PUTEST_DL_FW_BUFF:
+ return "D/L FW BUFFER";
+ case UNIFI_PUTEST_COREDUMP_PREPARE:
+ return "PREPARE COREDUMP";
+ case UNIFI_PUTEST_GP_READ16:
+ return "GP16R";
+ case UNIFI_PUTEST_GP_WRITE16:
+ return "GP16W";
+ default:
+ return "ERROR: unrecognised command";
+ }
+ }
+
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+int uf_register_hip_offline_debug(unifi_priv_t *priv)
+{
+ ul_client_t *udi_cli;
+ int i;
+
+ udi_cli = ul_register_client(priv, CLI_USING_WIRE_FORMAT, udi_log_event);
+ if (udi_cli == NULL) {
+ /* Too many clients already using this device */
+ unifi_error(priv, "Too many UDI clients already open\n");
+ return -ENOSPC;
+ }
+ unifi_trace(priv, UDBG1, "Offline HIP client is registered\n");
+
+ down(&priv->udi_logging_mutex);
+ udi_cli->event_hook = udi_log_event;
+ unifi_set_udi_hook(priv->card, logging_handler);
+ /* Log all signals by default */
+ for (i = 0; i < SIG_FILTER_SIZE; i++) {
+ udi_cli->signal_filter[i] = 0xFFFF;
+ }
+ priv->logging_client = udi_cli;
+ up(&priv->udi_logging_mutex);
+
+ return 0;
+}
+
+int uf_unregister_hip_offline_debug(unifi_priv_t *priv)
+{
+ ul_client_t *udi_cli = priv->logging_client;
+ if (udi_cli == NULL)
+ {
+ unifi_error(priv, "Unknown HIP client unregister request\n");
+ return -ERANGE;
+ }
+
+ unifi_trace(priv, UDBG1, "Offline HIP client is unregistered\n");
+
+ down(&priv->udi_logging_mutex);
+ priv->logging_client = NULL;
+ udi_cli->event_hook = NULL;
+ up(&priv->udi_logging_mutex);
+
+ ul_deregister_client(udi_cli);
+
+ return 0;
+}
+#endif
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_open
+ * unifi_release
+ *
+ * Open and release entry points for the UniFi debug driver.
+ *
+ * Arguments:
+ * Normal linux driver args.
+ *
+ * Returns:
+ * Linux error code.
+ * ---------------------------------------------------------------------------
+ */
+static int
+unifi_open(struct inode *inode, struct file *file)
+{
+ int devno;
+ unifi_priv_t *priv;
+ ul_client_t *udi_cli;
+
+ func_enter();
+
+ devno = MINOR(inode->i_rdev) >> 1;
+
+ /*
+ * Increase the ref_count for the char device clients.
+ * Make sure you call uf_put_instance() to decreace it if
+ * unifi_open returns an error.
+ */
+ priv = uf_get_instance(devno);
+ if (priv == NULL) {
+ unifi_error(NULL, "unifi_open: No device present\n");
+ func_exit();
+ return -ENODEV;
+ }
+
+ /* Register this instance in the client's list. */
+ /* The minor number determines the nature of the client (Unicli or SME). */
+ if (MINOR(inode->i_rdev) & 0x1) {
+ udi_cli = ul_register_client(priv, CLI_USING_WIRE_FORMAT, udi_log_event);
+ if (udi_cli == NULL) {
+ /* Too many clients already using this device */
+ unifi_error(priv, "Too many clients already open\n");
+ uf_put_instance(devno);
+ func_exit();
+ return -ENOSPC;
+ }
+ unifi_trace(priv, UDBG1, "Client is registered to /dev/unifiudi%d\n", devno);
+ } else {
+ /*
+ * Even-numbered device nodes are the control application.
+ * This is the userspace helper containing SME or
+ * unifi_manager.
+ */
+
+ down(&udi_mutex);
+
+#ifdef CSR_SME_USERSPACE
+ /* Check if a config client is already attached */
+ if (priv->sme_cli) {
+ up(&udi_mutex);
+ uf_put_instance(devno);
+
+ unifi_info(priv, "There is already a configuration client using the character device\n");
+ func_exit();
+ return -EBUSY;
+ }
+#endif /* CSR_SME_USERSPACE */
+
+#ifdef CSR_SUPPORT_SME
+ udi_cli = ul_register_client(priv,
+ CLI_USING_WIRE_FORMAT | CLI_SME_USERSPACE,
+ sme_log_event);
+#else
+ /* Config client for native driver */
+ udi_cli = ul_register_client(priv,
+ 0,
+ sme_native_log_event);
+#endif
+ if (udi_cli == NULL) {
+ /* Too many clients already using this device */
+ up(&udi_mutex);
+ uf_put_instance(devno);
+
+ unifi_error(priv, "Too many clients already open\n");
+ func_exit();
+ return -ENOSPC;
+ }
+
+ /*
+ * Fill-in the pointer to the configuration client.
+ * This is the SME userspace helper or unifi_manager.
+ * Not used in the SME embedded version.
+ */
+ unifi_trace(priv, UDBG1, "SME client (id:%d s:0x%X) is registered\n",
+ udi_cli->client_id, udi_cli->sender_id);
+ /* Store the SME UniFi Linux Client */
+ if (priv->sme_cli == NULL) {
+ priv->sme_cli = udi_cli;
+ }
+
+ up(&udi_mutex);
+ }
+
+
+ /*
+ * Store the pointer to the client.
+ * All char driver's entry points will pass this pointer.
+ */
+ file->private_data = udi_cli;
+
+ func_exit();
+ return 0;
+} /* unifi_open() */
+
+
+static int
+unifi_release(struct inode *inode, struct file *filp)
+{
+ ul_client_t *udi_cli = (void*)filp->private_data;
+ int devno;
+ unifi_priv_t *priv;
+
+ func_enter();
+
+ priv = uf_find_instance(udi_cli->instance);
+ if (!priv) {
+ unifi_error(priv, "unifi_close: instance for device not found\n");
+ return -ENODEV;
+ }
+
+ devno = MINOR(inode->i_rdev) >> 1;
+
+ /* Even device nodes are the config client (i.e. SME or unifi_manager) */
+ if ((MINOR(inode->i_rdev) & 0x1) == 0) {
+
+ if (priv->sme_cli != udi_cli) {
+ unifi_notice(priv, "Surprise closing config device: not the sme client\n");
+ }
+ unifi_notice(priv, "SME client close (unifi%d)\n", devno);
+
+ /*
+ * Clear sme_cli before calling unifi_sys_... so it doesn't try to
+ * queue a reply to the (now gone) SME.
+ */
+ down(&udi_mutex);
+ priv->sme_cli = NULL;
+ up(&udi_mutex);
+
+#ifdef CSR_SME_USERSPACE
+ /* Power-down when config client closes */
+ {
+ CsrWifiRouterCtrlWifiOffReq req = {{CSR_WIFI_ROUTER_CTRL_HIP_REQ, 0, 0, 0, NULL}};
+ CsrWifiRouterCtrlWifiOffReqHandler(priv, &req.common);
+ }
+
+ uf_sme_deinit(priv);
+
+ /* It is possible that a blocking SME request was made from another process
+ * which did not get read by the SME before the WifiOffReq.
+ * So check for a pending request which will go unanswered and cancel
+ * the wait for event. As only one blocking request can be in progress at
+ * a time, up to one event should be completed.
+ */
+ uf_sme_cancel_request(priv, 0);
+
+#endif /* CSR_SME_USERSPACE */
+ } else {
+
+ unifi_trace(priv, UDBG2, "UDI client close (unifiudi%d)\n", devno);
+
+ /* If the pointer matches the logging client, stop logging. */
+ down(&priv->udi_logging_mutex);
+ if (udi_cli == priv->logging_client) {
+ priv->logging_client = NULL;
+ }
+ up(&priv->udi_logging_mutex);
+
+ if (udi_cli == priv->amp_client) {
+ priv->amp_client = NULL;
+ }
+ }
+
+ /* Deregister this instance from the client's list. */
+ ul_deregister_client(udi_cli);
+
+ uf_put_instance(devno);
+
+ return 0;
+} /* unifi_release() */
+
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_read
+ *
+ * The read() driver entry point.
+ *
+ * Arguments:
+ * filp The file descriptor returned by unifi_open()
+ * p The user space buffer to copy the read data
+ * len The size of the p buffer
+ * poff
+ *
+ * Returns:
+ * number of bytes read or an error code on failure
+ * ---------------------------------------------------------------------------
+ */
+static ssize_t
+unifi_read(struct file *filp, char *p, size_t len, loff_t *poff)
+{
+ ul_client_t *pcli = (void*)filp->private_data;
+ unifi_priv_t *priv;
+ udi_log_t *logptr = NULL;
+ udi_msg_t *msgptr;
+ struct list_head *l;
+ int msglen;
+
+ func_enter();
+
+ priv = uf_find_instance(pcli->instance);
+ if (!priv) {
+ unifi_error(priv, "invalid priv\n");
+ return -ENODEV;
+ }
+
+ if (!pcli->udi_enabled) {
+ unifi_error(priv, "unifi_read: unknown client.");
+ return -EINVAL;
+ }
+
+ if (list_empty(&pcli->udi_log)) {
+ if (filp->f_flags & O_NONBLOCK) {
+ /* Non-blocking - just return if the udi_log is empty */
+ return 0;
+ } else {
+ /* Blocking - wait on the UDI wait queue */
+ if (wait_event_interruptible(pcli->udi_wq,
+ !list_empty(&pcli->udi_log)))
+ {
+ unifi_error(priv, "unifi_read: wait_event_interruptible failed.");
+ return -ERESTARTSYS;
+ }
+ }
+ }
+
+ /* Read entry from list head and remove it from the list */
+ if (down_interruptible(&pcli->udi_sem)) {
+ return -ERESTARTSYS;
+ }
+ l = pcli->udi_log.next;
+ list_del(l);
+ up(&pcli->udi_sem);
+
+ /* Get a pointer to whole struct */
+ logptr = list_entry(l, udi_log_t, q);
+ if (logptr == NULL) {
+ unifi_error(priv, "unifi_read: failed to get event.\n");
+ return -EINVAL;
+ }
+
+ /* Get the real message */
+ msgptr = &logptr->msg;
+ msglen = msgptr->length;
+ if (msglen > len) {
+ printk(KERN_WARNING "truncated read to %d actual msg len is %lu\n", msglen, (long unsigned int)len);
+ msglen = len;
+ }
+
+ /* and pass it to the client (SME or Unicli). */
+ if (copy_to_user(p, msgptr, msglen))
+ {
+ printk(KERN_ERR "Failed to copy UDI log to user\n");
+ kfree(logptr);
+ return -EFAULT;
+ }
+
+ /* It is our resposibility to free the message buffer. */
+ kfree(logptr);
+
+ func_exit_r(msglen);
+ return msglen;
+
+} /* unifi_read() */
+
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * udi_send_signal_unpacked
+ *
+ * Sends an unpacked signal to UniFi.
+ *
+ * Arguments:
+ * priv Pointer to private context struct
+ * data Pointer to request structure and data to send
+ * data_len Length of data in data pointer.
+ *
+ * Returns:
+ * Number of bytes written, error otherwise.
+ *
+ * Notes:
+ * All clients that use this function to send a signal to the unifi
+ * must use the host formatted structures.
+ * ---------------------------------------------------------------------------
+ */
+static int
+udi_send_signal_unpacked(unifi_priv_t *priv, unsigned char* data, uint data_len)
+{
+ CSR_SIGNAL *sigptr = (CSR_SIGNAL*)data;
+ CSR_DATAREF *datarefptr;
+ bulk_data_param_t bulk_data;
+ uint signal_size, i;
+ uint bulk_data_offset = 0;
+ int bytecount, r;
+ CsrResult csrResult;
+
+ /* Number of bytes in the signal */
+ signal_size = SigGetSize(sigptr);
+ if (!signal_size || (signal_size > data_len)) {
+ unifi_error(priv, "unifi_sme_mlme_req - Invalid signal 0x%x size should be %d bytes\n",
+ sigptr->SignalPrimitiveHeader.SignalId,
+ signal_size);
+ return -EINVAL;
+ }
+ bytecount = signal_size;
+
+ /* Get a pointer to the information of the first data reference */
+ datarefptr = (CSR_DATAREF*)&sigptr->u;
+
+ /* Initialize the offset in the data buffer, bulk data is right after the signal. */
+ bulk_data_offset = signal_size;
+
+ /* store the references and the size of the bulk data to the bulkdata structure */
+ for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) {
+ /* the length of the bulk data is in the signal */
+ if ((datarefptr+i)->DataLength) {
+ void *dest;
+
+ csrResult = unifi_net_data_malloc(priv, &bulk_data.d[i], (datarefptr+i)->DataLength);
+ if (csrResult != CSR_RESULT_SUCCESS) {
+ unifi_error(priv, "udi_send_signal_unpacked: failed to allocate request_data.\n");
+ return -EIO;
+ }
+
+ dest = (void*)bulk_data.d[i].os_data_ptr;
+ memcpy(dest, data + bulk_data_offset, bulk_data.d[i].data_length);
+ } else {
+ bulk_data.d[i].data_length = 0;
+ }
+
+ bytecount += bulk_data.d[i].data_length;
+ /* advance the offset, to point the next bulk data */
+ bulk_data_offset += bulk_data.d[i].data_length;
+ }
+
+
+ unifi_trace(priv, UDBG3, "SME Send: signal 0x%.4X\n", sigptr->SignalPrimitiveHeader.SignalId);
+
+ /* Send the signal. */
+ r = ul_send_signal_unpacked(priv, sigptr, &bulk_data);
+ if (r < 0) {
+ unifi_error(priv, "udi_send_signal_unpacked: send failed (%d)\n", r);
+ for(i=0;i<UNIFI_MAX_DATA_REFERENCES;i++) {
+ if(bulk_data.d[i].data_length != 0) {
+ unifi_net_data_free(priv, &bulk_data.d[i]);
+ }
+ }
+ func_exit();
+ return -EIO;
+ }
+
+ return bytecount;
+} /* udi_send_signal_unpacked() */
+
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * udi_send_signal_raw
+ *
+ * Sends a packed signal to UniFi.
+ *
+ * Arguments:
+ * priv Pointer to private context struct
+ * buf Pointer to request structure and data to send
+ * buflen Length of data in data pointer.
+ *
+ * Returns:
+ * Number of bytes written, error otherwise.
+ *
+ * Notes:
+ * All clients that use this function to send a signal to the unifi
+ * must use the wire formatted structures.
+ * ---------------------------------------------------------------------------
+ */
+static int
+udi_send_signal_raw(unifi_priv_t *priv, unsigned char *buf, int buflen)
+{
+ int signal_size;
+ int sig_id;
+ bulk_data_param_t data_ptrs;
+ int i, r;
+ unsigned int num_data_refs;
+ int bytecount;
+ CsrResult csrResult;
+
+ func_enter();
+
+ /*
+ * The signal is the first thing in buf, the signal id is the
+ * first 16 bits of the signal.
+ */
+ /* Number of bytes in the signal */
+ sig_id = GET_SIGNAL_ID(buf);
+ signal_size = buflen;
+ signal_size -= GET_PACKED_DATAREF_LEN(buf, 0);
+ signal_size -= GET_PACKED_DATAREF_LEN(buf, 1);
+ if ((signal_size <= 0) || (signal_size > buflen)) {
+ unifi_error(priv, "udi_send_signal_raw - Couldn't find length of signal 0x%x\n",
+ sig_id);
+ func_exit();
+ return -EINVAL;
+ }
+ unifi_trace(priv, UDBG2, "udi_send_signal_raw: signal 0x%.4X len:%d\n",
+ sig_id, signal_size);
+ /* Zero the data ref arrays */
+ memset(&data_ptrs, 0, sizeof(data_ptrs));
+
+ /*
+ * Find the number of associated bulk data packets. Scan through
+ * the data refs to check that we have enough data and pick out
+ * pointers to appended bulk data.
+ */
+ num_data_refs = 0;
+ bytecount = signal_size;
+
+ for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; ++i)
+ {
+ unsigned int len = GET_PACKED_DATAREF_LEN(buf, i);
+ unifi_trace(priv, UDBG3, "udi_send_signal_raw: data_ref length = %d\n", len);
+
+ if (len != 0) {
+ void *dest;
+
+ csrResult = unifi_net_data_malloc(priv, &data_ptrs.d[i], len);
+ if (csrResult != CSR_RESULT_SUCCESS) {
+ unifi_error(priv, "udi_send_signal_raw: failed to allocate request_data.\n");
+ return -EIO;
+ }
+
+ dest = (void*)data_ptrs.d[i].os_data_ptr;
+ memcpy(dest, buf + bytecount, len);
+
+ bytecount += len;
+ num_data_refs++;
+ }
+ data_ptrs.d[i].data_length = len;
+ }
+
+ unifi_trace(priv, UDBG3, "Queueing signal 0x%.4X from UDI with %u data refs\n",
+ sig_id,
+ num_data_refs);
+
+ if (bytecount > buflen) {
+ unifi_error(priv, "udi_send_signal_raw: Not enough data (%d instead of %d)\n", buflen, bytecount);
+ func_exit();
+ return -EINVAL;
+ }
+
+ /* Send the signal calling the function that uses the wire-formatted signals. */
+ r = ul_send_signal_raw(priv, buf, signal_size, &data_ptrs);
+ if (r < 0) {
+ unifi_error(priv, "udi_send_signal_raw: send failed (%d)\n", r);
+ func_exit();
+ return -EIO;
+ }
+
+#ifdef CSR_NATIVE_LINUX
+ if (sig_id == CSR_MLME_POWERMGT_REQUEST_ID) {
+ int power_mode = CSR_GET_UINT16_FROM_LITTLE_ENDIAN((buf +
+ SIZEOF_SIGNAL_HEADER + (UNIFI_MAX_DATA_REFERENCES*SIZEOF_DATAREF)));
+#ifdef CSR_SUPPORT_WEXT
+ /* Overide the wext power mode to the new value */
+ priv->wext_conf.power_mode = power_mode;
+#endif
+ /* Configure deep sleep signaling */
+ if (power_mode || (priv->interfacePriv[0]->connected == UnifiNotConnected)) {
+ csrResult = unifi_configure_low_power_mode(priv->card,
+ UNIFI_LOW_POWER_ENABLED,
+ UNIFI_PERIODIC_WAKE_HOST_DISABLED);
+ } else {
+ csrResult = unifi_configure_low_power_mode(priv->card,
+ UNIFI_LOW_POWER_DISABLED,
+ UNIFI_PERIODIC_WAKE_HOST_DISABLED);
+ }
+ }
+#endif
+
+ func_exit_r(bytecount);
+
+ return bytecount;
+} /* udi_send_signal_raw */
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_write
+ *
+ * The write() driver entry point.
+ * A UniFi Debug Interface client such as unicli can write a signal
+ * plus bulk data to the driver for sending to the UniFi chip.
+ *
+ * Only one signal may be sent per write operation.
+ *
+ * Arguments:
+ * filp The file descriptor returned by unifi_open()
+ * p The user space buffer to get the data from
+ * len The size of the p buffer
+ * poff
+ *
+ * Returns:
+ * number of bytes written or an error code on failure
+ * ---------------------------------------------------------------------------
+ */
+static ssize_t
+unifi_write(struct file *filp, const char *p, size_t len, loff_t *poff)
+{
+ ul_client_t *pcli = (ul_client_t*)filp->private_data;
+ unifi_priv_t *priv;
+ unsigned char *buf;
+ unsigned char *bufptr;
+ int remaining;
+ int bytes_written;
+ int r;
+ bulk_data_param_t bulkdata;
+ CsrResult csrResult;
+
+ func_enter();
+
+ priv = uf_find_instance(pcli->instance);
+ if (!priv) {
+ unifi_error(priv, "invalid priv\n");
+ return -ENODEV;
+ }
+
+ unifi_trace(priv, UDBG5, "unifi_write: len = %d\n", len);
+
+ if (!pcli->udi_enabled) {
+ unifi_error(priv, "udi disabled\n");
+ return -EINVAL;
+ }
+
+ /*
+ * AMP client sends only one signal at a time, so we can use
+ * unifi_net_data_malloc to save the extra copy.
+ */
+ if (pcli == priv->amp_client) {
+ int signal_size;
+ int sig_id;
+ unsigned char *signal_buf;
+ char *user_data_buf;
+
+ csrResult = unifi_net_data_malloc(priv, &bulkdata.d[0], len);
+ if (csrResult != CSR_RESULT_SUCCESS) {
+ unifi_error(priv, "unifi_write: failed to allocate request_data.\n");
+ func_exit();
+ return -ENOMEM;
+ }
+
+ user_data_buf = (char*)bulkdata.d[0].os_data_ptr;
+
+ /* Get the data from the AMP client. */
+ if (copy_from_user((void*)user_data_buf, p, len)) {
+ unifi_error(priv, "unifi_write: copy from user failed\n");
+ unifi_net_data_free(priv, &bulkdata.d[0]);
+ func_exit();
+ return -EFAULT;
+ }
+
+ bulkdata.d[1].os_data_ptr = NULL;
+ bulkdata.d[1].data_length = 0;
+
+ /* Number of bytes in the signal */
+ sig_id = GET_SIGNAL_ID(bulkdata.d[0].os_data_ptr);
+ signal_size = len;
+ signal_size -= GET_PACKED_DATAREF_LEN(bulkdata.d[0].os_data_ptr, 0);
+ signal_size -= GET_PACKED_DATAREF_LEN(bulkdata.d[0].os_data_ptr, 1);
+ if ((signal_size <= 0) || (signal_size > len)) {
+ unifi_error(priv, "unifi_write - Couldn't find length of signal 0x%x\n",
+ sig_id);
+ unifi_net_data_free(priv, &bulkdata.d[0]);
+ func_exit();
+ return -EINVAL;
+ }
+
+ unifi_trace(priv, UDBG2, "unifi_write: signal 0x%.4X len:%d\n",
+ sig_id, signal_size);
+
+ /* Allocate a buffer for the signal */
+ signal_buf = kmalloc(signal_size, GFP_KERNEL);
+ if (!signal_buf) {
+ unifi_net_data_free(priv, &bulkdata.d[0]);
+ func_exit();
+ return -ENOMEM;
+ }
+
+ /* Get the signal from the os_data_ptr */
+ memcpy(signal_buf, bulkdata.d[0].os_data_ptr, signal_size);
+ signal_buf[5] = (pcli->sender_id >> 8) & 0xff;
+
+ if (signal_size < len) {
+ /* Remove the signal from the os_data_ptr */
+ bulkdata.d[0].data_length -= signal_size;
+ bulkdata.d[0].os_data_ptr += signal_size;
+ } else {
+ bulkdata.d[0].data_length = 0;
+ bulkdata.d[0].os_data_ptr = NULL;
+ }
+
+ /* Send the signal calling the function that uses the wire-formatted signals. */
+ r = ul_send_signal_raw(priv, signal_buf, signal_size, &bulkdata);
+ if (r < 0) {
+ unifi_error(priv, "unifi_write: send failed (%d)\n", r);
+ if (bulkdata.d[0].os_data_ptr != NULL) {
+ unifi_net_data_free(priv, &bulkdata.d[0]);
+ }
+ }
+
+ /* Free the signal buffer and return */
+ kfree(signal_buf);
+ return len;
+ }
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (!buf) {
+ return -ENOMEM;
+ }
+
+ /* Get the data from the client (SME or Unicli). */
+ if (copy_from_user((void*)buf, p, len)) {
+ unifi_error(priv, "copy from user failed\n");
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ /*
+ * In SME userspace build read() contains a SYS or MGT message.
+ * Note that even though the SME sends one signal at a time, we can not
+ * use unifi_net_data_malloc because in the early stages, before having
+ * initialised the core, it will fail since the I/O block size is unknown.
+ */
+#ifdef CSR_SME_USERSPACE
+ if (pcli->configuration & CLI_SME_USERSPACE) {
+ CsrWifiRouterTransportRecv(priv, buf, len);
+ kfree(buf);
+ return len;
+ }
+#endif
+
+ /* ul_send_signal_raw will do a sanity check of len against signal content */
+
+ /*
+ * udi_send_signal_raw() and udi_send_signal_unpacked() return the number of bytes consumed.
+ * A write call can pass multiple signal concatenated together.
+ */
+ bytes_written = 0;
+ remaining = len;
+ bufptr = buf;
+ while (remaining > 0)
+ {
+ int r;
+
+ /*
+ * Set the SenderProcessId.
+ * The SignalPrimitiveHeader is the first 3 16-bit words of the signal,
+ * the SenderProcessId is bytes 4,5.
+ * The MSB of the sender ID needs to be set to the client ID.
+ * The LSB is controlled by the SME.
+ */
+ bufptr[5] = (pcli->sender_id >> 8) & 0xff;
+
+ /* use the appropriate interface, depending on the clients' configuration */
+ if (pcli->configuration & CLI_USING_WIRE_FORMAT) {
+ unifi_trace(priv, UDBG1, "unifi_write: call udi_send_signal().\n");
+ r = udi_send_signal_raw(priv, bufptr, remaining);
+ } else {
+ r = udi_send_signal_unpacked(priv, bufptr, remaining);
+ }
+ if (r < 0) {
+ /* Set the return value to the error code */
+ unifi_error(priv, "unifi_write: (udi or sme)_send_signal() returns %d\n", r);
+ bytes_written = r;
+ break;
+ }
+ bufptr += r;
+ remaining -= r;
+ bytes_written += r;
+ }
+
+ kfree(buf);
+
+ func_exit_r(bytes_written);
+
+ return bytes_written;
+} /* unifi_write() */
+
+
+static const char* build_type_to_string(unsigned char build_type)
+{
+ switch (build_type)
+ {
+ case UNIFI_BUILD_NME: return "NME";
+ case UNIFI_BUILD_WEXT: return "WEXT";
+ case UNIFI_BUILD_AP: return "AP";
+ }
+ return "unknown";
+}
+
+
+/*
+ * ----------------------------------------------------------------
+ * unifi_ioctl
+ *
+ * Ioctl handler for unifi driver.
+ *
+ * Arguments:
+ * inodep Pointer to inode structure.
+ * filp Pointer to file structure.
+ * cmd Ioctl cmd passed by user.
+ * arg Ioctl arg passed by user.
+ *
+ * Returns:
+ * 0 on success, -ve error code on error.
+ * ----------------------------------------------------------------
+ */
+static long
+unifi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ ul_client_t *pcli = (ul_client_t*)filp->private_data;
+ unifi_priv_t *priv;
+ struct net_device *dev;
+ int r = 0;
+ int int_param, i;
+ u8* buf;
+ CsrResult csrResult;
+#if (defined CSR_SUPPORT_SME)
+ unifi_cfg_command_t cfg_cmd;
+#if (defined CSR_SUPPORT_WEXT)
+ CsrWifiSmeCoexConfig coex_config;
+ unsigned char uchar_param;
+ unsigned char varbind[MAX_VARBIND_LENGTH];
+ int vblen;
+#endif
+#endif
+ unifi_putest_command_t putest_cmd;
+
+ priv = uf_find_instance(pcli->instance);
+ if (!priv) {
+ unifi_error(priv, "ioctl error: unknown instance=%d\n", pcli->instance);
+ r = -ENODEV;
+ goto out;
+ }
+ unifi_trace(priv, UDBG5, "unifi_ioctl: cmd=0x%X, arg=0x%lX\n", cmd, arg);
+
+ switch (cmd) {
+
+ case UNIFI_GET_UDI_ENABLE:
+ unifi_trace(priv, UDBG4, "UniFi Get UDI Enable\n");
+
+ down(&priv->udi_logging_mutex);
+ int_param = (priv->logging_client == NULL) ? 0 : 1;
+ up(&priv->udi_logging_mutex);
+
+ if (put_user(int_param, (int*)arg))
+ {
+ unifi_error(priv, "UNIFI_GET_UDI_ENABLE: Failed to copy to user\n");
+ r = -EFAULT;
+ goto out;
+ }
+ break;
+
+ case UNIFI_SET_UDI_ENABLE:
+ unifi_trace(priv, UDBG4, "UniFi Set UDI Enable\n");
+ if (get_user(int_param, (int*)arg))
+ {
+ unifi_error(priv, "UNIFI_SET_UDI_ENABLE: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+ if (log_hip_signals) {
+ unifi_error(priv, "omnicli cannot be used when log_hip_signals is used\n");
+ r = -EFAULT;
+ goto out;
+ }
+#endif
+
+ down(&priv->udi_logging_mutex);
+ if (int_param) {
+ pcli->event_hook = udi_log_event;
+ unifi_set_udi_hook(priv->card, logging_handler);
+ /* Log all signals by default */
+ for (i = 0; i < SIG_FILTER_SIZE; i++) {
+ pcli->signal_filter[i] = 0xFFFF;
+ }
+ priv->logging_client = pcli;
+
+ } else {
+ priv->logging_client = NULL;
+ pcli->event_hook = NULL;
+ }
+ up(&priv->udi_logging_mutex);
+
+ break;
+
+ case UNIFI_SET_MIB:
+ unifi_trace(priv, UDBG4, "UniFi Set MIB\n");
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+ /* Read first 2 bytes and check length */
+ if (copy_from_user((void*)varbind, (void*)arg, 2)) {
+ unifi_error(priv,
+ "UNIFI_SET_MIB: Failed to copy in varbind header\n");
+ r = -EFAULT;
+ goto out;
+ }
+ vblen = varbind[1];
+ if ((vblen + 2) > MAX_VARBIND_LENGTH) {
+ unifi_error(priv,
+ "UNIFI_SET_MIB: Varbind too long (%d, limit %d)\n",
+ (vblen+2), MAX_VARBIND_LENGTH);
+ r = -EINVAL;
+ goto out;
+ }
+ /* Read rest of varbind */
+ if (copy_from_user((void*)(varbind+2), (void*)(arg+2), vblen)) {
+ unifi_error(priv, "UNIFI_SET_MIB: Failed to copy in varbind\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ /* send to SME */
+ vblen += 2;
+ r = sme_mgt_mib_set(priv, varbind, vblen);
+ if (r) {
+ goto out;
+ }
+#else
+ unifi_notice(priv, "UNIFI_SET_MIB: Unsupported.\n");
+#endif /* CSR_SUPPORT_WEXT */
+ break;
+
+ case UNIFI_GET_MIB:
+ unifi_trace(priv, UDBG4, "UniFi Get MIB\n");
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+ /* Read first 2 bytes and check length */
+ if (copy_from_user((void*)varbind, (void*)arg, 2)) {
+ unifi_error(priv, "UNIFI_GET_MIB: Failed to copy in varbind header\n");
+ r = -EFAULT;
+ goto out;
+ }
+ vblen = varbind[1];
+ if ((vblen+2) > MAX_VARBIND_LENGTH) {
+ unifi_error(priv, "UNIFI_GET_MIB: Varbind too long (%d, limit %d)\n",
+ (vblen+2), MAX_VARBIND_LENGTH);
+ r = -EINVAL;
+ goto out;
+ }
+ /* Read rest of varbind */
+ if (copy_from_user((void*)(varbind+2), (void*)(arg+2), vblen)) {
+ unifi_error(priv, "UNIFI_GET_MIB: Failed to copy in varbind\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ vblen += 2;
+ r = sme_mgt_mib_get(priv, varbind, &vblen);
+ if (r) {
+ goto out;
+ }
+ /* copy out varbind */
+ if (vblen > MAX_VARBIND_LENGTH) {
+ unifi_error(priv,
+ "UNIFI_GET_MIB: Varbind result too long (%d, limit %d)\n",
+ vblen, MAX_VARBIND_LENGTH);
+ r = -EINVAL;
+ goto out;
+ }
+ if (copy_to_user((void*)arg, varbind, vblen)) {
+ r = -EFAULT;
+ goto out;
+ }
+#else
+ unifi_notice(priv, "UNIFI_GET_MIB: Unsupported.\n");
+#endif /* CSR_SUPPORT_WEXT */
+ break;
+
+ case UNIFI_CFG:
+#if (defined CSR_SUPPORT_SME)
+ if (get_user(cfg_cmd, (unifi_cfg_command_t*)arg))
+ {
+ unifi_error(priv, "UNIFI_CFG: Failed to get the command\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ unifi_trace(priv, UDBG1, "UNIFI_CFG: Command is %d (t=%u) sz=%d\n",
+ cfg_cmd, jiffies_to_msecs(jiffies), sizeof(unifi_cfg_command_t));
+ switch (cfg_cmd) {
+ case UNIFI_CFG_POWER:
+ r = unifi_cfg_power(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_POWERSAVE:
+ r = unifi_cfg_power_save(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_POWERSUPPLY:
+ r = unifi_cfg_power_supply(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_FILTER:
+ r = unifi_cfg_packet_filters(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_GET:
+ r = unifi_cfg_get_info(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_WMM_QOSINFO:
+ r = unifi_cfg_wmm_qos_info(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_WMM_ADDTS:
+ r = unifi_cfg_wmm_addts(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_WMM_DELTS:
+ r = unifi_cfg_wmm_delts(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_STRICT_DRAFT_N:
+ r = unifi_cfg_strict_draft_n(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_CFG_ENABLE_OKC:
+ r = unifi_cfg_enable_okc(priv, (unsigned char*)arg);
+ break;
+#ifdef CSR_SUPPORT_SME
+ case UNIFI_CFG_CORE_DUMP:
+ CsrWifiRouterCtrlWifiOffIndSend(priv->CSR_WIFI_SME_IFACEQUEUE,0,CSR_WIFI_SME_CONTROL_INDICATION_ERROR);
+ unifi_trace(priv, UDBG2, "UNIFI_CFG_CORE_DUMP: sent wifi off indication\n");
+ break;
+#endif
+#ifdef CSR_SUPPORT_WEXT_AP
+ case UNIFI_CFG_SET_AP_CONFIG:
+ r= unifi_cfg_set_ap_config(priv,(unsigned char*)arg);
+ break;
+#endif
+ default:
+ unifi_error(priv, "UNIFI_CFG: Unknown Command (%d)\n", cfg_cmd);
+ r = -EINVAL;
+ goto out;
+ }
+#endif
+
+ break;
+
+ case UNIFI_PUTEST:
+ if (get_user(putest_cmd, (unifi_putest_command_t*)arg))
+ {
+ unifi_error(priv, "UNIFI_PUTEST: Failed to get the command\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ unifi_trace(priv, UDBG1, "UNIFI_PUTEST: Command is %s\n",
+ trace_putest_cmdid(putest_cmd));
+ switch (putest_cmd) {
+ case UNIFI_PUTEST_START:
+ r = unifi_putest_start(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_STOP:
+ r = unifi_putest_stop(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_SET_SDIO_CLOCK:
+ r = unifi_putest_set_sdio_clock(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_CMD52_READ:
+ r = unifi_putest_cmd52_read(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_CMD52_BLOCK_READ:
+ r = unifi_putest_cmd52_block_read(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_CMD52_WRITE:
+ r = unifi_putest_cmd52_write(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_DL_FW:
+ r = unifi_putest_dl_fw(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_DL_FW_BUFF:
+ r = unifi_putest_dl_fw_buff(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_COREDUMP_PREPARE:
+ r = unifi_putest_coredump_prepare(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_GP_READ16:
+ r = unifi_putest_gp_read16(priv, (unsigned char*)arg);
+ break;
+ case UNIFI_PUTEST_GP_WRITE16:
+ r = unifi_putest_gp_write16(priv, (unsigned char*)arg);
+ break;
+ default:
+ unifi_error(priv, "UNIFI_PUTEST: Unknown Command (%d)\n", putest_cmd);
+ r = -EINVAL;
+ goto out;
+ }
+
+ break;
+ case UNIFI_BUILD_TYPE:
+ unifi_trace(priv, UDBG2, "UNIFI_BUILD_TYPE userspace=%s\n", build_type_to_string(*(unsigned char*)arg));
+#ifndef CSR_SUPPORT_WEXT_AP
+ if (UNIFI_BUILD_AP == *(unsigned char*)arg)
+ {
+ unifi_error(priv, "Userspace has AP support, which is incompatible\n");
+ }
+#endif
+
+#ifndef CSR_SUPPORT_WEXT
+ if (UNIFI_BUILD_WEXT == *(unsigned char*)arg)
+ {
+ unifi_error(priv, "Userspace has WEXT support, which is incompatible\n");
+ }
+#endif
+ break;
+ case UNIFI_INIT_HW:
+ unifi_trace(priv, UDBG2, "UNIFI_INIT_HW.\n");
+ priv->init_progress = UNIFI_INIT_NONE;
+
+#if defined(CSR_SUPPORT_WEXT) || defined (CSR_NATIVE_LINUX)
+ /* At this point we are ready to start the SME. */
+ r = sme_mgt_wifi_on(priv);
+ if (r) {
+ goto out;
+ }
+#endif
+
+ break;
+
+ case UNIFI_INIT_NETDEV:
+ {
+ /* get the proper interfaceTagId */
+ u16 interfaceTag=0;
+ netInterface_priv_t *interfacePriv = priv->interfacePriv[interfaceTag];
+
+ dev = priv->netdev[interfaceTag];
+ unifi_trace(priv, UDBG2, "UNIFI_INIT_NETDEV.\n");
+
+ if (copy_from_user((void*)dev->dev_addr, (void*)arg, 6)) {
+ r = -EFAULT;
+ goto out;
+ }
+
+ /* Attach the network device to the stack */
+ if (!interfacePriv->netdev_registered)
+ {
+ r = uf_register_netdev(priv,interfaceTag);
+ if (r) {
+ unifi_error(priv, "Failed to register the network device.\n");
+ goto out;
+ }
+ }
+
+ /* Apply scheduled interrupt mode, if requested by module param */
+ if (run_bh_once != -1) {
+ unifi_set_interrupt_mode(priv->card, (u32)run_bh_once);
+ }
+
+ priv->init_progress = UNIFI_INIT_COMPLETED;
+
+ /* Firmware initialisation is complete, so let the SDIO bus
+ * clock be raised when convienent to the core.
+ */
+ unifi_request_max_sdio_clock(priv->card);
+
+#ifdef CSR_SUPPORT_WEXT
+ /* Notify the Android wpa_supplicant that we are ready */
+ wext_send_started_event(priv);
+#endif
+
+ unifi_info(priv, "UniFi ready\n");
+
+#ifdef ANDROID_BUILD
+ /* Release the wakelock */
+ unifi_trace(priv, UDBG1, "netdev_init: release wake lock\n");
+ wake_unlock(&unifi_sdio_wake_lock);
+#endif
+#ifdef CSR_NATIVE_SOFTMAC /* For softmac dev, force-enable the network interface rather than wait for a connected-ind */
+ {
+ struct net_device *dev = priv->netdev[interfaceTag];
+#ifdef CSR_SUPPORT_WEXT
+ interfacePriv->wait_netdev_change = TRUE;
+#endif
+ netif_carrier_on(dev);
+ }
+#endif
+ }
+ break;
+ case UNIFI_GET_INIT_STATUS:
+ unifi_trace(priv, UDBG2, "UNIFI_GET_INIT_STATUS.\n");
+ if (put_user(priv->init_progress, (int*)arg))
+ {
+ printk(KERN_ERR "UNIFI_GET_INIT_STATUS: Failed to copy to user\n");
+ r = -EFAULT;
+ goto out;
+ }
+ break;
+
+ case UNIFI_KICK:
+ unifi_trace(priv, UDBG4, "Kick UniFi\n");
+ unifi_sdio_interrupt_handler(priv->card);
+ break;
+
+ case UNIFI_SET_DEBUG:
+ unifi_debug = arg;
+ unifi_trace(priv, UDBG4, "unifi_debug set to %d\n", unifi_debug);
+ break;
+
+ case UNIFI_SET_TRACE:
+ /* no longer supported */
+ r = -EINVAL;
+ break;
+
+
+ case UNIFI_SET_UDI_LOG_MASK:
+ {
+ unifiio_filter_t udi_filter;
+ uint16_t *sig_ids_addr;
+#define UF_MAX_SIG_IDS 128 /* Impose a sensible limit */
+
+ if (copy_from_user((void*)(&udi_filter), (void*)arg, sizeof(udi_filter))) {
+ r = -EFAULT;
+ goto out;
+ }
+ if ((udi_filter.action < UfSigFil_AllOn) ||
+ (udi_filter.action > UfSigFil_SelectOff))
+ {
+ printk(KERN_WARNING
+ "UNIFI_SET_UDI_LOG_MASK: Bad action value: %d\n",
+ udi_filter.action);
+ r = -EINVAL;
+ goto out;
+ }
+ /* No signal list for "All" actions */
+ if ((udi_filter.action == UfSigFil_AllOn) ||
+ (udi_filter.action == UfSigFil_AllOff))
+ {
+ udi_filter.num_sig_ids = 0;
+ }
+
+ if (udi_filter.num_sig_ids > UF_MAX_SIG_IDS) {
+ printk(KERN_WARNING
+ "UNIFI_SET_UDI_LOG_MASK: too many signal ids (%d, max %d)\n",
+ udi_filter.num_sig_ids, UF_MAX_SIG_IDS);
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* Copy in signal id list if given */
+ if (udi_filter.num_sig_ids > 0) {
+ /* Preserve userspace address of sig_ids array */
+ sig_ids_addr = udi_filter.sig_ids;
+ /* Allocate kernel memory for sig_ids and copy to it */
+ udi_filter.sig_ids =
+ kmalloc(udi_filter.num_sig_ids * sizeof(uint16_t), GFP_KERNEL);
+ if (!udi_filter.sig_ids) {
+ r = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user((void*)udi_filter.sig_ids,
+ (void*)sig_ids_addr,
+ udi_filter.num_sig_ids * sizeof(uint16_t)))
+ {
+ kfree(udi_filter.sig_ids);
+ r = -EFAULT;
+ goto out;
+ }
+ }
+
+ udi_set_log_filter(pcli, &udi_filter);
+
+ if (udi_filter.num_sig_ids > 0) {
+ kfree(udi_filter.sig_ids);
+ }
+ }
+ break;
+
+ case UNIFI_SET_AMP_ENABLE:
+ unifi_trace(priv, UDBG4, "UniFi Set AMP Enable\n");
+ if (get_user(int_param, (int*)arg))
+ {
+ unifi_error(priv, "UNIFI_SET_AMP_ENABLE: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ if (int_param) {
+ priv->amp_client = pcli;
+ } else {
+ priv->amp_client = NULL;
+ }
+
+ int_param = 0;
+ buf = (u8*)&int_param;
+ buf[0] = UNIFI_SOFT_COMMAND_Q_LENGTH - 1;
+ buf[1] = UNIFI_SOFT_TRAFFIC_Q_LENGTH - 1;
+ if (copy_to_user((void*)arg, &int_param, sizeof(int))) {
+ r = -EFAULT;
+ goto out;
+ }
+ break;
+
+ case UNIFI_SET_UDI_SNAP_MASK:
+ {
+ unifiio_snap_filter_t snap_filter;
+
+ if (copy_from_user((void*)(&snap_filter), (void*)arg, sizeof(snap_filter))) {
+ r = -EFAULT;
+ goto out;
+ }
+
+ if (pcli->snap_filter.count) {
+ pcli->snap_filter.count = 0;
+ kfree(pcli->snap_filter.protocols);
+ }
+
+ if (snap_filter.count == 0) {
+ break;
+ }
+
+ pcli->snap_filter.protocols = kmalloc(snap_filter.count * sizeof(u16), GFP_KERNEL);
+ if (!pcli->snap_filter.protocols) {
+ r = -ENOMEM;
+ goto out;
+ }
+ if (copy_from_user((void*)pcli->snap_filter.protocols,
+ (void*)snap_filter.protocols,
+ snap_filter.count * sizeof(u16)))
+ {
+ kfree(pcli->snap_filter.protocols);
+ r = -EFAULT;
+ goto out;
+ }
+
+ pcli->snap_filter.count = snap_filter.count;
+
+ }
+ break;
+
+ case UNIFI_SME_PRESENT:
+ {
+ u8 ind;
+ unifi_trace(priv, UDBG4, "UniFi SME Present IOCTL.\n");
+ if (copy_from_user((void*)(&int_param), (void*)arg, sizeof(int)))
+ {
+ printk(KERN_ERR "UNIFI_SME_PRESENT: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ priv->sme_is_present = int_param;
+ if (priv->sme_is_present == 1) {
+ ind = CONFIG_SME_PRESENT;
+ } else {
+ ind = CONFIG_SME_NOT_PRESENT;
+ }
+ /* Send an indication to the helper app. */
+ ul_log_config_ind(priv, &ind, sizeof(u8));
+ }
+ break;
+
+ case UNIFI_CFG_PERIOD_TRAFFIC:
+ {
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+ CsrWifiSmeCoexConfig coexConfig;
+#endif /* CSR_SUPPORT_SME && CSR_SUPPORT_WEXT */
+ unifi_trace(priv, UDBG4, "UniFi Configure Periodic Traffic.\n");
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+ if (copy_from_user((void*)(&uchar_param), (void*)arg, sizeof(unsigned char))) {
+ unifi_error(priv, "UNIFI_CFG_PERIOD_TRAFFIC: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ if (uchar_param == 0) {
+ r = sme_mgt_coex_config_get(priv, &coexConfig);
+ if (r) {
+ unifi_error(priv, "UNIFI_CFG_PERIOD_TRAFFIC: Get unifi_CoexInfoValue failed.\n");
+ goto out;
+ }
+ if (copy_to_user((void*)(arg + 1),
+ (void*)&coexConfig,
+ sizeof(CsrWifiSmeCoexConfig))) {
+ r = -EFAULT;
+ goto out;
+ }
+ goto out;
+ }
+
+ if (copy_from_user((void*)(&coex_config), (void*)(arg + 1), sizeof(CsrWifiSmeCoexConfig)))
+ {
+ unifi_error(priv, "UNIFI_CFG_PERIOD_TRAFFIC: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+
+ coexConfig = coex_config;
+ r = sme_mgt_coex_config_set(priv, &coexConfig);
+ if (r) {
+ unifi_error(priv, "UNIFI_CFG_PERIOD_TRAFFIC: Set unifi_CoexInfoValue failed.\n");
+ goto out;
+ }
+
+#endif /* CSR_SUPPORT_SME && CSR_SUPPORT_WEXT */
+ break;
+ }
+ case UNIFI_CFG_UAPSD_TRAFFIC:
+ unifi_trace(priv, UDBG4, "UniFi Configure U-APSD Mask.\n");
+#if (defined CSR_SUPPORT_SME) && (defined CSR_SUPPORT_WEXT)
+ if (copy_from_user((void*)(&uchar_param), (void*)arg, sizeof(unsigned char))) {
+ unifi_error(priv, "UNIFI_CFG_UAPSD_TRAFFIC: Failed to copy from user\n");
+ r = -EFAULT;
+ goto out;
+ }
+ unifi_trace(priv, UDBG4, "New U-APSD Mask: 0x%x\n", uchar_param);
+#endif /* CSR_SUPPORT_SME && CSR_SUPPORT_WEXT */
+ break;
+
+#ifndef UNIFI_DISABLE_COREDUMP
+ case UNIFI_COREDUMP_GET_REG:
+ unifi_trace(priv, UDBG4, "Mini-coredump data request\n");
+ {
+ unifiio_coredump_req_t dump_req; /* Public OS layer structure */
+ unifi_coredump_req_t priv_req; /* Private HIP structure */
+
+ if (copy_from_user((void*)(&dump_req), (void*)arg, sizeof(dump_req))) {
+ r = -EFAULT;
+ goto out;
+ }
+ memset(&priv_req, 0, sizeof(priv_req));
+ priv_req.index = dump_req.index;
+ priv_req.offset = dump_req.offset;
+
+ /* Convert OS-layer's XAP memory space ID to HIP's ID in case they differ */
+ switch (dump_req.space) {
+ case UNIFIIO_COREDUMP_MAC_REG: priv_req.space = UNIFI_COREDUMP_MAC_REG; break;
+ case UNIFIIO_COREDUMP_PHY_REG: priv_req.space = UNIFI_COREDUMP_PHY_REG; break;
+ case UNIFIIO_COREDUMP_SH_DMEM: priv_req.space = UNIFI_COREDUMP_SH_DMEM; break;
+ case UNIFIIO_COREDUMP_MAC_DMEM: priv_req.space = UNIFI_COREDUMP_MAC_DMEM; break;
+ case UNIFIIO_COREDUMP_PHY_DMEM: priv_req.space = UNIFI_COREDUMP_PHY_DMEM; break;
+ case UNIFIIO_COREDUMP_TRIGGER_MAGIC: priv_req.space = UNIFI_COREDUMP_TRIGGER_MAGIC; break;
+ default:
+ r = -EINVAL;
+ goto out;
+ }
+
+ if (priv_req.space == UNIFI_COREDUMP_TRIGGER_MAGIC) {
+ /* Force a coredump grab now */
+ unifi_trace(priv, UDBG2, "UNIFI_COREDUMP_GET_REG: Force capture\n");
+ csrResult = unifi_coredump_capture(priv->card, &priv_req);
+ r = CsrHipResultToStatus(csrResult);
+ unifi_trace(priv, UDBG5, "UNIFI_COREDUMP_GET_REG: status %d\n", r);
+ } else {
+ /* Retrieve the appropriate register entry */
+ csrResult = unifi_coredump_get_value(priv->card, &priv_req);
+ r = CsrHipResultToStatus(csrResult);
+ if (r) {
+ unifi_trace(priv, UDBG5, "UNIFI_COREDUMP_GET_REG: Status %d\n", r);
+ goto out;
+ }
+ /* Update the OS-layer structure with values returned in the private */
+ dump_req.value = priv_req.value;
+ dump_req.timestamp = priv_req.timestamp;
+ dump_req.requestor = priv_req.requestor;
+ dump_req.serial = priv_req.serial;
+ dump_req.chip_ver = priv_req.chip_ver;
+ dump_req.fw_ver = priv_req.fw_ver;
+ dump_req.drv_build = 0;
+
+ unifi_trace(priv, UDBG6,
+ "Dump: %d (seq %d): V:0x%04x (%d) @0x%02x:%04x = 0x%04x\n",
+ dump_req.index, dump_req.serial,
+ dump_req.chip_ver, dump_req.drv_build,
+ dump_req.space, dump_req.offset, dump_req.value);
+ }
+ if (copy_to_user((void*)arg, (void*)&dump_req, sizeof(dump_req))) {
+ r = -EFAULT;
+ goto out;
+ }
+ }
+ break;
+#endif
+ default:
+ r = -EINVAL;
+ }
+
+out:
+ return (long)r;
+} /* unifi_ioctl() */
+
+
+
+static unsigned int
+unifi_poll(struct file *filp, poll_table *wait)
+{
+ ul_client_t *pcli = (ul_client_t*)filp->private_data;
+ unsigned int mask = 0;
+ int ready;
+
+ func_enter();
+
+ ready = !list_empty(&pcli->udi_log);
+
+ poll_wait(filp, &pcli->udi_wq, wait);
+
+ if (ready) {
+ mask |= POLLIN | POLLRDNORM; /* readable */
+ }
+
+ func_exit();
+
+ return mask;
+} /* unifi_poll() */
+
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * udi_set_log_filter
+ *
+ * Configure the bit mask that determines which signal primitives are
+ * passed to the logging process.
+ *
+ * Arguments:
+ * pcli Pointer to the client to configure.
+ * udi_filter Pointer to a unifiio_filter_t containing instructions.
+ *
+ * Returns:
+ * None.
+ *
+ * Notes:
+ * SigGetFilterPos() returns a 32-bit value that contains an index and a
+ * mask for accessing a signal_filter array. The top 16 bits specify an
+ * index into a signal_filter, the bottom 16 bits specify a mask to
+ * apply.
+ * ---------------------------------------------------------------------------
+ */
+static void
+udi_set_log_filter(ul_client_t *pcli, unifiio_filter_t *udi_filter)
+{
+ u32 filter_pos;
+ int i;
+
+ if (udi_filter->action == UfSigFil_AllOn)
+ {
+ for (i = 0; i < SIG_FILTER_SIZE; i++) {
+ pcli->signal_filter[i] = 0xFFFF;
+ }
+ }
+ else if (udi_filter->action == UfSigFil_AllOff)
+ {
+ for (i = 0; i < SIG_FILTER_SIZE; i++) {
+ pcli->signal_filter[i] = 0;
+ }
+ }
+ else if (udi_filter->action == UfSigFil_SelectOn)
+ {
+ for (i = 0; i < udi_filter->num_sig_ids; i++) {
+ filter_pos = SigGetFilterPos(udi_filter->sig_ids[i]);
+ if (filter_pos == 0xFFFFFFFF)
+ {
+ printk(KERN_WARNING
+ "Unrecognised signal id (0x%X) specifed in logging filter\n",
+ udi_filter->sig_ids[i]);
+ } else {
+ pcli->signal_filter[filter_pos >> 16] |= (filter_pos & 0xFFFF);
+ }
+ }
+ }
+ else if (udi_filter->action == UfSigFil_SelectOff)
+ {
+ for (i = 0; i < udi_filter->num_sig_ids; i++) {
+ filter_pos = SigGetFilterPos(udi_filter->sig_ids[i]);
+ if (filter_pos == 0xFFFFFFFF)
+ {
+ printk(KERN_WARNING
+ "Unrecognised signal id (0x%X) specifed in logging filter\n",
+ udi_filter->sig_ids[i]);
+ } else {
+ pcli->signal_filter[filter_pos >> 16] &= ~(filter_pos & 0xFFFF);
+ }
+ }
+ }
+
+} /* udi_set_log_filter() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * udi_log_event
+ *
+ * Callback function to be registered as the UDI hook callback.
+ * Copies the signal content into a new udi_log_t struct and adds
+ * it to the read queue for this UDI client.
+ *
+ * Arguments:
+ * pcli A pointer to the client instance.
+ * signal Pointer to the received signal.
+ * signal_len Size of the signal structure in bytes.
+ * bulkdata Pointers to any associated bulk data.
+ * dir Direction of the signal. Zero means from host,
+ * non-zero means to host.
+ *
+ * Returns:
+ * None.
+ * ---------------------------------------------------------------------------
+ */
+void
+udi_log_event(ul_client_t *pcli,
+ const u8 *signal, int signal_len,
+ const bulk_data_param_t *bulkdata,
+ int dir)
+{
+ udi_log_t *logptr;
+ u8 *p;
+ int i;
+ int total_len;
+ udi_msg_t *msgptr;
+ u32 filter_pos;
+#ifdef OMNICLI_LINUX_EXTRA_LOG
+ static volatile unsigned int printk_cpu = UINT_MAX;
+ unsigned long long t;
+ unsigned long nanosec_rem;
+ unsigned long n_1000;
+#endif
+
+ func_enter();
+
+ /* Just a sanity check */
+ if ((signal == NULL) || (signal_len <= 0)) {
+ return;
+ }
+
+#ifdef CSR_WIFI_HIP_DEBUG_OFFLINE
+ /* When HIP offline signal logging is enabled, omnicli cannot run */
+ if (log_hip_signals)
+ {
+ /* Add timestamp */
+ if (log_hip_signals & UNIFI_LOG_HIP_SIGNALS_FILTER_TIMESTAMP)
+ {
+ int timestamp = jiffies_to_msecs(jiffies);
+ unifi_debug_log_to_buf("T:");
+ unifi_debug_log_to_buf("%04X%04X ", *(((u16*)&timestamp) + 1),
+ *(u16*)&timestamp);
+ }
+
+ /* Add signal */
+ unifi_debug_log_to_buf("S%s:%04X R:%04X D:%04X ",
+ dir ? "T" : "F",
+ *(u16*)signal,
+ *(u16*)(signal + 2),
+ *(u16*)(signal + 4));
+ unifi_debug_hex_to_buf(signal + 6, signal_len - 6);
+
+ /* Add bulk data (assume 1 bulk data per signal) */
+ if ((log_hip_signals & UNIFI_LOG_HIP_SIGNALS_FILTER_BULKDATA) &&
+ (bulkdata->d[0].data_length > 0))
+ {
+ unifi_debug_log_to_buf("\nD:");
+ unifi_debug_hex_to_buf(bulkdata->d[0].os_data_ptr, bulkdata->d[0].data_length);
+ }
+ unifi_debug_log_to_buf("\n");
+
+ return;
+ }
+#endif
+
+#ifdef CSR_NATIVE_LINUX
+ uf_native_process_udi_signal(pcli, signal, signal_len, bulkdata, dir);
+#endif
+
+ /*
+ * Apply the logging filter - only report signals that have their
+ * bit set in the filter mask.
+ */
+ filter_pos = SigGetFilterPos(GET_SIGNAL_ID(signal));
+
+ if ((filter_pos != 0xFFFFFFFF) &&
+ ((pcli->signal_filter[filter_pos >> 16] & (filter_pos & 0xFFFF)) == 0))
+ {
+ /* Signal is not wanted by client */
+ return;
+ }
+
+
+ /* Calculate the buffer we need to store signal plus bulk data */
+ total_len = signal_len;
+ for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) {
+ total_len += bulkdata->d[i].data_length;
+ }
+
+ /* Allocate log structure plus actual signal. */
+ logptr = (udi_log_t *)kmalloc(sizeof(udi_log_t) + total_len, GFP_KERNEL);
+
+ if (logptr == NULL) {
+ printk(KERN_ERR
+ "Failed to allocate %lu bytes for a UDI log record\n",
+ (long unsigned int)(sizeof(udi_log_t) + total_len));
+ return;
+ }
+
+ /* Fill in udi_log struct */
+ INIT_LIST_HEAD(&logptr->q);
+ msgptr = &logptr->msg;
+ msgptr->length = sizeof(udi_msg_t) + total_len;
+#ifdef OMNICLI_LINUX_EXTRA_LOG
+ t = cpu_clock(printk_cpu);
+ nanosec_rem = do_div(t, 1000000000);
+ n_1000 = nanosec_rem/1000;
+ msgptr->timestamp = (t <<10 ) | ((unsigned long)(n_1000 >> 10) & 0x3ff);
+#else
+ msgptr->timestamp = jiffies_to_msecs(jiffies);
+#endif
+ msgptr->direction = dir;
+ msgptr->signal_length = signal_len;
+
+ /* Copy signal and bulk data to the log */
+ p = (u8 *)(msgptr + 1);
+ memcpy(p, signal, signal_len);
+ p += signal_len;
+
+ /* Append any bulk data */
+ for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) {
+ int len = bulkdata->d[i].data_length;
+
+ /*
+ * Len here might not be the same as the length in the bulk data slot.
+ * The slot length will always be even, but len could be odd.
+ */
+ if (len > 0) {
+ if (bulkdata->d[i].os_data_ptr) {
+ memcpy(p, bulkdata->d[i].os_data_ptr, len);
+ } else {
+ memset(p, 0, len);
+ }
+ p += len;
+ }
+ }
+
+ /* Add to tail of log queue */
+ if (down_interruptible(&pcli->udi_sem)) {
+ printk(KERN_WARNING "udi_log_event_q: Failed to get udi sem\n");
+ kfree(logptr);
+ func_exit();
+ return;
+ }
+ list_add_tail(&logptr->q, &pcli->udi_log);
+ up(&pcli->udi_sem);
+
+ /* Wake any waiting user process */
+ wake_up_interruptible(&pcli->udi_wq);
+
+ func_exit();
+} /* udi_log_event() */
+
+#ifdef CSR_SME_USERSPACE
+int
+uf_sme_queue_message(unifi_priv_t *priv, u8 *buffer, int length)
+{
+ udi_log_t *logptr;
+ udi_msg_t *msgptr;
+ u8 *p;
+
+ func_enter();
+
+ /* Just a sanity check */
+ if ((buffer == NULL) || (length <= 0)) {
+ return -EINVAL;
+ }
+
+ /* Allocate log structure plus actual signal. */
+ logptr = (udi_log_t *)kmalloc(sizeof(udi_log_t) + length, GFP_ATOMIC);
+ if (logptr == NULL) {
+ unifi_error(priv, "Failed to allocate %d bytes for an SME message\n",
+ sizeof(udi_log_t) + length);
+ kfree(buffer);
+ return -ENOMEM;
+ }
+
+ /* Fill in udi_log struct */
+ INIT_LIST_HEAD(&logptr->q);
+ msgptr = &logptr->msg;
+ msgptr->length = sizeof(udi_msg_t) + length;
+ msgptr->signal_length = length;
+
+ /* Copy signal and bulk data to the log */
+ p = (u8 *)(msgptr + 1);
+ memcpy(p, buffer, length);
+
+ /* Add to tail of log queue */
+ down(&udi_mutex);
+ if (priv->sme_cli == NULL) {
+ kfree(logptr);
+ kfree(buffer);
+ up(&udi_mutex);
+ unifi_info(priv, "Message for the SME dropped, SME has gone away\n");
+ return 0;
+ }
+
+ down(&priv->sme_cli->udi_sem);
+ list_add_tail(&logptr->q, &priv->sme_cli->udi_log);
+ up(&priv->sme_cli->udi_sem);
+
+ /* Wake any waiting user process */
+ wake_up_interruptible(&priv->sme_cli->udi_wq);
+ up(&udi_mutex);
+
+ /* It is our responsibility to free the buffer allocated in build_packed_*() */
+ kfree(buffer);
+
+ func_exit();
+
+ return 0;
+
+} /* uf_sme_queue_message() */
+#endif
+
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create(_class, _parent, _devno, _priv, _fmt, _args)
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create_drvdata(_class, _parent, _devno, _priv, _fmt, _args)
+#else
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create(_class, _parent, _devno, _fmt, _args)
+#endif
+
+/*
+ ****************************************************************************
+ *
+ * Driver instantiation
+ *
+ ****************************************************************************
+ */
+static struct file_operations unifi_fops = {
+ .owner = THIS_MODULE,
+ .open = unifi_open,
+ .release = unifi_release,
+ .read = unifi_read,
+ .write = unifi_write,
+ .unlocked_ioctl = unifi_ioctl,
+ .poll = unifi_poll,
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create(_class, _parent, _devno, _priv, _fmt, _args)
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create_drvdata(_class, _parent, _devno, _priv, _fmt, _args)
+#else
+#define UF_DEVICE_CREATE(_class, _parent, _devno, _priv, _fmt, _args) \
+ device_create(_class, _parent, _devno, _fmt, _args)
+#endif
+
+static dev_t unifi_first_devno;
+static struct class *unifi_class;
+
+
+int uf_create_device_nodes(unifi_priv_t *priv, int bus_id)
+{
+ dev_t devno;
+ int r;
+
+ cdev_init(&priv->unifi_cdev, &unifi_fops);
+
+ /* cdev_init() should set the cdev owner, but it does not */
+ priv->unifi_cdev.owner = THIS_MODULE;
+
+ devno = MKDEV(MAJOR(unifi_first_devno),
+ MINOR(unifi_first_devno) + (bus_id * 2));
+ r = cdev_add(&priv->unifi_cdev, devno, 1);
+ if (r) {
+ return r;
+ }
+
+#ifdef SDIO_EXPORTS_STRUCT_DEVICE
+ if (!UF_DEVICE_CREATE(unifi_class, priv->unifi_device,
+ devno, priv, "unifi%d", bus_id)) {
+#else
+ priv->unifi_device = UF_DEVICE_CREATE(unifi_class, NULL,
+ devno, priv, "unifi%d", bus_id);
+ if (priv->unifi_device == NULL) {
+#endif /* SDIO_EXPORTS_STRUCT_DEVICE */
+
+ cdev_del(&priv->unifi_cdev);
+ return -EINVAL;
+ }
+
+ cdev_init(&priv->unifiudi_cdev, &unifi_fops);
+
+ /* cdev_init() should set the cdev owner, but it does not */
+ priv->unifiudi_cdev.owner = THIS_MODULE;
+
+ devno = MKDEV(MAJOR(unifi_first_devno),
+ MINOR(unifi_first_devno) + (bus_id * 2) + 1);
+ r = cdev_add(&priv->unifiudi_cdev, devno, 1);
+ if (r) {
+ device_destroy(unifi_class, priv->unifi_cdev.dev);
+ cdev_del(&priv->unifi_cdev);
+ return r;
+ }
+
+ if (!UF_DEVICE_CREATE(unifi_class,
+#ifdef SDIO_EXPORTS_STRUCT_DEVICE
+ priv->unifi_device,
+#else
+ NULL,
+#endif /* SDIO_EXPORTS_STRUCT_DEVICE */
+ devno, priv, "unifiudi%d", bus_id)) {
+ device_destroy(unifi_class, priv->unifi_cdev.dev);
+ cdev_del(&priv->unifiudi_cdev);
+ cdev_del(&priv->unifi_cdev);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+void uf_destroy_device_nodes(unifi_priv_t *priv)
+{
+ device_destroy(unifi_class, priv->unifiudi_cdev.dev);
+ device_destroy(unifi_class, priv->unifi_cdev.dev);
+ cdev_del(&priv->unifiudi_cdev);
+ cdev_del(&priv->unifi_cdev);
+}
+
+
+
+/*
+ * ----------------------------------------------------------------
+ * uf_create_debug_device
+ *
+ * Allocates device numbers for unifi character device nodes
+ * and creates a unifi class in sysfs
+ *
+ * Arguments:
+ * fops Pointer to the char device operations structure.
+ *
+ * Returns:
+ * 0 on success, -ve error code on error.
+ * ----------------------------------------------------------------
+ */
+static int
+uf_create_debug_device(struct file_operations *fops)
+{
+ int ret;
+
+ /* Allocate two device numbers for each device. */
+ ret = alloc_chrdev_region(&unifi_first_devno, 0, MAX_UNIFI_DEVS*2, UNIFI_NAME);
+ if (ret) {
+ unifi_error(NULL, "Failed to add alloc dev numbers: %d\n", ret);
+ return ret;
+ }
+
+ /* Create a UniFi class */
+ unifi_class = class_create(THIS_MODULE, UNIFI_NAME);
+ if (IS_ERR(unifi_class)) {
+ unifi_error(NULL, "Failed to create UniFi class\n");
+
+ /* Release device numbers */
+ unregister_chrdev_region(unifi_first_devno, MAX_UNIFI_DEVS*2);
+ unifi_first_devno = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+} /* uf_create_debug_device() */
+
+
+/*
+ * ----------------------------------------------------------------
+ * uf_remove_debug_device
+ *
+ * Destroys the unifi class and releases the allocated
+ * device numbers for unifi character device nodes.
+ *
+ * Arguments:
+ *
+ * Returns:
+ * ----------------------------------------------------------------
+ */
+static void
+uf_remove_debug_device(void)
+{
+ /* Destroy the UniFi class */
+ class_destroy(unifi_class);
+
+ /* Release device numbers */
+ unregister_chrdev_region(unifi_first_devno, MAX_UNIFI_DEVS*2);
+ unifi_first_devno = 0;
+
+} /* uf_remove_debug_device() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ *
+ * Module loading.
+ *
+ * ---------------------------------------------------------------------------
+ */
+int __init
+unifi_load(void)
+{
+ int r;
+
+ printk("UniFi SDIO Driver: %s %s %s\n",
+ CSR_WIFI_VERSION,
+ __DATE__, __TIME__);
+
+#ifdef CSR_SME_USERSPACE
+#ifdef CSR_SUPPORT_WEXT
+ printk("CSR SME with WEXT support\n");
+#else
+ printk("CSR SME no WEXT support\n");
+#endif /* CSR_SUPPORT_WEXT */
+#endif /* CSR_SME_USERSPACE */
+
+#ifdef CSR_NATIVE_LINUX
+#ifdef CSR_SUPPORT_WEXT
+#error WEXT unsupported in the native driver
+#endif
+ printk("CSR native no WEXT support\n");
+#endif
+#ifdef CSR_WIFI_SPLIT_PATCH
+ printk("Split patch support\n");
+#endif
+ printk("Kernel %d.%d.%d\n",
+ ((LINUX_VERSION_CODE) >> 16) & 0xff,
+ ((LINUX_VERSION_CODE) >> 8) & 0xff,
+ (LINUX_VERSION_CODE) & 0xff);
+ /*
+ * Instantiate the /dev/unifi* device nodes.
+ * We must do this before registering with the SDIO driver because it
+ * will immediately call the "insert" callback if the card is
+ * already present.
+ */
+ r = uf_create_debug_device(&unifi_fops);
+ if (r) {
+ return r;
+ }
+
+ /* Now register with the SDIO driver */
+ r = uf_sdio_load();
+ if (r) {
+ uf_remove_debug_device();
+ return r;
+ }
+
+ if (sdio_block_size > -1) {
+ unifi_info(NULL, "sdio_block_size %d\n", sdio_block_size);
+ }
+
+ if (sdio_byte_mode) {
+ unifi_info(NULL, "sdio_byte_mode\n");
+ }
+
+ if (disable_power_control) {
+ unifi_info(NULL, "disable_power_control\n");
+ }
+
+ if (disable_hw_reset) {
+ unifi_info(NULL, "disable_hw_reset\n");
+ }
+
+ if (enable_wol) {
+ unifi_info(NULL, "enable_wol %d\n", enable_wol);
+ }
+
+ if (run_bh_once != -1) {
+ unifi_info(NULL, "run_bh_once %d\n", run_bh_once);
+ }
+
+ return 0;
+} /* unifi_load() */
+
+
+void __exit
+unifi_unload(void)
+{
+ /* The SDIO remove hook will call unifi_disconnect(). */
+ uf_sdio_unload();
+
+ uf_remove_debug_device();
+
+} /* unifi_unload() */
+
+module_init(unifi_load);
+module_exit(unifi_unload);
+
+MODULE_DESCRIPTION("UniFi Device driver");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL and additional rights");