aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/brcm80211/brcmfmac
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/brcm80211/brcmfmac')
-rw-r--r--drivers/staging/brcm80211/brcmfmac/Kconfig15
-rw-r--r--drivers/staging/brcm80211/brcmfmac/Makefile47
-rw-r--r--drivers/staging/brcm80211/brcmfmac/README36
-rw-r--r--drivers/staging/brcm80211/brcmfmac/bcmsdh.c632
-rw-r--r--drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c658
-rw-r--r--drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c1238
-rw-r--r--drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c231
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd.h468
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_bus.h82
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_cdc.c487
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_common.c1910
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c160
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_dbg.h103
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_linux.c2929
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_linux_sched.c26
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_proto.h92
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dhd_sdio.c6103
-rw-r--r--drivers/staging/brcm80211/brcmfmac/dngl_stats.h32
-rw-r--r--drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c4229
-rw-r--r--drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h394
-rw-r--r--drivers/staging/brcm80211/brcmfmac/wl_iw.c3767
-rw-r--r--drivers/staging/brcm80211/brcmfmac/wl_iw.h149
22 files changed, 23788 insertions, 0 deletions
diff --git a/drivers/staging/brcm80211/brcmfmac/Kconfig b/drivers/staging/brcm80211/brcmfmac/Kconfig
new file mode 100644
index 000000000000..e9f3037b0876
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/Kconfig
@@ -0,0 +1,15 @@
+menuconfig BRCMFMAC
+ tristate "Broadcom fullmac wireless cards support"
+ depends on MMC
+ depends on CFG80211
+ select FW_LOADER
+ select WIRELESS_EXT
+ select WEXT_PRIV
+ ---help---
+ This module adds support for wireless adapters based on
+ Broadcom fullmac chipsets.
+ This driver uses the kernel's wireless extensions subsystem.
+ If you choose to build a module, it'll be called brcmfmac.ko. Say M if
+ unsure.
+
+
diff --git a/drivers/staging/brcm80211/brcmfmac/Makefile b/drivers/staging/brcm80211/brcmfmac/Makefile
new file mode 100644
index 000000000000..76f2d8b37e45
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/Makefile
@@ -0,0 +1,47 @@
+#
+# Makefile fragment for Broadcom 802.11n Networking Device Driver
+#
+# Copyright (c) 2010 Broadcom Corporation
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ccflags-y := \
+ -DARP_OFFLOAD_SUPPORT \
+ -DBCMDBG \
+ -DBCMLXSDMMC \
+ -DBCMPLATFORM_BUS \
+ -DBCMSDIO \
+ -DBDC \
+ -DBRCM_FULLMAC \
+ -DDHD_DEBUG \
+ -DDHD_FIRSTREAD=64 \
+ -DDHD_SCHED \
+ -DDHD_SDALIGN=64 \
+ -DEMBEDDED_PLATFORM \
+ -DMAX_HDR_READ=64 \
+ -DMMC_SDIO_ABORT \
+ -DPKT_FILTER_SUPPORT \
+ -DSHOW_EVENTS \
+ -DTOE \
+ -Idrivers/staging/brcm80211/brcmfmac \
+ -Idrivers/staging/brcm80211/include \
+ -Idrivers/staging/brcm80211/util
+
+DHDOFILES = dhd_linux.o ../util/linux_osl.o ../util/bcmutils.o dhd_common.o dhd_custom_gpio.o \
+ wl_iw.o wl_cfg80211.o ../util/siutils.o ../util/sbutils.o ../util/aiutils.o ../util/hndpmu.o ../util/bcmwifi.o dhd_sdio.o \
+ dhd_linux_sched.o dhd_cdc.o bcmsdh_sdmmc.o bcmsdh.o bcmsdh_linux.o \
+ bcmsdh_sdmmc_linux.o
+
+obj-m += brcmfmac.o
+brcmfmac-objs += $(DHDOFILES)
+
diff --git a/drivers/staging/brcm80211/brcmfmac/README b/drivers/staging/brcm80211/brcmfmac/README
new file mode 100644
index 000000000000..43601fa8b17e
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/README
@@ -0,0 +1,36 @@
+Broadcom fullmac driver
+
+This is production driver.
+
+What's here
+===========
+- Completely open source host driver, no binary object files
+- Features Broadcom's OneDriver architecture (single source base for
+ supported chips and architectures)
+- On-chip firmware loaded using standard request_firmware()
+- Support for BCM4329(SDIO)
+
+What's done
+==========
+- Integration with cfg80211 stack
+- Most of Mac functionality is performed in dongle
+- A-MPDU single stream rates
+- BCM4329: Dualband, Single stream, 20MHz channels
+
+Firmware installation
+======================
+Firmware is available from the Linux firmware repository at:
+
+ git://git.kernel.org/pub/scm/linux/kernel/git/dwmw2/linux-firmware.git
+ http://git.kernel.org/?p=linux/kernel/git/dwmw2/linux-firmware.git
+ https://git.kernel.org/?p=linux/kernel/git/dwmw2/linux-firmware.git
+
+For 4329 chip, copy brcm/bcm4329-fullmac-4-218-248-5.bin and
+bcm4329-fullmac-4-218-248-5.txt to /lib/firmware/brcm
+
+Contact Info:
+=============
+Brett Rudley brudley@broadcom.com
+Henry Ptasinski henryp@broadcom.com
+Nohee Ko noheek@broadcom.com
+
diff --git a/drivers/staging/brcm80211/brcmfmac/bcmsdh.c b/drivers/staging/brcm80211/brcmfmac/bcmsdh.c
new file mode 100644
index 000000000000..4c613da3553a
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+/* ****************** BCMSDH Interface Functions *************************** */
+
+#include <linux/types.h>
+#include <bcmdefs.h>
+#include <bcmdevs.h>
+#include <bcmendian.h>
+#include <bcmutils.h>
+#include <hndsoc.h>
+#include <siutils.h>
+#include <osl.h>
+
+#include <bcmsdh.h> /* BRCM API for SDIO
+ clients (such as wl, dhd) */
+#include <bcmsdbus.h> /* common SDIO/controller interface */
+#include <sbsdio.h> /* BRCM sdio device core */
+
+#include <sdio.h> /* sdio spec */
+
+#define SDIOH_API_ACCESS_RETRY_LIMIT 2
+const uint bcmsdh_msglevel = BCMSDH_ERROR_VAL;
+
+struct bcmsdh_info {
+ bool init_success; /* underlying driver successfully attached */
+ void *sdioh; /* handler for sdioh */
+ u32 vendevid; /* Target Vendor and Device ID on SD bus */
+ osl_t *osh;
+ bool regfail; /* Save status of last
+ reg_read/reg_write call */
+ u32 sbwad; /* Save backplane window address */
+};
+/* local copy of bcm sd handler */
+bcmsdh_info_t *l_bcmsdh;
+
+#if defined(OOB_INTR_ONLY) && defined(HW_OOB)
+extern int sdioh_enable_hw_oob_intr(void *sdioh, bool enable);
+
+void bcmsdh_enable_hw_oob_intr(bcmsdh_info_t *sdh, bool enable)
+{
+ sdioh_enable_hw_oob_intr(sdh->sdioh, enable);
+}
+#endif
+
+bcmsdh_info_t *bcmsdh_attach(osl_t *osh, void *cfghdl, void **regsva, uint irq)
+{
+ bcmsdh_info_t *bcmsdh;
+
+ bcmsdh = kzalloc(sizeof(bcmsdh_info_t), GFP_ATOMIC);
+ if (bcmsdh == NULL) {
+ BCMSDH_ERROR(("bcmsdh_attach: out of memory"));
+ return NULL;
+ }
+
+ /* save the handler locally */
+ l_bcmsdh = bcmsdh;
+
+ bcmsdh->sdioh = sdioh_attach(osh, cfghdl, irq);
+ if (!bcmsdh->sdioh) {
+ bcmsdh_detach(osh, bcmsdh);
+ return NULL;
+ }
+
+ bcmsdh->osh = osh;
+ bcmsdh->init_success = true;
+
+ *regsva = (u32 *) SI_ENUM_BASE;
+
+ /* Report the BAR, to fix if needed */
+ bcmsdh->sbwad = SI_ENUM_BASE;
+ return bcmsdh;
+}
+
+int bcmsdh_detach(osl_t *osh, void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ if (bcmsdh != NULL) {
+ if (bcmsdh->sdioh) {
+ sdioh_detach(osh, bcmsdh->sdioh);
+ bcmsdh->sdioh = NULL;
+ }
+ kfree(bcmsdh);
+ }
+
+ l_bcmsdh = NULL;
+ return 0;
+}
+
+int
+bcmsdh_iovar_op(void *sdh, const char *name,
+ void *params, int plen, void *arg, int len, bool set)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ return sdioh_iovar_op(bcmsdh->sdioh, name, params, plen, arg, len, set);
+}
+
+bool bcmsdh_intr_query(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ bool on;
+
+ ASSERT(bcmsdh);
+ status = sdioh_interrupt_query(bcmsdh->sdioh, &on);
+ if (SDIOH_API_SUCCESS(status))
+ return false;
+ else
+ return on;
+}
+
+int bcmsdh_intr_enable(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ ASSERT(bcmsdh);
+
+ status = sdioh_interrupt_set(bcmsdh->sdioh, true);
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+int bcmsdh_intr_disable(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ ASSERT(bcmsdh);
+
+ status = sdioh_interrupt_set(bcmsdh->sdioh, false);
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+int bcmsdh_intr_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ ASSERT(bcmsdh);
+
+ status = sdioh_interrupt_register(bcmsdh->sdioh, fn, argh);
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+int bcmsdh_intr_dereg(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ ASSERT(bcmsdh);
+
+ status = sdioh_interrupt_deregister(bcmsdh->sdioh);
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+#if defined(DHD_DEBUG)
+bool bcmsdh_intr_pending(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ ASSERT(sdh);
+ return sdioh_interrupt_pending(bcmsdh->sdioh);
+}
+#endif
+
+int bcmsdh_devremove_reg(void *sdh, bcmsdh_cb_fn_t fn, void *argh)
+{
+ ASSERT(sdh);
+
+ /* don't support yet */
+ return BCME_UNSUPPORTED;
+}
+
+u8 bcmsdh_cfg_read(void *sdh, uint fnc_num, u32 addr, int *err)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ s32 retry = 0;
+#endif
+ u8 data = 0;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ do {
+ if (retry) /* wait for 1 ms till bus get settled down */
+ udelay(1000);
+#endif
+ status =
+ sdioh_cfg_read(bcmsdh->sdioh, fnc_num, addr,
+ (u8 *) &data);
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ } while (!SDIOH_API_SUCCESS(status)
+ && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT));
+#endif
+ if (err)
+ *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR);
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, u8data = 0x%x\n",
+ __func__, fnc_num, addr, data));
+
+ return data;
+}
+
+void
+bcmsdh_cfg_write(void *sdh, uint fnc_num, u32 addr, u8 data, int *err)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ s32 retry = 0;
+#endif
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ do {
+ if (retry) /* wait for 1 ms till bus get settled down */
+ udelay(1000);
+#endif
+ status =
+ sdioh_cfg_write(bcmsdh->sdioh, fnc_num, addr,
+ (u8 *) &data);
+#ifdef SDIOH_API_ACCESS_RETRY_LIMIT
+ } while (!SDIOH_API_SUCCESS(status)
+ && (retry++ < SDIOH_API_ACCESS_RETRY_LIMIT));
+#endif
+ if (err)
+ *err = SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR;
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, u8data = 0x%x\n",
+ __func__, fnc_num, addr, data));
+}
+
+u32 bcmsdh_cfg_read_word(void *sdh, uint fnc_num, u32 addr, int *err)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ u32 data = 0;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+ status =
+ sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL, SDIOH_READ,
+ fnc_num, addr, &data, 4);
+
+ if (err)
+ *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR);
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, u32data = 0x%x\n",
+ __func__, fnc_num, addr, data));
+
+ return data;
+}
+
+void
+bcmsdh_cfg_write_word(void *sdh, uint fnc_num, u32 addr, u32 data,
+ int *err)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+ status =
+ sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL,
+ SDIOH_WRITE, fnc_num, addr, &data, 4);
+
+ if (err)
+ *err = (SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR);
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, u32data = 0x%x\n",
+ __func__, fnc_num, addr, data));
+}
+
+int bcmsdh_cis_read(void *sdh, uint func, u8 * cis, uint length)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+
+ u8 *tmp_buf, *tmp_ptr;
+ u8 *ptr;
+ bool ascii = func & ~0xf;
+ func &= 0x7;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+ ASSERT(cis);
+ ASSERT(length <= SBSDIO_CIS_SIZE_LIMIT);
+
+ status = sdioh_cis_read(bcmsdh->sdioh, func, cis, length);
+
+ if (ascii) {
+ /* Move binary bits to tmp and format them
+ into the provided buffer. */
+ tmp_buf = kmalloc(length, GFP_ATOMIC);
+ if (tmp_buf == NULL) {
+ BCMSDH_ERROR(("%s: out of memory\n", __func__));
+ return BCME_NOMEM;
+ }
+ bcopy(cis, tmp_buf, length);
+ for (tmp_ptr = tmp_buf, ptr = cis; ptr < (cis + length - 4);
+ tmp_ptr++) {
+ ptr += sprintf((char *)ptr, "%.2x ", *tmp_ptr & 0xff);
+ if ((((tmp_ptr - tmp_buf) + 1) & 0xf) == 0)
+ ptr += sprintf((char *)ptr, "\n");
+ }
+ kfree(tmp_buf);
+ }
+
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+static int bcmsdhsdio_set_sbaddr_window(void *sdh, u32 address)
+{
+ int err = 0;
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW,
+ (address >> 8) & SBSDIO_SBADDRLOW_MASK, &err);
+ if (!err)
+ bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID,
+ (address >> 16) & SBSDIO_SBADDRMID_MASK, &err);
+ if (!err)
+ bcmsdh_cfg_write(bcmsdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH,
+ (address >> 24) & SBSDIO_SBADDRHIGH_MASK,
+ &err);
+
+ return err;
+}
+
+u32 bcmsdh_reg_read(void *sdh, u32 addr, uint size)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ u32 word = 0;
+ uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK;
+
+ BCMSDH_INFO(("%s:fun = 1, addr = 0x%x, ", __func__, addr));
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+ if (bar0 != bcmsdh->sbwad) {
+ if (bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))
+ return 0xFFFFFFFF;
+
+ bcmsdh->sbwad = bar0;
+ }
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ if (size == 4)
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ status = sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL,
+ SDIOH_READ, SDIO_FUNC_1, addr, &word, size);
+
+ bcmsdh->regfail = !(SDIOH_API_SUCCESS(status));
+
+ BCMSDH_INFO(("u32data = 0x%x\n", word));
+
+ /* if ok, return appropriately masked word */
+ if (SDIOH_API_SUCCESS(status)) {
+ switch (size) {
+ case sizeof(u8):
+ return word & 0xff;
+ case sizeof(u16):
+ return word & 0xffff;
+ case sizeof(u32):
+ return word;
+ default:
+ bcmsdh->regfail = true;
+
+ }
+ }
+
+ /* otherwise, bad sdio access or invalid size */
+ BCMSDH_ERROR(("%s: error reading addr 0x%04x size %d\n", __func__,
+ addr, size));
+ return 0xFFFFFFFF;
+}
+
+u32 bcmsdh_reg_write(void *sdh, u32 addr, uint size, u32 data)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK;
+ int err = 0;
+
+ BCMSDH_INFO(("%s:fun = 1, addr = 0x%x, uint%ddata = 0x%x\n",
+ __func__, addr, size * 8, data));
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ ASSERT(bcmsdh->init_success);
+
+ if (bar0 != bcmsdh->sbwad) {
+ err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0);
+ if (err)
+ return err;
+
+ bcmsdh->sbwad = bar0;
+ }
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ if (size == 4)
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+ status =
+ sdioh_request_word(bcmsdh->sdioh, SDIOH_CMD_TYPE_NORMAL,
+ SDIOH_WRITE, SDIO_FUNC_1, addr, &data, size);
+ bcmsdh->regfail = !(SDIOH_API_SUCCESS(status));
+
+ if (SDIOH_API_SUCCESS(status))
+ return 0;
+
+ BCMSDH_ERROR(("%s: error writing 0x%08x to addr 0x%04x size %d\n",
+ __func__, data, addr, size));
+ return 0xFFFFFFFF;
+}
+
+bool bcmsdh_regfail(void *sdh)
+{
+ return ((bcmsdh_info_t *) sdh)->regfail;
+}
+
+int
+bcmsdh_recv_buf(void *sdh, u32 addr, uint fn, uint flags,
+ u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ uint incr_fix;
+ uint width;
+ uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK;
+ int err = 0;
+
+ ASSERT(bcmsdh);
+ ASSERT(bcmsdh->init_success);
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, size = %d\n",
+ __func__, fn, addr, nbytes));
+
+ /* Async not implemented yet */
+ ASSERT(!(flags & SDIO_REQ_ASYNC));
+ if (flags & SDIO_REQ_ASYNC)
+ return BCME_UNSUPPORTED;
+
+ if (bar0 != bcmsdh->sbwad) {
+ err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0);
+ if (err)
+ return err;
+
+ bcmsdh->sbwad = bar0;
+ }
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+
+ incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC;
+ width = (flags & SDIO_REQ_4BYTE) ? 4 : 2;
+ if (width == 4)
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ status = sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, incr_fix,
+ SDIOH_READ, fn, addr, width, nbytes, buf,
+ pkt);
+
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_SDIO_ERROR;
+}
+
+int
+bcmsdh_send_buf(void *sdh, u32 addr, uint fn, uint flags,
+ u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+ uint incr_fix;
+ uint width;
+ uint bar0 = addr & ~SBSDIO_SB_OFT_ADDR_MASK;
+ int err = 0;
+
+ ASSERT(bcmsdh);
+ ASSERT(bcmsdh->init_success);
+
+ BCMSDH_INFO(("%s:fun = %d, addr = 0x%x, size = %d\n",
+ __func__, fn, addr, nbytes));
+
+ /* Async not implemented yet */
+ ASSERT(!(flags & SDIO_REQ_ASYNC));
+ if (flags & SDIO_REQ_ASYNC)
+ return BCME_UNSUPPORTED;
+
+ if (bar0 != bcmsdh->sbwad) {
+ err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0);
+ if (err)
+ return err;
+
+ bcmsdh->sbwad = bar0;
+ }
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+
+ incr_fix = (flags & SDIO_REQ_FIXED) ? SDIOH_DATA_FIX : SDIOH_DATA_INC;
+ width = (flags & SDIO_REQ_4BYTE) ? 4 : 2;
+ if (width == 4)
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ status = sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, incr_fix,
+ SDIOH_WRITE, fn, addr, width, nbytes, buf,
+ pkt);
+
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+int bcmsdh_rwdata(void *sdh, uint rw, u32 addr, u8 *buf, uint nbytes)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ SDIOH_API_RC status;
+
+ ASSERT(bcmsdh);
+ ASSERT(bcmsdh->init_success);
+ ASSERT((addr & SBSDIO_SBWINDOW_MASK) == 0);
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ status =
+ sdioh_request_buffer(bcmsdh->sdioh, SDIOH_DATA_PIO, SDIOH_DATA_INC,
+ (rw ? SDIOH_WRITE : SDIOH_READ), SDIO_FUNC_1,
+ addr, 4, nbytes, buf, NULL);
+
+ return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR;
+}
+
+int bcmsdh_abort(void *sdh, uint fn)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ return sdioh_abort(bcmsdh->sdioh, fn);
+}
+
+int bcmsdh_start(void *sdh, int stage)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ return sdioh_start(bcmsdh->sdioh, stage);
+}
+
+int bcmsdh_stop(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ return sdioh_stop(bcmsdh->sdioh);
+}
+
+int bcmsdh_query_device(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+ bcmsdh->vendevid = (VENDOR_BROADCOM << 16) | 0;
+ return bcmsdh->vendevid;
+}
+
+uint bcmsdh_query_iofnum(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ return sdioh_query_iofnum(bcmsdh->sdioh);
+}
+
+int bcmsdh_reset(bcmsdh_info_t *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ return sdioh_sdio_reset(bcmsdh->sdioh);
+}
+
+void *bcmsdh_get_sdioh(bcmsdh_info_t *sdh)
+{
+ ASSERT(sdh);
+ return sdh->sdioh;
+}
+
+/* Function to pass device-status bits to DHD. */
+u32 bcmsdh_get_dstatus(void *sdh)
+{
+ return 0;
+}
+
+u32 bcmsdh_cur_sbwad(void *sdh)
+{
+ bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh;
+
+ if (!bcmsdh)
+ bcmsdh = l_bcmsdh;
+
+ return bcmsdh->sbwad;
+}
+
+void bcmsdh_chipinfo(void *sdh, u32 chip, u32 chiprev)
+{
+ return;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c b/drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c
new file mode 100644
index 000000000000..9028cd01d9d0
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * @file bcmsdh_linux.c
+ */
+
+#define __UNDEF_NO_VERSION__
+
+#include <linuxver.h>
+
+#include <linux/pci.h>
+#include <linux/completion.h>
+
+#include <osl.h>
+#include <pcicfg.h>
+#include <bcmdefs.h>
+#include <bcmdevs.h>
+
+#if defined(OOB_INTR_ONLY)
+#include <linux/irq.h>
+extern void dhdsdio_isr(void *args);
+#include <bcmutils.h>
+#include <dngl_stats.h>
+#include <dhd.h>
+#endif /* defined(OOB_INTR_ONLY) */
+#if defined(CONFIG_MACH_SANDGATE2G) || defined(CONFIG_MACH_LOGICPD_PXA270)
+#if !defined(BCMPLATFORM_BUS)
+#define BCMPLATFORM_BUS
+#endif /* !defined(BCMPLATFORM_BUS) */
+
+#include <linux/platform_device.h>
+#endif /* CONFIG_MACH_SANDGATE2G */
+
+/**
+ * SDIO Host Controller info
+ */
+typedef struct bcmsdh_hc bcmsdh_hc_t;
+
+struct bcmsdh_hc {
+ bcmsdh_hc_t *next;
+#ifdef BCMPLATFORM_BUS
+ struct device *dev; /* platform device handle */
+#else
+ struct pci_dev *dev; /* pci device handle */
+#endif /* BCMPLATFORM_BUS */
+ osl_t *osh;
+ void *regs; /* SDIO Host Controller address */
+ bcmsdh_info_t *sdh; /* SDIO Host Controller handle */
+ void *ch;
+ unsigned int oob_irq;
+ unsigned long oob_flags; /* OOB Host specifiction
+ as edge and etc */
+ bool oob_irq_registered;
+#if defined(OOB_INTR_ONLY)
+ spinlock_t irq_lock;
+#endif
+};
+static bcmsdh_hc_t *sdhcinfo;
+
+/* driver info, initialized when bcmsdh_register is called */
+static bcmsdh_driver_t drvinfo = { NULL, NULL };
+
+/* debugging macros */
+#define SDLX_MSG(x)
+
+/**
+ * Checks to see if vendor and device IDs match a supported SDIO Host Controller.
+ */
+bool bcmsdh_chipmatch(u16 vendor, u16 device)
+{
+ /* Add other vendors and devices as required */
+
+#ifdef BCMSDIOH_STD
+ /* Check for Arasan host controller */
+ if (vendor == VENDOR_SI_IMAGE)
+ return true;
+
+ /* Check for BRCM 27XX Standard host controller */
+ if (device == BCM27XX_SDIOH_ID && vendor == VENDOR_BROADCOM)
+ return true;
+
+ /* Check for BRCM Standard host controller */
+ if (device == SDIOH_FPGA_ID && vendor == VENDOR_BROADCOM)
+ return true;
+
+ /* Check for TI PCIxx21 Standard host controller */
+ if (device == PCIXX21_SDIOH_ID && vendor == VENDOR_TI)
+ return true;
+
+ if (device == PCIXX21_SDIOH0_ID && vendor == VENDOR_TI)
+ return true;
+
+ /* Ricoh R5C822 Standard SDIO Host */
+ if (device == R5C822_SDIOH_ID && vendor == VENDOR_RICOH)
+ return true;
+
+ /* JMicron Standard SDIO Host */
+ if (device == JMICRON_SDIOH_ID && vendor == VENDOR_JMICRON)
+ return true;
+#endif /* BCMSDIOH_STD */
+#ifdef BCMSDIOH_SPI
+ /* This is the PciSpiHost. */
+ if (device == SPIH_FPGA_ID && vendor == VENDOR_BROADCOM) {
+ printf("Found PCI SPI Host Controller\n");
+ return true;
+ }
+#endif /* BCMSDIOH_SPI */
+
+ return false;
+}
+
+#if defined(BCMPLATFORM_BUS)
+#if defined(BCMLXSDMMC)
+/* forward declarations */
+int bcmsdh_probe(struct device *dev);
+EXPORT_SYMBOL(bcmsdh_probe);
+
+int bcmsdh_remove(struct device *dev);
+EXPORT_SYMBOL(bcmsdh_remove);
+
+#else
+/* forward declarations */
+static int __devinit bcmsdh_probe(struct device *dev);
+static int __devexit bcmsdh_remove(struct device *dev);
+#endif /* BCMLXSDMMC */
+
+#ifndef BCMLXSDMMC
+static struct device_driver bcmsdh_driver = {
+ .name = "pxa2xx-mci",
+ .bus = &platform_bus_type,
+ .probe = bcmsdh_probe,
+ .remove = bcmsdh_remove,
+ .suspend = NULL,
+ .resume = NULL,
+};
+#endif /* BCMLXSDMMC */
+
+#ifndef BCMLXSDMMC
+static
+#endif /* BCMLXSDMMC */
+int bcmsdh_probe(struct device *dev)
+{
+ osl_t *osh = NULL;
+ bcmsdh_hc_t *sdhc = NULL;
+ unsigned long regs = 0;
+ bcmsdh_info_t *sdh = NULL;
+#if !defined(BCMLXSDMMC) && defined(BCMPLATFORM_BUS)
+ struct platform_device *pdev;
+ struct resource *r;
+#endif /* BCMLXSDMMC */
+ int irq = 0;
+ u32 vendevid;
+ unsigned long irq_flags = 0;
+
+#if !defined(BCMLXSDMMC) && defined(BCMPLATFORM_BUS)
+ pdev = to_platform_device(dev);
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!r || irq == NO_IRQ)
+ return -ENXIO;
+#endif /* BCMLXSDMMC */
+
+#if defined(OOB_INTR_ONLY)
+#ifdef HW_OOB
+ irq_flags =
+ IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL |
+ IORESOURCE_IRQ_SHAREABLE;
+#else
+ irq_flags = IRQF_TRIGGER_FALLING;
+#endif /* HW_OOB */
+ irq = dhd_customer_oob_irq_map(&irq_flags);
+ if (irq < 0) {
+ SDLX_MSG(("%s: Host irq is not defined\n", __func__));
+ return 1;
+ }
+#endif /* defined(OOB_INTR_ONLY) */
+ /* allocate SDIO Host Controller state info */
+ osh = osl_attach(dev, PCI_BUS, false);
+ if (!osh) {
+ SDLX_MSG(("%s: osl_attach failed\n", __func__));
+ goto err;
+ }
+ sdhc = kzalloc(sizeof(bcmsdh_hc_t), GFP_ATOMIC);
+ if (!sdhc) {
+ SDLX_MSG(("%s: out of memory\n", __func__));
+ goto err;
+ }
+ sdhc->osh = osh;
+
+ sdhc->dev = (void *)dev;
+
+#ifdef BCMLXSDMMC
+ sdh = bcmsdh_attach(osh, (void *)0, (void **)&regs, irq);
+ if (!sdh) {
+ SDLX_MSG(("%s: bcmsdh_attach failed\n", __func__));
+ goto err;
+ }
+#else
+ sdh = bcmsdh_attach(osh, (void *)r->start, (void **)&regs, irq);
+ if (!sdh) {
+ SDLX_MSG(("%s: bcmsdh_attach failed\n", __func__));
+ goto err;
+ }
+#endif /* BCMLXSDMMC */
+ sdhc->sdh = sdh;
+ sdhc->oob_irq = irq;
+ sdhc->oob_flags = irq_flags;
+ sdhc->oob_irq_registered = false; /* to make sure.. */
+#if defined(OOB_INTR_ONLY)
+ spin_lock_init(&sdhc->irq_lock);
+#endif
+
+ /* chain SDIO Host Controller info together */
+ sdhc->next = sdhcinfo;
+ sdhcinfo = sdhc;
+ /* Read the vendor/device ID from the CIS */
+ vendevid = bcmsdh_query_device(sdh);
+
+ /* try to attach to the target device */
+ sdhc->ch = drvinfo.attach((vendevid >> 16), (vendevid & 0xFFFF),
+ 0, 0, 0, 0, (void *)regs, NULL, sdh);
+ if (!sdhc->ch) {
+ SDLX_MSG(("%s: device attach failed\n", __func__));
+ goto err;
+ }
+
+ return 0;
+
+ /* error handling */
+err:
+ if (sdhc) {
+ if (sdhc->sdh)
+ bcmsdh_detach(sdhc->osh, sdhc->sdh);
+ kfree(sdhc);
+ }
+ if (osh)
+ osl_detach(osh);
+ return -ENODEV;
+}
+
+#ifndef BCMLXSDMMC
+static
+#endif /* BCMLXSDMMC */
+int bcmsdh_remove(struct device *dev)
+{
+ bcmsdh_hc_t *sdhc, *prev;
+ osl_t *osh;
+
+ sdhc = sdhcinfo;
+ drvinfo.detach(sdhc->ch);
+ bcmsdh_detach(sdhc->osh, sdhc->sdh);
+ /* find the SDIO Host Controller state for this pdev
+ and take it out from the list */
+ for (sdhc = sdhcinfo, prev = NULL; sdhc; sdhc = sdhc->next) {
+ if (sdhc->dev == (void *)dev) {
+ if (prev)
+ prev->next = sdhc->next;
+ else
+ sdhcinfo = NULL;
+ break;
+ }
+ prev = sdhc;
+ }
+ if (!sdhc) {
+ SDLX_MSG(("%s: failed\n", __func__));
+ return 0;
+ }
+
+ /* release SDIO Host Controller info */
+ osh = sdhc->osh;
+ kfree(sdhc);
+ osl_detach(osh);
+
+#if !defined(BCMLXSDMMC)
+ dev_set_drvdata(dev, NULL);
+#endif /* !defined(BCMLXSDMMC) */
+
+ return 0;
+}
+
+#else /* BCMPLATFORM_BUS */
+
+#if !defined(BCMLXSDMMC)
+/* forward declarations for PCI probe and remove functions. */
+static int __devinit bcmsdh_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent);
+static void __devexit bcmsdh_pci_remove(struct pci_dev *pdev);
+
+/**
+ * pci id table
+ */
+static struct pci_device_id bcmsdh_pci_devid[] __devinitdata = {
+{
+ .vendor = PCI_ANY_ID,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .class = 0,
+ .class_mask = 0,
+ .driver_data = 0,
+},
+{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, bcmsdh_pci_devid);
+
+/**
+ * SDIO Host Controller pci driver info
+ */
+static struct pci_driver bcmsdh_pci_driver = {
+ .node = {},
+ .name = "bcmsdh",
+ .id_table = bcmsdh_pci_devid,
+ .probe = bcmsdh_pci_probe,
+ .remove = bcmsdh_pci_remove,
+ .suspend = NULL,
+ .resume = NULL,
+};
+
+extern uint sd_pci_slot; /* Force detection to a particular PCI */
+ /* slot only . Allows for having multiple */
+ /* WL devices at once in a PC */
+ /* Only one instance of dhd will be */
+ /* usable at a time */
+ /* Upper word is bus number, */
+ /* lower word is slot number */
+ /* Default value of 0xFFFFffff turns this */
+ /* off */
+module_param(sd_pci_slot, uint, 0);
+
+/**
+ * Detect supported SDIO Host Controller and attach if found.
+ *
+ * Determine if the device described by pdev is a supported SDIO Host
+ * Controller. If so, attach to it and attach to the target device.
+ */
+static int __devinit
+bcmsdh_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ osl_t *osh = NULL;
+ bcmsdh_hc_t *sdhc = NULL;
+ unsigned long regs;
+ bcmsdh_info_t *sdh = NULL;
+ int rc;
+
+ if (sd_pci_slot != 0xFFFFffff) {
+ if (pdev->bus->number != (sd_pci_slot >> 16) ||
+ PCI_SLOT(pdev->devfn) != (sd_pci_slot & 0xffff)) {
+ SDLX_MSG(("%s: %s: bus %X, slot %X, vend %X, dev %X\n",
+ __func__,
+ bcmsdh_chipmatch(pdev->vendor, pdev->device) ?
+ "Found compatible SDIOHC" :
+ "Probing unknown device",
+ pdev->bus->number, PCI_SLOT(pdev->devfn),
+ pdev->vendor, pdev->device));
+ return -ENODEV;
+ }
+ SDLX_MSG(("%s: %s: bus %X, slot %X, vendor %X, device %X "
+ "(good PCI location)\n", __func__,
+ bcmsdh_chipmatch(pdev->vendor, pdev->device) ?
+ "Using compatible SDIOHC" : "WARNING, forced use "
+ "of unkown device",
+ pdev->bus->number, PCI_SLOT(pdev->devfn), pdev->vendor,
+ pdev->device));
+ }
+
+ if ((pdev->vendor == VENDOR_TI)
+ && ((pdev->device == PCIXX21_FLASHMEDIA_ID)
+ || (pdev->device == PCIXX21_FLASHMEDIA0_ID))) {
+ u32 config_reg;
+
+ SDLX_MSG(("%s: Disabling TI FlashMedia Controller.\n",
+ __func__));
+ osh = osl_attach(pdev, PCI_BUS, false);
+ if (!osh) {
+ SDLX_MSG(("%s: osl_attach failed\n", __func__));
+ goto err;
+ }
+
+ config_reg = OSL_PCI_READ_CONFIG(osh, 0x4c, 4);
+
+ /*
+ * Set MMC_SD_DIS bit in FlashMedia Controller.
+ * Disbling the SD/MMC Controller in the FlashMedia Controller
+ * allows the Standard SD Host Controller to take over control
+ * of the SD Slot.
+ */
+ config_reg |= 0x02;
+ OSL_PCI_WRITE_CONFIG(osh, 0x4c, 4, config_reg);
+ osl_detach(osh);
+ }
+ /* match this pci device with what we support */
+ /* we can't solely rely on this to believe it is
+ our SDIO Host Controller! */
+ if (!bcmsdh_chipmatch(pdev->vendor, pdev->device))
+ return -ENODEV;
+
+ /* this is a pci device we might support */
+ SDLX_MSG(("%s: Found possible SDIO Host Controller: "
+ "bus %d slot %d func %d irq %d\n", __func__,
+ pdev->bus->number, PCI_SLOT(pdev->devfn),
+ PCI_FUNC(pdev->devfn), pdev->irq));
+
+ /* use bcmsdh_query_device() to get the vendor ID of the target device
+ * so it will eventually appear in the Broadcom string on the console
+ */
+
+ /* allocate SDIO Host Controller state info */
+ osh = osl_attach(pdev, PCI_BUS, false);
+ if (!osh) {
+ SDLX_MSG(("%s: osl_attach failed\n", __func__));
+ goto err;
+ }
+ sdhc = kzalloc(sizeof(bcmsdh_hc_t), GFP_ATOMIC);
+ if (!sdhc) {
+ SDLX_MSG(("%s: out of memory\n", __func__));
+ goto err;
+ }
+ sdhc->osh = osh;
+
+ sdhc->dev = pdev;
+
+ /* map to address where host can access */
+ pci_set_master(pdev);
+ rc = pci_enable_device(pdev);
+ if (rc) {
+ SDLX_MSG(("%s: Cannot enable PCI device\n", __func__));
+ goto err;
+ }
+ sdh = bcmsdh_attach(osh, (void *)(unsigned long)pci_resource_start(pdev, 0),
+ (void **)&regs, pdev->irq);
+ if (!sdh) {
+ SDLX_MSG(("%s: bcmsdh_attach failed\n", __func__));
+ goto err;
+ }
+
+ sdhc->sdh = sdh;
+
+ /* try to attach to the target device */
+ sdhc->ch = drvinfo.attach(VENDOR_BROADCOM, /* pdev->vendor, */
+ bcmsdh_query_device(sdh) & 0xFFFF, 0, 0, 0, 0,
+ (void *)regs, NULL, sdh);
+ if (!sdhc->ch) {
+ SDLX_MSG(("%s: device attach failed\n", __func__));
+ goto err;
+ }
+
+ /* chain SDIO Host Controller info together */
+ sdhc->next = sdhcinfo;
+ sdhcinfo = sdhc;
+
+ return 0;
+
+ /* error handling */
+err:
+ if (sdhc->sdh)
+ bcmsdh_detach(sdhc->osh, sdhc->sdh);
+ if (sdhc)
+ kfree(sdhc);
+ if (osh)
+ osl_detach(osh);
+ return -ENODEV;
+}
+
+/**
+ * Detach from target devices and SDIO Host Controller
+ */
+static void __devexit bcmsdh_pci_remove(struct pci_dev *pdev)
+{
+ bcmsdh_hc_t *sdhc, *prev;
+ osl_t *osh;
+
+ /* find the SDIO Host Controller state for this
+ pdev and take it out from the list */
+ for (sdhc = sdhcinfo, prev = NULL; sdhc; sdhc = sdhc->next) {
+ if (sdhc->dev == pdev) {
+ if (prev)
+ prev->next = sdhc->next;
+ else
+ sdhcinfo = NULL;
+ break;
+ }
+ prev = sdhc;
+ }
+ if (!sdhc)
+ return;
+
+ drvinfo.detach(sdhc->ch);
+
+ bcmsdh_detach(sdhc->osh, sdhc->sdh);
+
+ /* release SDIO Host Controller info */
+ osh = sdhc->osh;
+ kfree(sdhc);
+ osl_detach(osh);
+}
+#endif /* BCMLXSDMMC */
+#endif /* BCMPLATFORM_BUS */
+
+extern int sdio_function_init(void);
+
+int bcmsdh_register(bcmsdh_driver_t *driver)
+{
+ int error = 0;
+
+ drvinfo = *driver;
+
+#if defined(BCMPLATFORM_BUS)
+#if defined(BCMLXSDMMC)
+ SDLX_MSG(("Linux Kernel SDIO/MMC Driver\n"));
+ error = sdio_function_init();
+#else
+ SDLX_MSG(("Intel PXA270 SDIO Driver\n"));
+ error = driver_register(&bcmsdh_driver);
+#endif /* defined(BCMLXSDMMC) */
+ return error;
+#endif /* defined(BCMPLATFORM_BUS) */
+
+#if !defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC)
+ error = pci_register_driver(&bcmsdh_pci_driver);
+ if (!error)
+ return 0;
+
+ SDLX_MSG(("%s: pci_register_driver failed 0x%x\n", __func__, error));
+#endif /* BCMPLATFORM_BUS */
+
+ return error;
+}
+
+extern void sdio_function_cleanup(void);
+
+void bcmsdh_unregister(void)
+{
+#if defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC)
+ driver_unregister(&bcmsdh_driver);
+#endif
+#if defined(BCMLXSDMMC)
+ sdio_function_cleanup();
+#endif /* BCMLXSDMMC */
+#if !defined(BCMPLATFORM_BUS) && !defined(BCMLXSDMMC)
+ pci_unregister_driver(&bcmsdh_pci_driver);
+#endif /* BCMPLATFORM_BUS */
+}
+
+#if defined(OOB_INTR_ONLY)
+void bcmsdh_oob_intr_set(bool enable)
+{
+ static bool curstate = 1;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sdhcinfo->irq_lock, flags);
+ if (curstate != enable) {
+ if (enable)
+ enable_irq(sdhcinfo->oob_irq);
+ else
+ disable_irq_nosync(sdhcinfo->oob_irq);
+ curstate = enable;
+ }
+ spin_unlock_irqrestore(&sdhcinfo->irq_lock, flags);
+}
+
+static irqreturn_t wlan_oob_irq(int irq, void *dev_id)
+{
+ dhd_pub_t *dhdp;
+
+ dhdp = (dhd_pub_t *) dev_get_drvdata(sdhcinfo->dev);
+
+ bcmsdh_oob_intr_set(0);
+
+ if (dhdp == NULL) {
+ SDLX_MSG(("Out of band GPIO interrupt fired way too early\n"));
+ return IRQ_HANDLED;
+ }
+
+ WAKE_LOCK_TIMEOUT(dhdp, WAKE_LOCK_TMOUT, 25);
+
+ dhdsdio_isr((void *)dhdp->bus);
+
+ return IRQ_HANDLED;
+}
+
+int bcmsdh_register_oob_intr(void *dhdp)
+{
+ int error = 0;
+
+ SDLX_MSG(("%s Enter\n", __func__));
+
+ sdhcinfo->oob_flags =
+ IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL |
+ IORESOURCE_IRQ_SHAREABLE;
+ dev_set_drvdata(sdhcinfo->dev, dhdp);
+
+ if (!sdhcinfo->oob_irq_registered) {
+ SDLX_MSG(("%s IRQ=%d Type=%X\n", __func__,
+ (int)sdhcinfo->oob_irq, (int)sdhcinfo->oob_flags));
+ /* Refer to customer Host IRQ docs about
+ proper irqflags definition */
+ error =
+ request_irq(sdhcinfo->oob_irq, wlan_oob_irq,
+ sdhcinfo->oob_flags, "bcmsdh_sdmmc", NULL);
+ if (error)
+ return -ENODEV;
+
+ set_irq_wake(sdhcinfo->oob_irq, 1);
+ sdhcinfo->oob_irq_registered = true;
+ }
+
+ return 0;
+}
+
+void bcmsdh_unregister_oob_intr(void)
+{
+ SDLX_MSG(("%s: Enter\n", __func__));
+
+ set_irq_wake(sdhcinfo->oob_irq, 0);
+ disable_irq(sdhcinfo->oob_irq); /* just in case.. */
+ free_irq(sdhcinfo->oob_irq, NULL);
+ sdhcinfo->oob_irq_registered = false;
+}
+#endif /* defined(OOB_INTR_ONLY) */
+/* Module parameters specific to each host-controller driver */
+
+extern uint sd_msglevel; /* Debug message level */
+module_param(sd_msglevel, uint, 0);
+
+extern uint sd_power; /* 0 = SD Power OFF,
+ 1 = SD Power ON. */
+module_param(sd_power, uint, 0);
+
+extern uint sd_clock; /* SD Clock Control, 0 = SD Clock OFF,
+ 1 = SD Clock ON */
+module_param(sd_clock, uint, 0);
+
+extern uint sd_divisor; /* Divisor (-1 means external clock) */
+module_param(sd_divisor, uint, 0);
+
+extern uint sd_sdmode; /* Default is SD4, 0=SPI, 1=SD1, 2=SD4 */
+module_param(sd_sdmode, uint, 0);
+
+extern uint sd_hiok; /* Ok to use hi-speed mode */
+module_param(sd_hiok, uint, 0);
+
+extern uint sd_f2_blocksize;
+module_param(sd_f2_blocksize, int, 0);
diff --git a/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c
new file mode 100644
index 000000000000..f6c9c4541813
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c
@@ -0,0 +1,1238 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/types.h>
+#include <bcmdefs.h>
+#include <bcmdevs.h>
+#include <bcmendian.h>
+#include <bcmutils.h>
+#include <osl.h>
+#include <sdio.h> /* SDIO Device and Protocol Specs */
+#include <sdioh.h> /* SDIO Host Controller Specification */
+#include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */
+#include <sdiovar.h> /* ioctl/iovars */
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+
+#if defined(CONFIG_PM_SLEEP)
+#include <linux/suspend.h>
+extern volatile bool dhd_mmc_suspend;
+#endif
+#include "bcmsdh_sdmmc.h"
+
+extern int sdio_function_init(void);
+extern void sdio_function_cleanup(void);
+
+#if !defined(OOB_INTR_ONLY)
+static void IRQHandler(struct sdio_func *func);
+static void IRQHandlerF2(struct sdio_func *func);
+#endif /* !defined(OOB_INTR_ONLY) */
+static int sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, u32 regaddr);
+extern int sdio_reset_comm(struct mmc_card *card);
+
+extern PBCMSDH_SDMMC_INSTANCE gInstance;
+
+uint sd_sdmode = SDIOH_MODE_SD4; /* Use SD4 mode by default */
+uint sd_f2_blocksize = 512; /* Default blocksize */
+
+uint sd_divisor = 2; /* Default 48MHz/2 = 24MHz */
+
+uint sd_power = 1; /* Default to SD Slot powered ON */
+uint sd_clock = 1; /* Default to SD Clock turned ON */
+uint sd_hiok = false; /* Don't use hi-speed mode by default */
+uint sd_msglevel = 0x01;
+uint sd_use_dma = true;
+DHD_PM_RESUME_WAIT_INIT(sdioh_request_byte_wait);
+DHD_PM_RESUME_WAIT_INIT(sdioh_request_word_wait);
+DHD_PM_RESUME_WAIT_INIT(sdioh_request_packet_wait);
+DHD_PM_RESUME_WAIT_INIT(sdioh_request_buffer_wait);
+
+#define DMA_ALIGN_MASK 0x03
+
+int sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, u32 regaddr,
+ int regsize, u32 *data);
+
+static int sdioh_sdmmc_card_enablefuncs(sdioh_info_t *sd)
+{
+ int err_ret;
+ u32 fbraddr;
+ u8 func;
+
+ sd_trace(("%s\n", __func__));
+
+ /* Get the Card's common CIS address */
+ sd->com_cis_ptr = sdioh_sdmmc_get_cisaddr(sd, SDIOD_CCCR_CISPTR_0);
+ sd->func_cis_ptr[0] = sd->com_cis_ptr;
+ sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __func__,
+ sd->com_cis_ptr));
+
+ /* Get the Card's function CIS (for each function) */
+ for (fbraddr = SDIOD_FBR_STARTADDR, func = 1;
+ func <= sd->num_funcs; func++, fbraddr += SDIOD_FBR_SIZE) {
+ sd->func_cis_ptr[func] =
+ sdioh_sdmmc_get_cisaddr(sd, SDIOD_FBR_CISPTR_0 + fbraddr);
+ sd_info(("%s: Function %d CIS Ptr = 0x%x\n", __func__, func,
+ sd->func_cis_ptr[func]));
+ }
+
+ sd->func_cis_ptr[0] = sd->com_cis_ptr;
+ sd_info(("%s: Card's Common CIS Ptr = 0x%x\n", __func__,
+ sd->com_cis_ptr));
+
+ /* Enable Function 1 */
+ sdio_claim_host(gInstance->func[1]);
+ err_ret = sdio_enable_func(gInstance->func[1]);
+ sdio_release_host(gInstance->func[1]);
+ if (err_ret) {
+ sd_err(("bcmsdh_sdmmc: Failed to enable F1 Err: 0x%08x",
+ err_ret));
+ }
+
+ return false;
+}
+
+/*
+ * Public entry points & extern's
+ */
+extern sdioh_info_t *sdioh_attach(osl_t *osh, void *bar0, uint irq)
+{
+ sdioh_info_t *sd;
+ int err_ret;
+
+ sd_trace(("%s\n", __func__));
+
+ if (gInstance == NULL) {
+ sd_err(("%s: SDIO Device not present\n", __func__));
+ return NULL;
+ }
+
+ sd = kzalloc(sizeof(sdioh_info_t), GFP_ATOMIC);
+ if (sd == NULL) {
+ sd_err(("sdioh_attach: out of memory\n"));
+ return NULL;
+ }
+ sd->osh = osh;
+ if (sdioh_sdmmc_osinit(sd) != 0) {
+ sd_err(("%s:sdioh_sdmmc_osinit() failed\n", __func__));
+ kfree(sd);
+ return NULL;
+ }
+
+ sd->num_funcs = 2;
+ sd->sd_blockmode = true;
+ sd->use_client_ints = true;
+ sd->client_block_size[0] = 64;
+
+ gInstance->sd = sd;
+
+ /* Claim host controller */
+ sdio_claim_host(gInstance->func[1]);
+
+ sd->client_block_size[1] = 64;
+ err_ret = sdio_set_block_size(gInstance->func[1], 64);
+ if (err_ret)
+ sd_err(("bcmsdh_sdmmc: Failed to set F1 blocksize\n"));
+
+ /* Release host controller F1 */
+ sdio_release_host(gInstance->func[1]);
+
+ if (gInstance->func[2]) {
+ /* Claim host controller F2 */
+ sdio_claim_host(gInstance->func[2]);
+
+ sd->client_block_size[2] = sd_f2_blocksize;
+ err_ret =
+ sdio_set_block_size(gInstance->func[2], sd_f2_blocksize);
+ if (err_ret)
+ sd_err(("bcmsdh_sdmmc: Failed to set F2 blocksize "
+ "to %d\n", sd_f2_blocksize));
+
+ /* Release host controller F2 */
+ sdio_release_host(gInstance->func[2]);
+ }
+
+ sdioh_sdmmc_card_enablefuncs(sd);
+
+ sd_trace(("%s: Done\n", __func__));
+ return sd;
+}
+
+extern SDIOH_API_RC sdioh_detach(osl_t *osh, sdioh_info_t *sd)
+{
+ sd_trace(("%s\n", __func__));
+
+ if (sd) {
+
+ /* Disable Function 2 */
+ sdio_claim_host(gInstance->func[2]);
+ sdio_disable_func(gInstance->func[2]);
+ sdio_release_host(gInstance->func[2]);
+
+ /* Disable Function 1 */
+ sdio_claim_host(gInstance->func[1]);
+ sdio_disable_func(gInstance->func[1]);
+ sdio_release_host(gInstance->func[1]);
+
+ /* deregister irq */
+ sdioh_sdmmc_osfree(sd);
+
+ kfree(sd);
+ }
+ return SDIOH_API_RC_SUCCESS;
+}
+
+#if defined(OOB_INTR_ONLY) && defined(HW_OOB)
+
+extern SDIOH_API_RC sdioh_enable_func_intr(void)
+{
+ u8 reg;
+ int err;
+
+ if (gInstance->func[0]) {
+ sdio_claim_host(gInstance->func[0]);
+
+ reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err);
+ if (err) {
+ sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n",
+ __func__, err));
+ sdio_release_host(gInstance->func[0]);
+ return SDIOH_API_RC_FAIL;
+ }
+
+ /* Enable F1 and F2 interrupts, set master enable */
+ reg |=
+ (INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN |
+ INTR_CTL_MASTER_EN);
+
+ sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err);
+ sdio_release_host(gInstance->func[0]);
+
+ if (err) {
+ sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n",
+ __func__, err));
+ return SDIOH_API_RC_FAIL;
+ }
+ }
+
+ return SDIOH_API_RC_SUCCESS;
+}
+
+extern SDIOH_API_RC sdioh_disable_func_intr(void)
+{
+ u8 reg;
+ int err;
+
+ if (gInstance->func[0]) {
+ sdio_claim_host(gInstance->func[0]);
+ reg = sdio_readb(gInstance->func[0], SDIOD_CCCR_INTEN, &err);
+ if (err) {
+ sd_err(("%s: error for read SDIO_CCCR_IENx : 0x%x\n",
+ __func__, err));
+ sdio_release_host(gInstance->func[0]);
+ return SDIOH_API_RC_FAIL;
+ }
+
+ reg &= ~(INTR_CTL_FUNC1_EN | INTR_CTL_FUNC2_EN);
+ /* Disable master interrupt with the last function interrupt */
+ if (!(reg & 0xFE))
+ reg = 0;
+ sdio_writeb(gInstance->func[0], reg, SDIOD_CCCR_INTEN, &err);
+
+ sdio_release_host(gInstance->func[0]);
+ if (err) {
+ sd_err(("%s: error for write SDIO_CCCR_IENx : 0x%x\n",
+ __func__, err));
+ return SDIOH_API_RC_FAIL;
+ }
+ }
+ return SDIOH_API_RC_SUCCESS;
+}
+#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */
+
+/* Configure callback to client when we recieve client interrupt */
+extern SDIOH_API_RC
+sdioh_interrupt_register(sdioh_info_t *sd, sdioh_cb_fn_t fn, void *argh)
+{
+ sd_trace(("%s: Entering\n", __func__));
+ if (fn == NULL) {
+ sd_err(("%s: interrupt handler is NULL, not registering\n",
+ __func__));
+ return SDIOH_API_RC_FAIL;
+ }
+#if !defined(OOB_INTR_ONLY)
+ sd->intr_handler = fn;
+ sd->intr_handler_arg = argh;
+ sd->intr_handler_valid = true;
+
+ /* register and unmask irq */
+ if (gInstance->func[2]) {
+ sdio_claim_host(gInstance->func[2]);
+ sdio_claim_irq(gInstance->func[2], IRQHandlerF2);
+ sdio_release_host(gInstance->func[2]);
+ }
+
+ if (gInstance->func[1]) {
+ sdio_claim_host(gInstance->func[1]);
+ sdio_claim_irq(gInstance->func[1], IRQHandler);
+ sdio_release_host(gInstance->func[1]);
+ }
+#elif defined(HW_OOB)
+ sdioh_enable_func_intr();
+#endif /* defined(OOB_INTR_ONLY) */
+ return SDIOH_API_RC_SUCCESS;
+}
+
+extern SDIOH_API_RC sdioh_interrupt_deregister(sdioh_info_t *sd)
+{
+ sd_trace(("%s: Entering\n", __func__));
+
+#if !defined(OOB_INTR_ONLY)
+ if (gInstance->func[1]) {
+ /* register and unmask irq */
+ sdio_claim_host(gInstance->func[1]);
+ sdio_release_irq(gInstance->func[1]);
+ sdio_release_host(gInstance->func[1]);
+ }
+
+ if (gInstance->func[2]) {
+ /* Claim host controller F2 */
+ sdio_claim_host(gInstance->func[2]);
+ sdio_release_irq(gInstance->func[2]);
+ /* Release host controller F2 */
+ sdio_release_host(gInstance->func[2]);
+ }
+
+ sd->intr_handler_valid = false;
+ sd->intr_handler = NULL;
+ sd->intr_handler_arg = NULL;
+#elif defined(HW_OOB)
+ sdioh_disable_func_intr();
+#endif /* !defined(OOB_INTR_ONLY) */
+ return SDIOH_API_RC_SUCCESS;
+}
+
+extern SDIOH_API_RC sdioh_interrupt_query(sdioh_info_t *sd, bool *onoff)
+{
+ sd_trace(("%s: Entering\n", __func__));
+ *onoff = sd->client_intr_enabled;
+ return SDIOH_API_RC_SUCCESS;
+}
+
+#if defined(DHD_DEBUG)
+extern bool sdioh_interrupt_pending(sdioh_info_t *sd)
+{
+ return 0;
+}
+#endif
+
+uint sdioh_query_iofnum(sdioh_info_t *sd)
+{
+ return sd->num_funcs;
+}
+
+/* IOVar table */
+enum {
+ IOV_MSGLEVEL = 1,
+ IOV_BLOCKMODE,
+ IOV_BLOCKSIZE,
+ IOV_DMA,
+ IOV_USEINTS,
+ IOV_NUMINTS,
+ IOV_NUMLOCALINTS,
+ IOV_HOSTREG,
+ IOV_DEVREG,
+ IOV_DIVISOR,
+ IOV_SDMODE,
+ IOV_HISPEED,
+ IOV_HCIREGS,
+ IOV_POWER,
+ IOV_CLOCK,
+ IOV_RXCHAIN
+};
+
+const bcm_iovar_t sdioh_iovars[] = {
+ {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0},
+ {"sd_blockmode", IOV_BLOCKMODE, 0, IOVT_BOOL, 0},
+ {"sd_blocksize", IOV_BLOCKSIZE, 0, IOVT_UINT32, 0},/* ((fn << 16) |
+ size) */
+ {"sd_dma", IOV_DMA, 0, IOVT_BOOL, 0},
+ {"sd_ints", IOV_USEINTS, 0, IOVT_BOOL, 0},
+ {"sd_numints", IOV_NUMINTS, 0, IOVT_UINT32, 0},
+ {"sd_numlocalints", IOV_NUMLOCALINTS, 0, IOVT_UINT32, 0},
+ {"sd_hostreg", IOV_HOSTREG, 0, IOVT_BUFFER, sizeof(sdreg_t)}
+ ,
+ {"sd_devreg", IOV_DEVREG, 0, IOVT_BUFFER, sizeof(sdreg_t)}
+ ,
+ {"sd_divisor", IOV_DIVISOR, 0, IOVT_UINT32, 0}
+ ,
+ {"sd_power", IOV_POWER, 0, IOVT_UINT32, 0}
+ ,
+ {"sd_clock", IOV_CLOCK, 0, IOVT_UINT32, 0}
+ ,
+ {"sd_mode", IOV_SDMODE, 0, IOVT_UINT32, 100}
+ ,
+ {"sd_highspeed", IOV_HISPEED, 0, IOVT_UINT32, 0}
+ ,
+ {"sd_rxchain", IOV_RXCHAIN, 0, IOVT_BOOL, 0}
+ ,
+ {NULL, 0, 0, 0, 0}
+};
+
+int
+sdioh_iovar_op(sdioh_info_t *si, const char *name,
+ void *params, int plen, void *arg, int len, bool set)
+{
+ const bcm_iovar_t *vi = NULL;
+ int bcmerror = 0;
+ int val_size;
+ s32 int_val = 0;
+ bool bool_val;
+ u32 actionid;
+
+ ASSERT(name);
+ ASSERT(len >= 0);
+
+ /* Get must have return space; Set does not take qualifiers */
+ ASSERT(set || (arg && len));
+ ASSERT(!set || (!params && !plen));
+
+ sd_trace(("%s: Enter (%s %s)\n", __func__, (set ? "set" : "get"),
+ name));
+
+ vi = bcm_iovar_lookup(sdioh_iovars, name);
+ if (vi == NULL) {
+ bcmerror = BCME_UNSUPPORTED;
+ goto exit;
+ }
+
+ bcmerror = bcm_iovar_lencheck(vi, arg, len, set);
+ if (bcmerror != 0)
+ goto exit;
+
+ /* Set up params so get and set can share the convenience variables */
+ if (params == NULL) {
+ params = arg;
+ plen = len;
+ }
+
+ if (vi->type == IOVT_VOID)
+ val_size = 0;
+ else if (vi->type == IOVT_BUFFER)
+ val_size = len;
+ else
+ val_size = sizeof(int);
+
+ if (plen >= (int)sizeof(int_val))
+ bcopy(params, &int_val, sizeof(int_val));
+
+ bool_val = (int_val != 0) ? true : false;
+
+ actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid);
+ switch (actionid) {
+ case IOV_GVAL(IOV_MSGLEVEL):
+ int_val = (s32) sd_msglevel;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_MSGLEVEL):
+ sd_msglevel = int_val;
+ break;
+
+ case IOV_GVAL(IOV_BLOCKMODE):
+ int_val = (s32) si->sd_blockmode;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_BLOCKMODE):
+ si->sd_blockmode = (bool) int_val;
+ /* Haven't figured out how to make non-block mode with DMA */
+ break;
+
+ case IOV_GVAL(IOV_BLOCKSIZE):
+ if ((u32) int_val > si->num_funcs) {
+ bcmerror = BCME_BADARG;
+ break;
+ }
+ int_val = (s32) si->client_block_size[int_val];
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_BLOCKSIZE):
+ {
+ uint func = ((u32) int_val >> 16);
+ uint blksize = (u16) int_val;
+ uint maxsize;
+
+ if (func > si->num_funcs) {
+ bcmerror = BCME_BADARG;
+ break;
+ }
+
+ switch (func) {
+ case 0:
+ maxsize = 32;
+ break;
+ case 1:
+ maxsize = BLOCK_SIZE_4318;
+ break;
+ case 2:
+ maxsize = BLOCK_SIZE_4328;
+ break;
+ default:
+ maxsize = 0;
+ }
+ if (blksize > maxsize) {
+ bcmerror = BCME_BADARG;
+ break;
+ }
+ if (!blksize)
+ blksize = maxsize;
+
+ /* Now set it */
+ si->client_block_size[func] = blksize;
+
+ break;
+ }
+
+ case IOV_GVAL(IOV_RXCHAIN):
+ int_val = false;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_GVAL(IOV_DMA):
+ int_val = (s32) si->sd_use_dma;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_DMA):
+ si->sd_use_dma = (bool) int_val;
+ break;
+
+ case IOV_GVAL(IOV_USEINTS):
+ int_val = (s32) si->use_client_ints;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_USEINTS):
+ si->use_client_ints = (bool) int_val;
+ if (si->use_client_ints)
+ si->intmask |= CLIENT_INTR;
+ else
+ si->intmask &= ~CLIENT_INTR;
+
+ break;
+
+ case IOV_GVAL(IOV_DIVISOR):
+ int_val = (u32) sd_divisor;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_DIVISOR):
+ sd_divisor = int_val;
+ break;
+
+ case IOV_GVAL(IOV_POWER):
+ int_val = (u32) sd_power;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_POWER):
+ sd_power = int_val;
+ break;
+
+ case IOV_GVAL(IOV_CLOCK):
+ int_val = (u32) sd_clock;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_CLOCK):
+ sd_clock = int_val;
+ break;
+
+ case IOV_GVAL(IOV_SDMODE):
+ int_val = (u32) sd_sdmode;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_SDMODE):
+ sd_sdmode = int_val;
+ break;
+
+ case IOV_GVAL(IOV_HISPEED):
+ int_val = (u32) sd_hiok;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_HISPEED):
+ sd_hiok = int_val;
+ break;
+
+ case IOV_GVAL(IOV_NUMINTS):
+ int_val = (s32) si->intrcount;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_GVAL(IOV_NUMLOCALINTS):
+ int_val = (s32) 0;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_GVAL(IOV_HOSTREG):
+ {
+ sdreg_t *sd_ptr = (sdreg_t *) params;
+
+ if (sd_ptr->offset < SD_SysAddr
+ || sd_ptr->offset > SD_MaxCurCap) {
+ sd_err(("%s: bad offset 0x%x\n", __func__,
+ sd_ptr->offset));
+ bcmerror = BCME_BADARG;
+ break;
+ }
+
+ sd_trace(("%s: rreg%d at offset %d\n", __func__,
+ (sd_ptr->offset & 1) ? 8
+ : ((sd_ptr->offset & 2) ? 16 : 32),
+ sd_ptr->offset));
+ if (sd_ptr->offset & 1)
+ int_val = 8; /* sdioh_sdmmc_rreg8(si,
+ sd_ptr->offset); */
+ else if (sd_ptr->offset & 2)
+ int_val = 16; /* sdioh_sdmmc_rreg16(si,
+ sd_ptr->offset); */
+ else
+ int_val = 32; /* sdioh_sdmmc_rreg(si,
+ sd_ptr->offset); */
+
+ bcopy(&int_val, arg, sizeof(int_val));
+ break;
+ }
+
+ case IOV_SVAL(IOV_HOSTREG):
+ {
+ sdreg_t *sd_ptr = (sdreg_t *) params;
+
+ if (sd_ptr->offset < SD_SysAddr
+ || sd_ptr->offset > SD_MaxCurCap) {
+ sd_err(("%s: bad offset 0x%x\n", __func__,
+ sd_ptr->offset));
+ bcmerror = BCME_BADARG;
+ break;
+ }
+
+ sd_trace(("%s: wreg%d value 0x%08x at offset %d\n",
+ __func__, sd_ptr->value,
+ (sd_ptr->offset & 1) ? 8
+ : ((sd_ptr->offset & 2) ? 16 : 32),
+ sd_ptr->offset));
+ break;
+ }
+
+ case IOV_GVAL(IOV_DEVREG):
+ {
+ sdreg_t *sd_ptr = (sdreg_t *) params;
+ u8 data = 0;
+
+ if (sdioh_cfg_read
+ (si, sd_ptr->func, sd_ptr->offset, &data)) {
+ bcmerror = BCME_SDIO_ERROR;
+ break;
+ }
+
+ int_val = (int)data;
+ bcopy(&int_val, arg, sizeof(int_val));
+ break;
+ }
+
+ case IOV_SVAL(IOV_DEVREG):
+ {
+ sdreg_t *sd_ptr = (sdreg_t *) params;
+ u8 data = (u8) sd_ptr->value;
+
+ if (sdioh_cfg_write
+ (si, sd_ptr->func, sd_ptr->offset, &data)) {
+ bcmerror = BCME_SDIO_ERROR;
+ break;
+ }
+ break;
+ }
+
+ default:
+ bcmerror = BCME_UNSUPPORTED;
+ break;
+ }
+exit:
+
+ return bcmerror;
+}
+
+#if defined(OOB_INTR_ONLY) && defined(HW_OOB)
+
+SDIOH_API_RC sdioh_enable_hw_oob_intr(sdioh_info_t *sd, bool enable)
+{
+ SDIOH_API_RC status;
+ u8 data;
+
+ if (enable)
+ data = 3; /* enable hw oob interrupt */
+ else
+ data = 4; /* disable hw oob interrupt */
+ data |= 4; /* Active HIGH */
+
+ status = sdioh_request_byte(sd, SDIOH_WRITE, 0, 0xf2, &data);
+ return status;
+}
+#endif /* defined(OOB_INTR_ONLY) && defined(HW_OOB) */
+
+extern SDIOH_API_RC
+sdioh_cfg_read(sdioh_info_t *sd, uint fnc_num, u32 addr, u8 *data)
+{
+ SDIOH_API_RC status;
+ /* No lock needed since sdioh_request_byte does locking */
+ status = sdioh_request_byte(sd, SDIOH_READ, fnc_num, addr, data);
+ return status;
+}
+
+extern SDIOH_API_RC
+sdioh_cfg_write(sdioh_info_t *sd, uint fnc_num, u32 addr, u8 *data)
+{
+ /* No lock needed since sdioh_request_byte does locking */
+ SDIOH_API_RC status;
+ status = sdioh_request_byte(sd, SDIOH_WRITE, fnc_num, addr, data);
+ return status;
+}
+
+static int sdioh_sdmmc_get_cisaddr(sdioh_info_t *sd, u32 regaddr)
+{
+ /* read 24 bits and return valid 17 bit addr */
+ int i;
+ u32 scratch, regdata;
+ u8 *ptr = (u8 *)&scratch;
+ for (i = 0; i < 3; i++) {
+ if ((sdioh_sdmmc_card_regread(sd, 0, regaddr, 1, &regdata)) !=
+ SUCCESS)
+ sd_err(("%s: Can't read!\n", __func__));
+
+ *ptr++ = (u8) regdata;
+ regaddr++;
+ }
+
+ /* Only the lower 17-bits are valid */
+ scratch = ltoh32(scratch);
+ scratch &= 0x0001FFFF;
+ return scratch;
+}
+
+extern SDIOH_API_RC
+sdioh_cis_read(sdioh_info_t *sd, uint func, u8 *cisd, u32 length)
+{
+ u32 count;
+ int offset;
+ u32 foo;
+ u8 *cis = cisd;
+
+ sd_trace(("%s: Func = %d\n", __func__, func));
+
+ if (!sd->func_cis_ptr[func]) {
+ bzero(cis, length);
+ sd_err(("%s: no func_cis_ptr[%d]\n", __func__, func));
+ return SDIOH_API_RC_FAIL;
+ }
+
+ sd_err(("%s: func_cis_ptr[%d]=0x%04x\n", __func__, func,
+ sd->func_cis_ptr[func]));
+
+ for (count = 0; count < length; count++) {
+ offset = sd->func_cis_ptr[func] + count;
+ if (sdioh_sdmmc_card_regread(sd, 0, offset, 1, &foo) < 0) {
+ sd_err(("%s: regread failed: Can't read CIS\n",
+ __func__));
+ return SDIOH_API_RC_FAIL;
+ }
+
+ *cis = (u8) (foo & 0xff);
+ cis++;
+ }
+
+ return SDIOH_API_RC_SUCCESS;
+}
+
+extern SDIOH_API_RC
+sdioh_request_byte(sdioh_info_t *sd, uint rw, uint func, uint regaddr,
+ u8 *byte)
+{
+ int err_ret;
+
+ sd_info(("%s: rw=%d, func=%d, addr=0x%05x\n", __func__, rw, func,
+ regaddr));
+
+ DHD_PM_RESUME_WAIT(sdioh_request_byte_wait);
+ DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL);
+ if (rw) { /* CMD52 Write */
+ if (func == 0) {
+ /* Can only directly write to some F0 registers.
+ * Handle F2 enable
+ * as a special case.
+ */
+ if (regaddr == SDIOD_CCCR_IOEN) {
+ if (gInstance->func[2]) {
+ sdio_claim_host(gInstance->func[2]);
+ if (*byte & SDIO_FUNC_ENABLE_2) {
+ /* Enable Function 2 */
+ err_ret =
+ sdio_enable_func
+ (gInstance->func[2]);
+ if (err_ret)
+ sd_err(("bcmsdh_sdmmc: enable F2 failed:%d",
+ err_ret));
+ } else {
+ /* Disable Function 2 */
+ err_ret =
+ sdio_disable_func
+ (gInstance->func[2]);
+ if (err_ret)
+ sd_err(("bcmsdh_sdmmc: Disab F2 failed:%d",
+ err_ret));
+ }
+ sdio_release_host(gInstance->func[2]);
+ }
+ }
+#if defined(MMC_SDIO_ABORT)
+ /* to allow abort command through F1 */
+ else if (regaddr == SDIOD_CCCR_IOABORT) {
+ sdio_claim_host(gInstance->func[func]);
+ /*
+ * this sdio_f0_writeb() can be replaced
+ * with another api
+ * depending upon MMC driver change.
+ * As of this time, this is temporaray one
+ */
+ sdio_writeb(gInstance->func[func], *byte,
+ regaddr, &err_ret);
+ sdio_release_host(gInstance->func[func]);
+ }
+#endif /* MMC_SDIO_ABORT */
+ else if (regaddr < 0xF0) {
+ sd_err(("bcmsdh_sdmmc: F0 Wr:0x%02x: write "
+ "disallowed\n", regaddr));
+ } else {
+ /* Claim host controller, perform F0 write,
+ and release */
+ sdio_claim_host(gInstance->func[func]);
+ sdio_f0_writeb(gInstance->func[func], *byte,
+ regaddr, &err_ret);
+ sdio_release_host(gInstance->func[func]);
+ }
+ } else {
+ /* Claim host controller, perform Fn write,
+ and release */
+ sdio_claim_host(gInstance->func[func]);
+ sdio_writeb(gInstance->func[func], *byte, regaddr,
+ &err_ret);
+ sdio_release_host(gInstance->func[func]);
+ }
+ } else { /* CMD52 Read */
+ /* Claim host controller, perform Fn read, and release */
+ sdio_claim_host(gInstance->func[func]);
+
+ if (func == 0) {
+ *byte =
+ sdio_f0_readb(gInstance->func[func], regaddr,
+ &err_ret);
+ } else {
+ *byte =
+ sdio_readb(gInstance->func[func], regaddr,
+ &err_ret);
+ }
+
+ sdio_release_host(gInstance->func[func]);
+ }
+
+ if (err_ret)
+ sd_err(("bcmsdh_sdmmc: Failed to %s byte F%d:@0x%05x=%02x, "
+ "Err: %d\n", rw ? "Write" : "Read", func, regaddr,
+ *byte, err_ret));
+
+ return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL);
+}
+
+extern SDIOH_API_RC
+sdioh_request_word(sdioh_info_t *sd, uint cmd_type, uint rw, uint func,
+ uint addr, u32 *word, uint nbytes)
+{
+ int err_ret = SDIOH_API_RC_FAIL;
+
+ if (func == 0) {
+ sd_err(("%s: Only CMD52 allowed to F0.\n", __func__));
+ return SDIOH_API_RC_FAIL;
+ }
+
+ sd_info(("%s: cmd_type=%d, rw=%d, func=%d, addr=0x%05x, nbytes=%d\n",
+ __func__, cmd_type, rw, func, addr, nbytes));
+
+ DHD_PM_RESUME_WAIT(sdioh_request_word_wait);
+ DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL);
+ /* Claim host controller */
+ sdio_claim_host(gInstance->func[func]);
+
+ if (rw) { /* CMD52 Write */
+ if (nbytes == 4) {
+ sdio_writel(gInstance->func[func], *word, addr,
+ &err_ret);
+ } else if (nbytes == 2) {
+ sdio_writew(gInstance->func[func], (*word & 0xFFFF),
+ addr, &err_ret);
+ } else {
+ sd_err(("%s: Invalid nbytes: %d\n", __func__, nbytes));
+ }
+ } else { /* CMD52 Read */
+ if (nbytes == 4) {
+ *word =
+ sdio_readl(gInstance->func[func], addr, &err_ret);
+ } else if (nbytes == 2) {
+ *word =
+ sdio_readw(gInstance->func[func], addr,
+ &err_ret) & 0xFFFF;
+ } else {
+ sd_err(("%s: Invalid nbytes: %d\n", __func__, nbytes));
+ }
+ }
+
+ /* Release host controller */
+ sdio_release_host(gInstance->func[func]);
+
+ if (err_ret) {
+ sd_err(("bcmsdh_sdmmc: Failed to %s word, Err: 0x%08x",
+ rw ? "Write" : "Read", err_ret));
+ }
+
+ return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL);
+}
+
+static SDIOH_API_RC
+sdioh_request_packet(sdioh_info_t *sd, uint fix_inc, uint write, uint func,
+ uint addr, void *pkt)
+{
+ bool fifo = (fix_inc == SDIOH_DATA_FIX);
+ u32 SGCount = 0;
+ int err_ret = 0;
+
+ void *pnext;
+
+ sd_trace(("%s: Enter\n", __func__));
+
+ ASSERT(pkt);
+ DHD_PM_RESUME_WAIT(sdioh_request_packet_wait);
+ DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL);
+
+ /* Claim host controller */
+ sdio_claim_host(gInstance->func[func]);
+ for (pnext = pkt; pnext; pnext = PKTNEXT(pnext)) {
+ uint pkt_len = PKTLEN(pnext);
+ pkt_len += 3;
+ pkt_len &= 0xFFFFFFFC;
+
+#ifdef CONFIG_MMC_MSM7X00A
+ if ((pkt_len % 64) == 32) {
+ sd_trace(("%s: Rounding up TX packet +=32\n",
+ __func__));
+ pkt_len += 32;
+ }
+#endif /* CONFIG_MMC_MSM7X00A */
+ /* Make sure the packet is aligned properly.
+ * If it isn't, then this
+ * is the fault of sdioh_request_buffer() which
+ * is supposed to give
+ * us something we can work with.
+ */
+ ASSERT(((u32) (PKTDATA(pkt)) & DMA_ALIGN_MASK) == 0);
+
+ if ((write) && (!fifo)) {
+ err_ret = sdio_memcpy_toio(gInstance->func[func], addr,
+ ((u8 *) PKTDATA(pnext)),
+ pkt_len);
+ } else if (write) {
+ err_ret = sdio_memcpy_toio(gInstance->func[func], addr,
+ ((u8 *) PKTDATA(pnext)),
+ pkt_len);
+ } else if (fifo) {
+ err_ret = sdio_readsb(gInstance->func[func],
+ ((u8 *) PKTDATA(pnext)),
+ addr, pkt_len);
+ } else {
+ err_ret = sdio_memcpy_fromio(gInstance->func[func],
+ ((u8 *) PKTDATA(pnext)),
+ addr, pkt_len);
+ }
+
+ if (err_ret) {
+ sd_err(("%s: %s FAILED %p[%d], addr=0x%05x, pkt_len=%d,"
+ "ERR=0x%08x\n", __func__,
+ (write) ? "TX" : "RX",
+ pnext, SGCount, addr, pkt_len, err_ret));
+ } else {
+ sd_trace(("%s: %s xfr'd %p[%d], addr=0x%05x, len=%d\n",
+ __func__,
+ (write) ? "TX" : "RX",
+ pnext, SGCount, addr, pkt_len));
+ }
+
+ if (!fifo)
+ addr += pkt_len;
+ SGCount++;
+
+ }
+
+ /* Release host controller */
+ sdio_release_host(gInstance->func[func]);
+
+ sd_trace(("%s: Exit\n", __func__));
+ return ((err_ret == 0) ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL);
+}
+
+/*
+ * This function takes a buffer or packet, and fixes everything up
+ * so that in the
+ * end, a DMA-able packet is created.
+ *
+ * A buffer does not have an associated packet pointer,
+ * and may or may not be aligned.
+ * A packet may consist of a single packet, or a packet chain.
+ * If it is a packet chain,
+ * then all the packets in the chain must be properly aligned.
+ * If the packet data is not
+ * aligned, then there may only be one packet, and in this case,
+ * it is copied to a new
+ * aligned packet.
+ *
+ */
+extern SDIOH_API_RC
+sdioh_request_buffer(sdioh_info_t *sd, uint pio_dma, uint fix_inc, uint write,
+ uint func, uint addr, uint reg_width, uint buflen_u,
+ u8 *buffer, void *pkt)
+{
+ SDIOH_API_RC Status;
+ void *mypkt = NULL;
+
+ sd_trace(("%s: Enter\n", __func__));
+
+ DHD_PM_RESUME_WAIT(sdioh_request_buffer_wait);
+ DHD_PM_RESUME_RETURN_ERROR(SDIOH_API_RC_FAIL);
+ /* Case 1: we don't have a packet. */
+ if (pkt == NULL) {
+ sd_data(("%s: Creating new %s Packet, len=%d\n",
+ __func__, write ? "TX" : "RX", buflen_u));
+ mypkt = PKTGET(sd->osh, buflen_u, write ? true : false);
+ if (!mypkt) {
+ sd_err(("%s: PKTGET failed: len %d\n",
+ __func__, buflen_u));
+ return SDIOH_API_RC_FAIL;
+ }
+
+ /* For a write, copy the buffer data into the packet. */
+ if (write)
+ bcopy(buffer, PKTDATA(mypkt), buflen_u);
+
+ Status =
+ sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt);
+
+ /* For a read, copy the packet data back to the buffer. */
+ if (!write)
+ bcopy(PKTDATA(mypkt), buffer, buflen_u);
+
+ PKTFREE(sd->osh, mypkt, write ? true : false);
+ } else if (((u32) (PKTDATA(pkt)) & DMA_ALIGN_MASK) != 0) {
+ /* Case 2: We have a packet, but it is unaligned. */
+
+ /* In this case, we cannot have a chain. */
+ ASSERT(PKTNEXT(pkt) == NULL);
+
+ sd_data(("%s: Creating aligned %s Packet, len=%d\n",
+ __func__, write ? "TX" : "RX", PKTLEN(pkt)));
+ mypkt = PKTGET(sd->osh, PKTLEN(pkt), write ? true : false);
+ if (!mypkt) {
+ sd_err(("%s: PKTGET failed: len %d\n",
+ __func__, PKTLEN(pkt)));
+ return SDIOH_API_RC_FAIL;
+ }
+
+ /* For a write, copy the buffer data into the packet. */
+ if (write)
+ bcopy(PKTDATA(pkt), PKTDATA(mypkt), PKTLEN(pkt));
+
+ Status =
+ sdioh_request_packet(sd, fix_inc, write, func, addr, mypkt);
+
+ /* For a read, copy the packet data back to the buffer. */
+ if (!write)
+ bcopy(PKTDATA(mypkt), PKTDATA(pkt), PKTLEN(mypkt));
+
+ PKTFREE(sd->osh, mypkt, write ? true : false);
+ } else { /* case 3: We have a packet and
+ it is aligned. */
+ sd_data(("%s: Aligned %s Packet, direct DMA\n",
+ __func__, write ? "Tx" : "Rx"));
+ Status =
+ sdioh_request_packet(sd, fix_inc, write, func, addr, pkt);
+ }
+
+ return Status;
+}
+
+/* this function performs "abort" for both of host & device */
+extern int sdioh_abort(sdioh_info_t *sd, uint func)
+{
+#if defined(MMC_SDIO_ABORT)
+ char t_func = (char)func;
+#endif /* defined(MMC_SDIO_ABORT) */
+ sd_trace(("%s: Enter\n", __func__));
+
+#if defined(MMC_SDIO_ABORT)
+ /* issue abort cmd52 command through F1 */
+ sdioh_request_byte(sd, SD_IO_OP_WRITE, SDIO_FUNC_0, SDIOD_CCCR_IOABORT,
+ &t_func);
+#endif /* defined(MMC_SDIO_ABORT) */
+
+ sd_trace(("%s: Exit\n", __func__));
+ return SDIOH_API_RC_SUCCESS;
+}
+
+/* Reset and re-initialize the device */
+int sdioh_sdio_reset(sdioh_info_t *si)
+{
+ sd_trace(("%s: Enter\n", __func__));
+ sd_trace(("%s: Exit\n", __func__));
+ return SDIOH_API_RC_SUCCESS;
+}
+
+/* Disable device interrupt */
+void sdioh_sdmmc_devintr_off(sdioh_info_t *sd)
+{
+ sd_trace(("%s: %d\n", __func__, sd->use_client_ints));
+ sd->intmask &= ~CLIENT_INTR;
+}
+
+/* Enable device interrupt */
+void sdioh_sdmmc_devintr_on(sdioh_info_t *sd)
+{
+ sd_trace(("%s: %d\n", __func__, sd->use_client_ints));
+ sd->intmask |= CLIENT_INTR;
+}
+
+/* Read client card reg */
+int
+sdioh_sdmmc_card_regread(sdioh_info_t *sd, int func, u32 regaddr,
+ int regsize, u32 *data)
+{
+
+ if ((func == 0) || (regsize == 1)) {
+ u8 temp = 0;
+
+ sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp);
+ *data = temp;
+ *data &= 0xff;
+ sd_data(("%s: byte read data=0x%02x\n", __func__, *data));
+ } else {
+ sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, data,
+ regsize);
+ if (regsize == 2)
+ *data &= 0xffff;
+
+ sd_data(("%s: word read data=0x%08x\n", __func__, *data));
+ }
+
+ return SUCCESS;
+}
+
+#if !defined(OOB_INTR_ONLY)
+/* bcmsdh_sdmmc interrupt handler */
+static void IRQHandler(struct sdio_func *func)
+{
+ sdioh_info_t *sd;
+
+ sd_trace(("bcmsdh_sdmmc: ***IRQHandler\n"));
+ sd = gInstance->sd;
+
+ ASSERT(sd != NULL);
+ sdio_release_host(gInstance->func[0]);
+
+ if (sd->use_client_ints) {
+ sd->intrcount++;
+ ASSERT(sd->intr_handler);
+ ASSERT(sd->intr_handler_arg);
+ (sd->intr_handler) (sd->intr_handler_arg);
+ } else {
+ sd_err(("bcmsdh_sdmmc: ***IRQHandler\n"));
+
+ sd_err(("%s: Not ready for intr: enabled %d, handler %p\n",
+ __func__, sd->client_intr_enabled, sd->intr_handler));
+ }
+
+ sdio_claim_host(gInstance->func[0]);
+}
+
+/* bcmsdh_sdmmc interrupt handler for F2 (dummy handler) */
+static void IRQHandlerF2(struct sdio_func *func)
+{
+ sdioh_info_t *sd;
+
+ sd_trace(("bcmsdh_sdmmc: ***IRQHandlerF2\n"));
+
+ sd = gInstance->sd;
+
+ ASSERT(sd != NULL);
+}
+#endif /* !defined(OOB_INTR_ONLY) */
+
+#ifdef NOTUSED
+/* Write client card reg */
+static int
+sdioh_sdmmc_card_regwrite(sdioh_info_t *sd, int func, u32 regaddr,
+ int regsize, u32 data)
+{
+
+ if ((func == 0) || (regsize == 1)) {
+ u8 temp;
+
+ temp = data & 0xff;
+ sdioh_request_byte(sd, SDIOH_READ, func, regaddr, &temp);
+ sd_data(("%s: byte write data=0x%02x\n", __func__, data));
+ } else {
+ if (regsize == 2)
+ data &= 0xffff;
+
+ sdioh_request_word(sd, 0, SDIOH_READ, func, regaddr, &data,
+ regsize);
+
+ sd_data(("%s: word write data=0x%08x\n", __func__, data));
+ }
+
+ return SUCCESS;
+}
+#endif /* NOTUSED */
+
+int sdioh_start(sdioh_info_t *si, int stage)
+{
+ return 0;
+}
+
+int sdioh_stop(sdioh_info_t *si)
+{
+ return 0;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c
new file mode 100644
index 000000000000..ae7b566b11d7
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/types.h>
+#include <linux/sched.h> /* request_irq() */
+#include <bcmdefs.h>
+#include <bcmutils.h>
+#include <sdio.h> /* SDIO Specs */
+#include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */
+#include <sdiovar.h> /* to get msglevel bit values */
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#if !defined(SDIO_VENDOR_ID_BROADCOM)
+#define SDIO_VENDOR_ID_BROADCOM 0x02d0
+#endif /* !defined(SDIO_VENDOR_ID_BROADCOM) */
+
+#define SDIO_DEVICE_ID_BROADCOM_DEFAULT 0x0000
+
+#if !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB)
+#define SDIO_DEVICE_ID_BROADCOM_4325_SDGWB 0x0492 /* BCM94325SDGWB */
+#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) */
+#if !defined(SDIO_DEVICE_ID_BROADCOM_4325)
+#define SDIO_DEVICE_ID_BROADCOM_4325 0x0493
+#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4325) */
+#if !defined(SDIO_DEVICE_ID_BROADCOM_4329)
+#define SDIO_DEVICE_ID_BROADCOM_4329 0x4329
+#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */
+#if !defined(SDIO_DEVICE_ID_BROADCOM_4319)
+#define SDIO_DEVICE_ID_BROADCOM_4319 0x4319
+#endif /* !defined(SDIO_DEVICE_ID_BROADCOM_4329) */
+
+#include <bcmsdh_sdmmc.h>
+
+#include <dhd_dbg.h>
+#include <wl_cfg80211.h>
+
+extern void sdioh_sdmmc_devintr_off(sdioh_info_t *sd);
+extern void sdioh_sdmmc_devintr_on(sdioh_info_t *sd);
+
+int sdio_function_init(void);
+void sdio_function_cleanup(void);
+
+/* module param defaults */
+static int clockoverride;
+
+module_param(clockoverride, int, 0644);
+MODULE_PARM_DESC(clockoverride, "SDIO card clock override");
+
+PBCMSDH_SDMMC_INSTANCE gInstance;
+
+/* Maximum number of bcmsdh_sdmmc devices supported by driver */
+#define BCMSDH_SDMMC_MAX_DEVICES 1
+
+extern int bcmsdh_probe(struct device *dev);
+extern int bcmsdh_remove(struct device *dev);
+struct device sdmmc_dev;
+
+static int bcmsdh_sdmmc_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ int ret = 0;
+ static struct sdio_func sdio_func_0;
+ sd_trace(("bcmsdh_sdmmc: %s Enter\n", __func__));
+ sd_trace(("sdio_bcmsdh: func->class=%x\n", func->class));
+ sd_trace(("sdio_vendor: 0x%04x\n", func->vendor));
+ sd_trace(("sdio_device: 0x%04x\n", func->device));
+ sd_trace(("Function#: 0x%04x\n", func->num));
+
+ if (func->num == 1) {
+ sdio_func_0.num = 0;
+ sdio_func_0.card = func->card;
+ gInstance->func[0] = &sdio_func_0;
+ if (func->device == 0x4) { /* 4318 */
+ gInstance->func[2] = NULL;
+ sd_trace(("NIC found, calling bcmsdh_probe...\n"));
+ ret = bcmsdh_probe(&sdmmc_dev);
+ }
+ }
+
+ gInstance->func[func->num] = func;
+
+ if (func->num == 2) {
+ wl_cfg80211_sdio_func(func);
+ sd_trace(("F2 found, calling bcmsdh_probe...\n"));
+ ret = bcmsdh_probe(&sdmmc_dev);
+ }
+
+ return ret;
+}
+
+static void bcmsdh_sdmmc_remove(struct sdio_func *func)
+{
+ sd_trace(("bcmsdh_sdmmc: %s Enter\n", __func__));
+ sd_info(("sdio_bcmsdh: func->class=%x\n", func->class));
+ sd_info(("sdio_vendor: 0x%04x\n", func->vendor));
+ sd_info(("sdio_device: 0x%04x\n", func->device));
+ sd_info(("Function#: 0x%04x\n", func->num));
+
+ if (func->num == 2) {
+ sd_trace(("F2 found, calling bcmsdh_remove...\n"));
+ bcmsdh_remove(&sdmmc_dev);
+ }
+}
+
+/* devices we support, null terminated */
+static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
+ {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT)},
+ {SDIO_DEVICE
+ (SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325_SDGWB)},
+ {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325)},
+ {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329)},
+ {SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4319)},
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids);
+
+static struct sdio_driver bcmsdh_sdmmc_driver = {
+ .probe = bcmsdh_sdmmc_probe,
+ .remove = bcmsdh_sdmmc_remove,
+ .name = "brcmfmac",
+ .id_table = bcmsdh_sdmmc_ids,
+};
+
+struct sdos_info {
+ sdioh_info_t *sd;
+ spinlock_t lock;
+};
+
+int sdioh_sdmmc_osinit(sdioh_info_t *sd)
+{
+ struct sdos_info *sdos;
+
+ sdos = kmalloc(sizeof(struct sdos_info), GFP_ATOMIC);
+ sd->sdos_info = (void *)sdos;
+ if (sdos == NULL)
+ return BCME_NOMEM;
+
+ sdos->sd = sd;
+ spin_lock_init(&sdos->lock);
+ return BCME_OK;
+}
+
+void sdioh_sdmmc_osfree(sdioh_info_t *sd)
+{
+ struct sdos_info *sdos;
+ ASSERT(sd && sd->sdos_info);
+
+ sdos = (struct sdos_info *)sd->sdos_info;
+ kfree(sdos);
+}
+
+/* Interrupt enable/disable */
+SDIOH_API_RC sdioh_interrupt_set(sdioh_info_t *sd, bool enable)
+{
+ unsigned long flags;
+ struct sdos_info *sdos;
+
+ sd_trace(("%s: %s\n", __func__, enable ? "Enabling" : "Disabling"));
+
+ sdos = (struct sdos_info *)sd->sdos_info;
+ ASSERT(sdos);
+
+#if !defined(OOB_INTR_ONLY)
+ if (enable && !(sd->intr_handler && sd->intr_handler_arg)) {
+ sd_err(("%s: no handler registered, will not enable\n",
+ __func__));
+ return SDIOH_API_RC_FAIL;
+ }
+#endif /* !defined(OOB_INTR_ONLY) */
+
+ /* Ensure atomicity for enable/disable calls */
+ spin_lock_irqsave(&sdos->lock, flags);
+
+ sd->client_intr_enabled = enable;
+ if (enable)
+ sdioh_sdmmc_devintr_on(sd);
+ else
+ sdioh_sdmmc_devintr_off(sd);
+
+ spin_unlock_irqrestore(&sdos->lock, flags);
+
+ return SDIOH_API_RC_SUCCESS;
+}
+
+/*
+ * module init
+*/
+int sdio_function_init(void)
+{
+ int error = 0;
+ sd_trace(("bcmsdh_sdmmc: %s Enter\n", __func__));
+
+ gInstance = kzalloc(sizeof(BCMSDH_SDMMC_INSTANCE), GFP_KERNEL);
+ if (!gInstance)
+ return -ENOMEM;
+
+ bzero(&sdmmc_dev, sizeof(sdmmc_dev));
+ error = sdio_register_driver(&bcmsdh_sdmmc_driver);
+
+ return error;
+}
+
+/*
+ * module cleanup
+*/
+extern int bcmsdh_remove(struct device *dev);
+void sdio_function_cleanup(void)
+{
+ sd_trace(("%s Enter\n", __func__));
+
+ sdio_unregister_driver(&bcmsdh_sdmmc_driver);
+
+ kfree(gInstance);
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd.h b/drivers/staging/brcm80211/brcmfmac/dhd.h
new file mode 100644
index 000000000000..57d06b2da46f
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/****************
+ * Common types *
+ */
+
+#ifndef _dhd_h_
+#define _dhd_h_
+
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+#include <linux/ethtool.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+#if defined(CONFIG_HAS_WAKELOCK)
+#include <linux/wakelock.h>
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+/* The kernel threading is sdio-specific */
+
+#include <wlioctl.h>
+
+/* Forward decls */
+struct dhd_bus;
+struct dhd_prot;
+struct dhd_info;
+
+/* The level of bus communication with the dongle */
+enum dhd_bus_state {
+ DHD_BUS_DOWN, /* Not ready for frame transfers */
+ DHD_BUS_LOAD, /* Download access only (CPU reset) */
+ DHD_BUS_DATA /* Ready for frame transfers */
+};
+
+enum dhd_bus_wake_state {
+ WAKE_LOCK_OFF,
+ WAKE_LOCK_PRIV,
+ WAKE_LOCK_DPC,
+ WAKE_LOCK_IOCTL,
+ WAKE_LOCK_DOWNLOAD,
+ WAKE_LOCK_TMOUT,
+ WAKE_LOCK_WATCHDOG,
+ WAKE_LOCK_LINK_DOWN_TMOUT,
+ WAKE_LOCK_PNO_FIND_TMOUT,
+ WAKE_LOCK_SOFTAP_SET,
+ WAKE_LOCK_SOFTAP_STOP,
+ WAKE_LOCK_SOFTAP_START,
+ WAKE_LOCK_MAX
+};
+enum dhd_prealloc_index {
+ DHD_PREALLOC_PROT = 0,
+ DHD_PREALLOC_RXBUF,
+ DHD_PREALLOC_DATABUF,
+ DHD_PREALLOC_OSL_BUF
+};
+
+/* Common structure for module and instance linkage */
+typedef struct dhd_pub {
+ /* Linkage ponters */
+ osl_t *osh; /* OSL handle */
+ struct dhd_bus *bus; /* Bus module handle */
+ struct dhd_prot *prot; /* Protocol module handle */
+ struct dhd_info *info; /* Info module handle */
+
+ /* Internal dhd items */
+ bool up; /* Driver up/down (to OS) */
+ bool txoff; /* Transmit flow-controlled */
+ bool dongle_reset; /* true = DEVRESET put dongle into reset */
+ enum dhd_bus_state busstate;
+ uint hdrlen; /* Total DHD header length (proto + bus) */
+ uint maxctl; /* Max size rxctl request from proto to bus */
+ uint rxsz; /* Rx buffer size bus module should use */
+ u8 wme_dp; /* wme discard priority */
+
+ /* Dongle media info */
+ bool iswl; /* Dongle-resident driver is wl */
+ unsigned long drv_version; /* Version of dongle-resident driver */
+ struct ether_addr mac; /* MAC address obtained from dongle */
+ dngl_stats_t dstats; /* Stats for dongle-based data */
+
+ /* Additional stats for the bus level */
+ unsigned long tx_packets; /* Data packets sent to dongle */
+ unsigned long tx_multicast; /* Multicast data packets sent to dongle */
+ unsigned long tx_errors; /* Errors in sending data to dongle */
+ unsigned long tx_ctlpkts; /* Control packets sent to dongle */
+ unsigned long tx_ctlerrs; /* Errors sending control frames to dongle */
+ unsigned long rx_packets; /* Packets sent up the network interface */
+ unsigned long rx_multicast; /* Multicast packets sent up the network
+ interface */
+ unsigned long rx_errors; /* Errors processing rx data packets */
+ unsigned long rx_ctlpkts; /* Control frames processed from dongle */
+ unsigned long rx_ctlerrs; /* Errors in processing rx control frames */
+ unsigned long rx_dropped; /* Packets dropped locally (no memory) */
+ unsigned long rx_flushed; /* Packets flushed due to
+ unscheduled sendup thread */
+ unsigned long wd_dpc_sched; /* Number of times dhd dpc scheduled by
+ watchdog timer */
+
+ unsigned long rx_readahead_cnt; /* Number of packets where header read-ahead
+ was used. */
+ unsigned long tx_realloc; /* Number of tx packets we had to realloc for
+ headroom */
+ unsigned long fc_packets; /* Number of flow control pkts recvd */
+
+ /* Last error return */
+ int bcmerror;
+ uint tickcnt;
+
+ /* Last error from dongle */
+ int dongle_error;
+
+ /* Suspend disable flag flag */
+ int suspend_disable_flag; /* "1" to disable all extra powersaving
+ during suspend */
+ int in_suspend; /* flag set to 1 when early suspend called */
+#ifdef PNO_SUPPORT
+ int pno_enable; /* pno status : "1" is pno enable */
+#endif /* PNO_SUPPORT */
+ int dtim_skip; /* dtim skip , default 0 means wake each dtim */
+
+ /* Pkt filter defination */
+ char *pktfilter[100];
+ int pktfilter_count;
+
+ u8 country_code[WLC_CNTRY_BUF_SZ];
+ char eventmask[WL_EVENTING_MASK_LEN];
+
+#if defined(CONFIG_HAS_WAKELOCK)
+ struct wake_lock wakelock[WAKE_LOCK_MAX];
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+} dhd_pub_t;
+
+#if defined(CONFIG_PM_SLEEP)
+
+#define DHD_PM_RESUME_WAIT_INIT(a) DECLARE_WAIT_QUEUE_HEAD(a);
+#define _DHD_PM_RESUME_WAIT(a, b) do {\
+ int retry = 0; \
+ while (dhd_mmc_suspend && retry++ != b) { \
+ wait_event_timeout(a, false, HZ/100); \
+ } \
+ } while (0)
+#define DHD_PM_RESUME_WAIT(a) _DHD_PM_RESUME_WAIT(a, 30)
+#define DHD_PM_RESUME_WAIT_FOREVER(a) _DHD_PM_RESUME_WAIT(a, ~0)
+#define DHD_PM_RESUME_RETURN_ERROR(a) \
+ do { if (dhd_mmc_suspend) return a; } while (0)
+#define DHD_PM_RESUME_RETURN do { if (dhd_mmc_suspend) return; } while (0)
+
+#define DHD_SPINWAIT_SLEEP_INIT(a) DECLARE_WAIT_QUEUE_HEAD(a);
+#define SPINWAIT_SLEEP(a, exp, us) do { \
+ uint countdown = (us) + 9999; \
+ while ((exp) && (countdown >= 10000)) { \
+ wait_event_timeout(a, false, HZ/100); \
+ countdown -= 10000; \
+ } \
+ } while (0)
+
+#else
+
+#define DHD_PM_RESUME_WAIT_INIT(a)
+#define DHD_PM_RESUME_WAIT(a)
+#define DHD_PM_RESUME_WAIT_FOREVER(a)
+#define DHD_PM_RESUME_RETURN_ERROR(a)
+#define DHD_PM_RESUME_RETURN
+
+#define DHD_SPINWAIT_SLEEP_INIT(a)
+#define SPINWAIT_SLEEP(a, exp, us) do { \
+ uint countdown = (us) + 9; \
+ while ((exp) && (countdown >= 10)) { \
+ udelay(10); \
+ countdown -= 10; \
+ } \
+ } while (0)
+
+#endif /* defined(CONFIG_PM_SLEEP) */
+#define DHD_IF_VIF 0x01 /* Virtual IF (Hidden from user) */
+
+static inline void MUTEX_LOCK_INIT(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_LOCK(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_UNLOCK(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_LOCK_SOFTAP_SET_INIT(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_LOCK_SOFTAP_SET(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_UNLOCK_SOFTAP_SET(dhd_pub_t *dhdp)
+{
+}
+
+static inline void MUTEX_LOCK_WL_SCAN_SET_INIT(void)
+{
+}
+
+static inline void MUTEX_LOCK_WL_SCAN_SET(void)
+{
+}
+
+static inline void MUTEX_UNLOCK_WL_SCAN_SET(void)
+{
+}
+
+static inline void WAKE_LOCK_INIT(dhd_pub_t *dhdp, int index, char *y)
+{
+#if defined(CONFIG_HAS_WAKELOCK)
+ wake_lock_init(&dhdp->wakelock[index], WAKE_LOCK_SUSPEND, y);
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+}
+
+static inline void WAKE_LOCK(dhd_pub_t *dhdp, int index)
+{
+#if defined(CONFIG_HAS_WAKELOCK)
+ wake_lock(&dhdp->wakelock[index]);
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+}
+
+static inline void WAKE_UNLOCK(dhd_pub_t *dhdp, int index)
+{
+#if defined(CONFIG_HAS_WAKELOCK)
+ wake_unlock(&dhdp->wakelock[index]);
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+}
+
+static inline void WAKE_LOCK_TIMEOUT(dhd_pub_t *dhdp, int index, long time)
+{
+#if defined(CONFIG_HAS_WAKELOCK)
+ wake_lock_timeout(&dhdp->wakelock[index], time);
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+}
+
+static inline void WAKE_LOCK_DESTROY(dhd_pub_t *dhdp, int index)
+{
+#if defined(CONFIG_HAS_WAKELOCK)
+ wake_lock_destroy(&dhdp->wakelock[index]);
+#endif /* defined (CONFIG_HAS_WAKELOCK) */
+}
+
+typedef struct dhd_if_event {
+ u8 ifidx;
+ u8 action;
+ u8 flags;
+ u8 bssidx;
+} dhd_if_event_t;
+
+/*
+ * Exported from dhd OS modules (dhd_linux/dhd_ndis)
+ */
+
+/* To allow osl_attach/detach calls from os-independent modules */
+osl_t *dhd_osl_attach(void *pdev, uint bustype);
+void dhd_osl_detach(osl_t *osh);
+
+/* Indication from bus module regarding presence/insertion of dongle.
+ * Return dhd_pub_t pointer, used as handle to OS module in later calls.
+ * Returned structure should have bus and prot pointers filled in.
+ * bus_hdrlen specifies required headroom for bus module header.
+ */
+extern dhd_pub_t *dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen);
+extern int dhd_net_attach(dhd_pub_t *dhdp, int idx);
+
+/* Indication from bus module regarding removal/absence of dongle */
+extern void dhd_detach(dhd_pub_t *dhdp);
+
+/* Indication from bus module to change flow-control state */
+extern void dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool on);
+
+extern bool dhd_prec_enq(dhd_pub_t *dhdp, struct pktq *q, void *pkt, int prec);
+
+/* Receive frame for delivery to OS. Callee disposes of rxp. */
+extern void dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *rxp, int numpkt);
+
+/* Return pointer to interface name */
+extern char *dhd_ifname(dhd_pub_t *dhdp, int idx);
+
+/* Request scheduling of the bus dpc */
+extern void dhd_sched_dpc(dhd_pub_t *dhdp);
+
+/* Notify tx completion */
+extern void dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success);
+
+/* Query ioctl */
+extern int dhdcdc_query_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf,
+ uint len);
+
+/* OS independent layer functions */
+extern int dhd_os_proto_block(dhd_pub_t *pub);
+extern int dhd_os_proto_unblock(dhd_pub_t *pub);
+extern int dhd_os_ioctl_resp_wait(dhd_pub_t *pub, uint *condition,
+ bool *pending);
+extern int dhd_os_ioctl_resp_wake(dhd_pub_t *pub);
+extern unsigned int dhd_os_get_ioctl_resp_timeout(void);
+extern void dhd_os_set_ioctl_resp_timeout(unsigned int timeout_msec);
+extern void *dhd_os_open_image(char *filename);
+extern int dhd_os_get_image_block(char *buf, int len, void *image);
+extern void dhd_os_close_image(void *image);
+extern void dhd_os_wd_timer(void *bus, uint wdtick);
+extern void dhd_os_sdlock(dhd_pub_t *pub);
+extern void dhd_os_sdunlock(dhd_pub_t *pub);
+extern void dhd_os_sdlock_txq(dhd_pub_t *pub);
+extern void dhd_os_sdunlock_txq(dhd_pub_t *pub);
+extern void dhd_os_sdlock_rxq(dhd_pub_t *pub);
+extern void dhd_os_sdunlock_rxq(dhd_pub_t *pub);
+extern void dhd_os_sdlock_sndup_rxq(dhd_pub_t *pub);
+extern void dhd_customer_gpio_wlan_ctrl(int onoff);
+extern int dhd_custom_get_mac_address(unsigned char *buf);
+extern void dhd_os_sdunlock_sndup_rxq(dhd_pub_t *pub);
+extern void dhd_os_sdlock_eventq(dhd_pub_t *pub);
+extern void dhd_os_sdunlock_eventq(dhd_pub_t *pub);
+#ifdef DHD_DEBUG
+extern int write_to_file(dhd_pub_t *dhd, u8 *buf, int size);
+#endif /* DHD_DEBUG */
+#if defined(OOB_INTR_ONLY)
+extern int dhd_customer_oob_irq_map(unsigned long *irq_flags_ptr);
+#endif /* defined(OOB_INTR_ONLY) */
+extern void dhd_os_sdtxlock(dhd_pub_t *pub);
+extern void dhd_os_sdtxunlock(dhd_pub_t *pub);
+
+int setScheduler(struct task_struct *p, int policy, struct sched_param *param);
+
+typedef struct {
+ u32 limit; /* Expiration time (usec) */
+ u32 increment; /* Current expiration increment (usec) */
+ u32 elapsed; /* Current elapsed time (usec) */
+ u32 tick; /* O/S tick time (usec) */
+} dhd_timeout_t;
+
+extern void dhd_timeout_start(dhd_timeout_t *tmo, uint usec);
+extern int dhd_timeout_expired(dhd_timeout_t *tmo);
+
+extern int dhd_ifname2idx(struct dhd_info *dhd, char *name);
+extern u8 *dhd_bssidx2bssid(dhd_pub_t *dhd, int idx);
+extern int wl_host_event(struct dhd_info *dhd, int *idx, void *pktdata,
+ wl_event_msg_t *, void **data_ptr);
+extern void wl_event_to_host_order(wl_event_msg_t *evt);
+
+extern void dhd_common_init(void);
+
+extern int dhd_add_if(struct dhd_info *dhd, int ifidx, void *handle,
+ char *name, u8 *mac_addr, u32 flags, u8 bssidx);
+extern void dhd_del_if(struct dhd_info *dhd, int ifidx);
+
+extern void dhd_vif_add(struct dhd_info *dhd, int ifidx, char *name);
+extern void dhd_vif_del(struct dhd_info *dhd, int ifidx);
+
+extern void dhd_event(struct dhd_info *dhd, char *evpkt, int evlen, int ifidx);
+extern void dhd_vif_sendup(struct dhd_info *dhd, int ifidx, unsigned char * cp,
+ int len);
+
+/* Send packet to dongle via data channel */
+extern int dhd_sendpkt(dhd_pub_t *dhdp, int ifidx, void *pkt);
+
+/* Send event to host */
+extern void dhd_sendup_event(dhd_pub_t *dhdp, wl_event_msg_t *event,
+ void *data);
+extern int dhd_bus_devreset(dhd_pub_t *dhdp, u8 flag);
+extern uint dhd_bus_status(dhd_pub_t *dhdp);
+extern int dhd_bus_start(dhd_pub_t *dhdp);
+
+extern void print_buf(void *pbuf, int len, int bytes_per_line);
+
+typedef enum cust_gpio_modes {
+ WLAN_RESET_ON,
+ WLAN_RESET_OFF,
+ WLAN_POWER_ON,
+ WLAN_POWER_OFF
+} cust_gpio_modes_t;
+/*
+ * Insmod parameters for debug/test
+ */
+
+/* Watchdog timer interval */
+extern uint dhd_watchdog_ms;
+
+#if defined(DHD_DEBUG)
+/* Console output poll interval */
+extern uint dhd_console_ms;
+#endif /* defined(DHD_DEBUG) */
+
+/* Use interrupts */
+extern uint dhd_intr;
+
+/* Use polling */
+extern uint dhd_poll;
+
+/* ARP offload agent mode */
+extern uint dhd_arp_mode;
+
+/* ARP offload enable */
+extern uint dhd_arp_enable;
+
+/* Pkt filte enable control */
+extern uint dhd_pkt_filter_enable;
+
+/* Pkt filter init setup */
+extern uint dhd_pkt_filter_init;
+
+/* Pkt filter mode control */
+extern uint dhd_master_mode;
+
+/* Roaming mode control */
+extern uint dhd_roam;
+
+/* Roaming mode control */
+extern uint dhd_radio_up;
+
+/* Initial idletime ticks (may be -1 for immediate idle, 0 for no idle) */
+extern int dhd_idletime;
+#define DHD_IDLETIME_TICKS 1
+
+/* SDIO Drive Strength */
+extern uint dhd_sdiod_drive_strength;
+
+/* Override to force tx queueing all the time */
+extern uint dhd_force_tx_queueing;
+
+#ifdef SDTEST
+/* Echo packet generator (SDIO), pkts/s */
+extern uint dhd_pktgen;
+
+/* Echo packet len (0 => sawtooth, max 1800) */
+extern uint dhd_pktgen_len;
+#define MAX_PKTGEN_LEN 1800
+#endif
+
+/* optionally set by a module_param_string() */
+#define MOD_PARAM_PATHLEN 2048
+extern char fw_path[MOD_PARAM_PATHLEN];
+extern char nv_path[MOD_PARAM_PATHLEN];
+
+/* For supporting multiple interfaces */
+#define DHD_MAX_IFS 16
+#define DHD_DEL_IF -0xe
+#define DHD_BAD_IF -0xf
+
+extern void dhd_wait_for_event(dhd_pub_t *dhd, bool * lockvar);
+extern void dhd_wait_event_wakeup(dhd_pub_t *dhd);
+
+#endif /* _dhd_h_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_bus.h b/drivers/staging/brcm80211/brcmfmac/dhd_bus.h
new file mode 100644
index 000000000000..3b39c9966f81
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_bus.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _dhd_bus_h_
+#define _dhd_bus_h_
+
+/*
+ * Exported from dhd bus module (dhd_usb, dhd_sdio)
+ */
+
+/* Indicate (dis)interest in finding dongles. */
+extern int dhd_bus_register(void);
+extern void dhd_bus_unregister(void);
+
+/* Download firmware image and nvram image */
+extern bool dhd_bus_download_firmware(struct dhd_bus *bus, osl_t * osh,
+ char *fw_path, char *nv_path);
+
+/* Stop bus module: clear pending frames, disable data flow */
+extern void dhd_bus_stop(struct dhd_bus *bus, bool enforce_mutex);
+
+/* Initialize bus module: prepare for communication w/dongle */
+extern int dhd_bus_init(dhd_pub_t *dhdp, bool enforce_mutex);
+
+/* Send a data frame to the dongle. Callee disposes of txp. */
+extern int dhd_bus_txdata(struct dhd_bus *bus, void *txp);
+
+/* Send/receive a control message to/from the dongle.
+ * Expects caller to enforce a single outstanding transaction.
+ */
+extern int dhd_bus_txctl(struct dhd_bus *bus, unsigned char *msg, uint msglen);
+extern int dhd_bus_rxctl(struct dhd_bus *bus, unsigned char *msg, uint msglen);
+
+/* Watchdog timer function */
+extern bool dhd_bus_watchdog(dhd_pub_t *dhd);
+
+#ifdef DHD_DEBUG
+/* Device console input function */
+extern int dhd_bus_console_in(dhd_pub_t *dhd, unsigned char *msg, uint msglen);
+#endif /* DHD_DEBUG */
+
+/* Deferred processing for the bus, return true requests reschedule */
+extern bool dhd_bus_dpc(struct dhd_bus *bus);
+extern void dhd_bus_isr(bool *InterruptRecognized,
+ bool *QueueMiniportHandleInterrupt, void *arg);
+
+/* Check for and handle local prot-specific iovar commands */
+extern int dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name,
+ void *params, int plen, void *arg, int len,
+ bool set);
+
+/* Add bus dump output to a buffer */
+extern void dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf);
+
+/* Clear any bus counters */
+extern void dhd_bus_clearcounts(dhd_pub_t *dhdp);
+
+/* return the dongle chipid */
+extern uint dhd_bus_chip(struct dhd_bus *bus);
+
+/* Set user-specified nvram parameters. */
+extern void dhd_bus_set_nvram_params(struct dhd_bus *bus,
+ const char *nvram_params);
+
+extern void *dhd_bus_pub(struct dhd_bus *bus);
+extern void *dhd_bus_txq(struct dhd_bus *bus);
+extern uint dhd_bus_hdrlen(struct dhd_bus *bus);
+
+#endif /* _dhd_bus_h_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_cdc.c b/drivers/staging/brcm80211/brcmfmac/dhd_cdc.c
new file mode 100644
index 000000000000..bcbaac9bcdcc
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_cdc.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/types.h>
+#include <bcmdefs.h>
+#include <osl.h>
+
+#include <bcmutils.h>
+#include <bcmcdc.h>
+#include <bcmendian.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhd_proto.h>
+#include <dhd_bus.h>
+#include <dhd_dbg.h>
+#ifdef CUSTOMER_HW2
+int wifi_get_mac_addr(unsigned char *buf);
+#endif
+
+extern int dhd_preinit_ioctls(dhd_pub_t *dhd);
+
+/* Packet alignment for most efficient SDIO (can change based on platform) */
+#ifndef DHD_SDALIGN
+#define DHD_SDALIGN 32
+#endif
+#if !ISPOWEROF2(DHD_SDALIGN)
+#error DHD_SDALIGN is not a power of 2!
+#endif
+
+#define RETRIES 2 /* # of retries to retrieve matching ioctl response */
+#define BUS_HEADER_LEN (16+DHD_SDALIGN) /* Must be atleast SDPCM_RESERVE
+ * defined in dhd_sdio.c
+ * (amount of header tha might be added)
+ * plus any space that might be needed
+ * for alignment padding.
+ */
+#define ROUND_UP_MARGIN 2048 /* Biggest SDIO block size possible for
+ * round off at the end of buffer
+ */
+
+typedef struct dhd_prot {
+ u16 reqid;
+ u8 pending;
+ u32 lastcmd;
+ u8 bus_header[BUS_HEADER_LEN];
+ cdc_ioctl_t msg;
+ unsigned char buf[WLC_IOCTL_MAXLEN + ROUND_UP_MARGIN];
+} dhd_prot_t;
+
+static int dhdcdc_msg(dhd_pub_t *dhd)
+{
+ dhd_prot_t *prot = dhd->prot;
+ int len = ltoh32(prot->msg.len) + sizeof(cdc_ioctl_t);
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* NOTE : cdc->msg.len holds the desired length of the buffer to be
+ * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area
+ * is actually sent to the dongle
+ */
+ if (len > CDC_MAX_MSG_SIZE)
+ len = CDC_MAX_MSG_SIZE;
+
+ /* Send request */
+ return dhd_bus_txctl(dhd->bus, (unsigned char *)&prot->msg, len);
+}
+
+static int dhdcdc_cmplt(dhd_pub_t *dhd, u32 id, u32 len)
+{
+ int ret;
+ dhd_prot_t *prot = dhd->prot;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ do {
+ ret =
+ dhd_bus_rxctl(dhd->bus, (unsigned char *)&prot->msg,
+ len + sizeof(cdc_ioctl_t));
+ if (ret < 0)
+ break;
+ } while (CDC_IOC_ID(ltoh32(prot->msg.flags)) != id);
+
+ return ret;
+}
+
+int
+dhdcdc_query_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len)
+{
+ dhd_prot_t *prot = dhd->prot;
+ cdc_ioctl_t *msg = &prot->msg;
+ void *info;
+ int ret = 0, retries = 0;
+ u32 id, flags = 0;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+ DHD_CTL(("%s: cmd %d len %d\n", __func__, cmd, len));
+
+ /* Respond "bcmerror" and "bcmerrorstr" with local cache */
+ if (cmd == WLC_GET_VAR && buf) {
+ if (!strcmp((char *)buf, "bcmerrorstr")) {
+ strncpy((char *)buf, bcmerrorstr(dhd->dongle_error),
+ BCME_STRLEN);
+ goto done;
+ } else if (!strcmp((char *)buf, "bcmerror")) {
+ *(int *)buf = dhd->dongle_error;
+ goto done;
+ }
+ }
+
+ memset(msg, 0, sizeof(cdc_ioctl_t));
+
+ msg->cmd = htol32(cmd);
+ msg->len = htol32(len);
+ msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT);
+ CDC_SET_IF_IDX(msg, ifidx);
+ msg->flags = htol32(msg->flags);
+
+ if (buf)
+ memcpy(prot->buf, buf, len);
+
+ ret = dhdcdc_msg(dhd);
+ if (ret < 0) {
+ DHD_ERROR(("dhdcdc_query_ioctl: dhdcdc_msg failed w/status "
+ "%d\n", ret));
+ goto done;
+ }
+
+retry:
+ /* wait for interrupt and get first fragment */
+ ret = dhdcdc_cmplt(dhd, prot->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = ltoh32(msg->flags);
+ id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT;
+
+ if ((id < prot->reqid) && (++retries < RETRIES))
+ goto retry;
+ if (id != prot->reqid) {
+ DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n",
+ dhd_ifname(dhd, ifidx), __func__, id, prot->reqid));
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Check info buffer */
+ info = (void *)&msg[1];
+
+ /* Copy info buffer */
+ if (buf) {
+ if (ret < (int)len)
+ len = ret;
+ memcpy(buf, info, len);
+ }
+
+ /* Check the ERROR flag */
+ if (flags & CDCF_IOC_ERROR) {
+ ret = ltoh32(msg->status);
+ /* Cache error from dongle */
+ dhd->dongle_error = ret;
+ }
+
+done:
+ return ret;
+}
+
+int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len)
+{
+ dhd_prot_t *prot = dhd->prot;
+ cdc_ioctl_t *msg = &prot->msg;
+ int ret = 0;
+ u32 flags, id;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+ DHD_CTL(("%s: cmd %d len %d\n", __func__, cmd, len));
+
+ memset(msg, 0, sizeof(cdc_ioctl_t));
+
+ msg->cmd = htol32(cmd);
+ msg->len = htol32(len);
+ msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT) | CDCF_IOC_SET;
+ CDC_SET_IF_IDX(msg, ifidx);
+ msg->flags = htol32(msg->flags);
+
+ if (buf)
+ memcpy(prot->buf, buf, len);
+
+ ret = dhdcdc_msg(dhd);
+ if (ret < 0)
+ goto done;
+
+ ret = dhdcdc_cmplt(dhd, prot->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = ltoh32(msg->flags);
+ id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT;
+
+ if (id != prot->reqid) {
+ DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n",
+ dhd_ifname(dhd, ifidx), __func__, id, prot->reqid));
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Check the ERROR flag */
+ if (flags & CDCF_IOC_ERROR) {
+ ret = ltoh32(msg->status);
+ /* Cache error from dongle */
+ dhd->dongle_error = ret;
+ }
+
+done:
+ return ret;
+}
+
+extern int dhd_bus_interface(struct dhd_bus *bus, uint arg, void *arg2);
+int
+dhd_prot_ioctl(dhd_pub_t *dhd, int ifidx, wl_ioctl_t *ioc, void *buf, int len)
+{
+ dhd_prot_t *prot = dhd->prot;
+ int ret = -1;
+
+ if (dhd->busstate == DHD_BUS_DOWN) {
+ DHD_ERROR(("%s : bus is down. we have nothing to do\n",
+ __func__));
+ return ret;
+ }
+ dhd_os_proto_block(dhd);
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ASSERT(len <= WLC_IOCTL_MAXLEN);
+
+ if (len > WLC_IOCTL_MAXLEN)
+ goto done;
+
+ if (prot->pending == true) {
+ DHD_TRACE(("CDC packet is pending!!!! cmd=0x%x (%lu) "
+ "lastcmd=0x%x (%lu)\n",
+ ioc->cmd, (unsigned long)ioc->cmd, prot->lastcmd,
+ (unsigned long)prot->lastcmd));
+ if ((ioc->cmd == WLC_SET_VAR) || (ioc->cmd == WLC_GET_VAR)) {
+ DHD_TRACE(("iovar cmd=%s\n", (char *)buf));
+ }
+ goto done;
+ }
+
+ prot->pending = true;
+ prot->lastcmd = ioc->cmd;
+ if (ioc->set)
+ ret = dhdcdc_set_ioctl(dhd, ifidx, ioc->cmd, buf, len);
+ else {
+ ret = dhdcdc_query_ioctl(dhd, ifidx, ioc->cmd, buf, len);
+ if (ret > 0)
+ ioc->used = ret - sizeof(cdc_ioctl_t);
+ }
+
+ /* Too many programs assume ioctl() returns 0 on success */
+ if (ret >= 0)
+ ret = 0;
+ else {
+ cdc_ioctl_t *msg = &prot->msg;
+ ioc->needed = ltoh32(msg->len); /* len == needed when set/query
+ fails from dongle */
+ }
+
+ /* Intercept the wme_dp ioctl here */
+ if ((!ret) && (ioc->cmd == WLC_SET_VAR) && (!strcmp(buf, "wme_dp"))) {
+ int slen, val = 0;
+
+ slen = strlen("wme_dp") + 1;
+ if (len >= (int)(slen + sizeof(int)))
+ bcopy(((char *)buf + slen), &val, sizeof(int));
+ dhd->wme_dp = (u8) ltoh32(val);
+ }
+
+ prot->pending = false;
+
+done:
+ dhd_os_proto_unblock(dhd);
+
+ return ret;
+}
+
+int
+dhd_prot_iovar_op(dhd_pub_t *dhdp, const char *name,
+ void *params, int plen, void *arg, int len, bool set)
+{
+ return BCME_UNSUPPORTED;
+}
+
+void dhd_prot_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf)
+{
+ bcm_bprintf(strbuf, "Protocol CDC: reqid %d\n", dhdp->prot->reqid);
+}
+
+void dhd_prot_hdrpush(dhd_pub_t *dhd, int ifidx, void *pktbuf)
+{
+#ifdef BDC
+ struct bdc_header *h;
+#endif /* BDC */
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+#ifdef BDC
+ /* Push BDC header used to convey priority for buses that don't */
+
+ PKTPUSH(pktbuf, BDC_HEADER_LEN);
+
+ h = (struct bdc_header *)PKTDATA(pktbuf);
+
+ h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT);
+ if (PKTSUMNEEDED(pktbuf))
+ h->flags |= BDC_FLAG_SUM_NEEDED;
+
+ h->priority = (PKTPRIO(pktbuf) & BDC_PRIORITY_MASK);
+ h->flags2 = 0;
+ h->rssi = 0;
+#endif /* BDC */
+ BDC_SET_IF_IDX(h, ifidx);
+}
+
+bool dhd_proto_fcinfo(dhd_pub_t *dhd, void *pktbuf, u8 * fcbits)
+{
+#ifdef BDC
+ struct bdc_header *h;
+
+ if (PKTLEN(pktbuf) < BDC_HEADER_LEN) {
+ DHD_ERROR(("%s: rx data too short (%d < %d)\n",
+ __func__, PKTLEN(pktbuf), BDC_HEADER_LEN));
+ return BCME_ERROR;
+ }
+
+ h = (struct bdc_header *)PKTDATA(pktbuf);
+
+ *fcbits = h->priority >> BDC_PRIORITY_FC_SHIFT;
+ if ((h->flags2 & BDC_FLAG2_FC_FLAG) == BDC_FLAG2_FC_FLAG)
+ return true;
+#endif
+ return false;
+}
+
+int dhd_prot_hdrpull(dhd_pub_t *dhd, int *ifidx, void *pktbuf)
+{
+#ifdef BDC
+ struct bdc_header *h;
+#endif
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+#ifdef BDC
+ /* Pop BDC header used to convey priority for buses that don't */
+
+ if (PKTLEN(pktbuf) < BDC_HEADER_LEN) {
+ DHD_ERROR(("%s: rx data too short (%d < %d)\n", __func__,
+ PKTLEN(pktbuf), BDC_HEADER_LEN));
+ return BCME_ERROR;
+ }
+
+ h = (struct bdc_header *)PKTDATA(pktbuf);
+
+ *ifidx = BDC_GET_IF_IDX(h);
+ if (*ifidx >= DHD_MAX_IFS) {
+ DHD_ERROR(("%s: rx data ifnum out of range (%d)\n",
+ __func__, *ifidx));
+ return BCME_ERROR;
+ }
+
+ if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) !=
+ BDC_PROTO_VER) {
+ DHD_ERROR(("%s: non-BDC packet received, flags 0x%x\n",
+ dhd_ifname(dhd, *ifidx), h->flags));
+ return BCME_ERROR;
+ }
+
+ if (h->flags & BDC_FLAG_SUM_GOOD) {
+ DHD_INFO(("%s: BDC packet received with good rx-csum, "
+ "flags 0x%x\n",
+ dhd_ifname(dhd, *ifidx), h->flags));
+ PKTSETSUMGOOD(pktbuf, true);
+ }
+
+ PKTSETPRIO(pktbuf, (h->priority & BDC_PRIORITY_MASK));
+
+ PKTPULL(pktbuf, BDC_HEADER_LEN);
+#endif /* BDC */
+
+ return 0;
+}
+
+int dhd_prot_attach(dhd_pub_t *dhd)
+{
+ dhd_prot_t *cdc;
+
+ cdc = kzalloc(sizeof(dhd_prot_t), GFP_ATOMIC);
+ if (!cdc) {
+ DHD_ERROR(("%s: kmalloc failed\n", __func__));
+ goto fail;
+ }
+
+ /* ensure that the msg buf directly follows the cdc msg struct */
+ if ((unsigned long)(&cdc->msg + 1) != (unsigned long)cdc->buf) {
+ DHD_ERROR(("dhd_prot_t is not correctly defined\n"));
+ goto fail;
+ }
+
+ dhd->prot = cdc;
+#ifdef BDC
+ dhd->hdrlen += BDC_HEADER_LEN;
+#endif
+ dhd->maxctl = WLC_IOCTL_MAXLEN + sizeof(cdc_ioctl_t) + ROUND_UP_MARGIN;
+ return 0;
+
+fail:
+ if (cdc != NULL)
+ kfree(cdc);
+ return BCME_NOMEM;
+}
+
+/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */
+void dhd_prot_detach(dhd_pub_t *dhd)
+{
+ kfree(dhd->prot);
+ dhd->prot = NULL;
+}
+
+void dhd_prot_dstats(dhd_pub_t *dhd)
+{
+ /* No stats from dongle added yet, copy bus stats */
+ dhd->dstats.tx_packets = dhd->tx_packets;
+ dhd->dstats.tx_errors = dhd->tx_errors;
+ dhd->dstats.rx_packets = dhd->rx_packets;
+ dhd->dstats.rx_errors = dhd->rx_errors;
+ dhd->dstats.rx_dropped = dhd->rx_dropped;
+ dhd->dstats.multicast = dhd->rx_multicast;
+ return;
+}
+
+int dhd_prot_init(dhd_pub_t *dhd)
+{
+ int ret = 0;
+ char buf[128];
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ dhd_os_proto_block(dhd);
+
+ /* Get the device MAC address */
+ strcpy(buf, "cur_etheraddr");
+ ret = dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, sizeof(buf));
+ if (ret < 0) {
+ dhd_os_proto_unblock(dhd);
+ return ret;
+ }
+ memcpy(dhd->mac.octet, buf, ETHER_ADDR_LEN);
+
+ dhd_os_proto_unblock(dhd);
+
+#ifdef EMBEDDED_PLATFORM
+ ret = dhd_preinit_ioctls(dhd);
+#endif /* EMBEDDED_PLATFORM */
+
+ /* Always assumes wl for now */
+ dhd->iswl = true;
+
+ return ret;
+}
+
+void dhd_prot_stop(dhd_pub_t *dhd)
+{
+ /* Nothing to do for CDC */
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_common.c b/drivers/staging/brcm80211/brcmfmac/dhd_common.c
new file mode 100644
index 000000000000..703188fc28ec
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_common.c
@@ -0,0 +1,1910 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <bcmdefs.h>
+#include <osl.h>
+#include <bcmutils.h>
+#include <bcmendian.h>
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhd_bus.h>
+#include <dhd_proto.h>
+#include <dhd_dbg.h>
+#include <msgtrace.h>
+#include <wlioctl.h>
+
+int dhd_msg_level;
+char fw_path[MOD_PARAM_PATHLEN];
+char nv_path[MOD_PARAM_PATHLEN];
+
+/* Last connection success/failure status */
+u32 dhd_conn_event;
+u32 dhd_conn_status;
+u32 dhd_conn_reason;
+
+#define htod32(i) i
+#define htod16(i) i
+#define dtoh32(i) i
+#define dtoh16(i) i
+
+extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf,
+ uint len);
+extern void dhd_ind_scan_confirm(void *h, bool status);
+extern int dhd_wl_ioctl(dhd_pub_t *dhd, uint cmd, char *buf, uint buflen);
+void dhd_iscan_lock(void);
+void dhd_iscan_unlock(void);
+
+/* Packet alignment for most efficient SDIO (can change based on platform) */
+#ifndef DHD_SDALIGN
+#define DHD_SDALIGN 32
+#endif
+#if !ISPOWEROF2(DHD_SDALIGN)
+#error DHD_SDALIGN is not a power of 2!
+#endif
+
+#ifdef DHD_DEBUG
+#define EPI_VERSION_STR "4.218.248.5"
+const char dhd_version[] =
+"Dongle Host Driver, version " EPI_VERSION_STR "\nCompiled on " __DATE__
+" at " __TIME__;
+#else
+const char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR;
+#endif
+
+void dhd_set_timer(void *bus, uint wdtick);
+
+/* IOVar table */
+enum {
+ IOV_VERSION = 1,
+ IOV_MSGLEVEL,
+ IOV_BCMERRORSTR,
+ IOV_BCMERROR,
+ IOV_WDTICK,
+ IOV_DUMP,
+#ifdef DHD_DEBUG
+ IOV_CONS,
+ IOV_DCONSOLE_POLL,
+#endif
+ IOV_CLEARCOUNTS,
+ IOV_LOGDUMP,
+ IOV_LOGCAL,
+ IOV_LOGSTAMP,
+ IOV_GPIOOB,
+ IOV_IOCTLTIMEOUT,
+ IOV_LAST
+};
+
+const bcm_iovar_t dhd_iovars[] = {
+ {"version", IOV_VERSION, 0, IOVT_BUFFER, sizeof(dhd_version)}
+ ,
+#ifdef DHD_DEBUG
+ {"msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0}
+ ,
+#endif /* DHD_DEBUG */
+ {"bcmerrorstr", IOV_BCMERRORSTR, 0, IOVT_BUFFER, BCME_STRLEN}
+ ,
+ {"bcmerror", IOV_BCMERROR, 0, IOVT_INT8, 0}
+ ,
+ {"wdtick", IOV_WDTICK, 0, IOVT_UINT32, 0}
+ ,
+ {"dump", IOV_DUMP, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN}
+ ,
+#ifdef DHD_DEBUG
+ {"dconpoll", IOV_DCONSOLE_POLL, 0, IOVT_UINT32, 0}
+ ,
+ {"cons", IOV_CONS, 0, IOVT_BUFFER, 0}
+ ,
+#endif
+ {"clearcounts", IOV_CLEARCOUNTS, 0, IOVT_VOID, 0}
+ ,
+ {"gpioob", IOV_GPIOOB, 0, IOVT_UINT32, 0}
+ ,
+ {"ioctl_timeout", IOV_IOCTLTIMEOUT, 0, IOVT_UINT32, 0}
+ ,
+ {NULL, 0, 0, 0, 0}
+};
+
+void dhd_common_init(void)
+{
+ /* Init global variables at run-time, not as part of the declaration.
+ * This is required to support init/de-init of the driver.
+ * Initialization
+ * of globals as part of the declaration results in non-deterministic
+ * behaviour since the value of the globals may be different on the
+ * first time that the driver is initialized vs subsequent
+ * initializations.
+ */
+ dhd_msg_level = DHD_ERROR_VAL;
+#ifdef CONFIG_BCM4329_FW_PATH
+ strncpy(fw_path, CONFIG_BCM4329_FW_PATH, MOD_PARAM_PATHLEN - 1);
+#else
+ fw_path[0] = '\0';
+#endif
+#ifdef CONFIG_BCM4329_NVRAM_PATH
+ strncpy(nv_path, CONFIG_BCM4329_NVRAM_PATH, MOD_PARAM_PATHLEN - 1);
+#else
+ nv_path[0] = '\0';
+#endif
+}
+
+static int dhd_dump(dhd_pub_t *dhdp, char *buf, int buflen)
+{
+ struct bcmstrbuf b;
+ struct bcmstrbuf *strbuf = &b;
+
+ bcm_binit(strbuf, buf, buflen);
+
+ /* Base DHD info */
+ bcm_bprintf(strbuf, "%s\n", dhd_version);
+ bcm_bprintf(strbuf, "\n");
+ bcm_bprintf(strbuf, "pub.up %d pub.txoff %d pub.busstate %d\n",
+ dhdp->up, dhdp->txoff, dhdp->busstate);
+ bcm_bprintf(strbuf, "pub.hdrlen %d pub.maxctl %d pub.rxsz %d\n",
+ dhdp->hdrlen, dhdp->maxctl, dhdp->rxsz);
+ bcm_bprintf(strbuf, "pub.iswl %d pub.drv_version %ld pub.mac %pM\n",
+ dhdp->iswl, dhdp->drv_version, &dhdp->mac);
+ bcm_bprintf(strbuf, "pub.bcmerror %d tickcnt %d\n", dhdp->bcmerror,
+ dhdp->tickcnt);
+
+ bcm_bprintf(strbuf, "dongle stats:\n");
+ bcm_bprintf(strbuf,
+ "tx_packets %ld tx_bytes %ld tx_errors %ld tx_dropped %ld\n",
+ dhdp->dstats.tx_packets, dhdp->dstats.tx_bytes,
+ dhdp->dstats.tx_errors, dhdp->dstats.tx_dropped);
+ bcm_bprintf(strbuf,
+ "rx_packets %ld rx_bytes %ld rx_errors %ld rx_dropped %ld\n",
+ dhdp->dstats.rx_packets, dhdp->dstats.rx_bytes,
+ dhdp->dstats.rx_errors, dhdp->dstats.rx_dropped);
+ bcm_bprintf(strbuf, "multicast %ld\n", dhdp->dstats.multicast);
+
+ bcm_bprintf(strbuf, "bus stats:\n");
+ bcm_bprintf(strbuf, "tx_packets %ld tx_multicast %ld tx_errors %ld\n",
+ dhdp->tx_packets, dhdp->tx_multicast, dhdp->tx_errors);
+ bcm_bprintf(strbuf, "tx_ctlpkts %ld tx_ctlerrs %ld\n",
+ dhdp->tx_ctlpkts, dhdp->tx_ctlerrs);
+ bcm_bprintf(strbuf, "rx_packets %ld rx_multicast %ld rx_errors %ld\n",
+ dhdp->rx_packets, dhdp->rx_multicast, dhdp->rx_errors);
+ bcm_bprintf(strbuf,
+ "rx_ctlpkts %ld rx_ctlerrs %ld rx_dropped %ld rx_flushed %ld\n",
+ dhdp->rx_ctlpkts, dhdp->rx_ctlerrs, dhdp->rx_dropped,
+ dhdp->rx_flushed);
+ bcm_bprintf(strbuf,
+ "rx_readahead_cnt %ld tx_realloc %ld fc_packets %ld\n",
+ dhdp->rx_readahead_cnt, dhdp->tx_realloc, dhdp->fc_packets);
+ bcm_bprintf(strbuf, "wd_dpc_sched %ld\n", dhdp->wd_dpc_sched);
+ bcm_bprintf(strbuf, "\n");
+
+ /* Add any prot info */
+ dhd_prot_dump(dhdp, strbuf);
+ bcm_bprintf(strbuf, "\n");
+
+ /* Add any bus info */
+ dhd_bus_dump(dhdp, strbuf);
+
+ return !strbuf->size ? BCME_BUFTOOSHORT : 0;
+}
+
+static int
+dhd_doiovar(dhd_pub_t *dhd_pub, const bcm_iovar_t *vi, u32 actionid,
+ const char *name, void *params, int plen, void *arg, int len,
+ int val_size)
+{
+ int bcmerror = 0;
+ s32 int_val = 0;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid));
+ if (bcmerror != 0)
+ goto exit;
+
+ if (plen >= (int)sizeof(int_val))
+ bcopy(params, &int_val, sizeof(int_val));
+
+ switch (actionid) {
+ case IOV_GVAL(IOV_VERSION):
+ /* Need to have checked buffer length */
+ strncpy((char *)arg, dhd_version, len);
+ break;
+
+ case IOV_GVAL(IOV_MSGLEVEL):
+ int_val = (s32) dhd_msg_level;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_MSGLEVEL):
+ dhd_msg_level = int_val;
+ break;
+
+ case IOV_GVAL(IOV_BCMERRORSTR):
+ strncpy((char *)arg, bcmerrorstr(dhd_pub->bcmerror),
+ BCME_STRLEN);
+ ((char *)arg)[BCME_STRLEN - 1] = 0x00;
+ break;
+
+ case IOV_GVAL(IOV_BCMERROR):
+ int_val = (s32) dhd_pub->bcmerror;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_GVAL(IOV_WDTICK):
+ int_val = (s32) dhd_watchdog_ms;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_WDTICK):
+ if (!dhd_pub->up) {
+ bcmerror = BCME_NOTUP;
+ break;
+ }
+ dhd_os_wd_timer(dhd_pub, (uint) int_val);
+ break;
+
+ case IOV_GVAL(IOV_DUMP):
+ bcmerror = dhd_dump(dhd_pub, arg, len);
+ break;
+
+#ifdef DHD_DEBUG
+ case IOV_GVAL(IOV_DCONSOLE_POLL):
+ int_val = (s32) dhd_console_ms;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_DCONSOLE_POLL):
+ dhd_console_ms = (uint) int_val;
+ break;
+
+ case IOV_SVAL(IOV_CONS):
+ if (len > 0)
+ bcmerror = dhd_bus_console_in(dhd_pub, arg, len - 1);
+ break;
+#endif
+
+ case IOV_SVAL(IOV_CLEARCOUNTS):
+ dhd_pub->tx_packets = dhd_pub->rx_packets = 0;
+ dhd_pub->tx_errors = dhd_pub->rx_errors = 0;
+ dhd_pub->tx_ctlpkts = dhd_pub->rx_ctlpkts = 0;
+ dhd_pub->tx_ctlerrs = dhd_pub->rx_ctlerrs = 0;
+ dhd_pub->rx_dropped = 0;
+ dhd_pub->rx_readahead_cnt = 0;
+ dhd_pub->tx_realloc = 0;
+ dhd_pub->wd_dpc_sched = 0;
+ memset(&dhd_pub->dstats, 0, sizeof(dhd_pub->dstats));
+ dhd_bus_clearcounts(dhd_pub);
+ break;
+
+ case IOV_GVAL(IOV_IOCTLTIMEOUT):{
+ int_val = (s32) dhd_os_get_ioctl_resp_timeout();
+ bcopy(&int_val, arg, sizeof(int_val));
+ break;
+ }
+
+ case IOV_SVAL(IOV_IOCTLTIMEOUT):{
+ if (int_val <= 0)
+ bcmerror = BCME_BADARG;
+ else
+ dhd_os_set_ioctl_resp_timeout((unsigned int)
+ int_val);
+ break;
+ }
+
+ default:
+ bcmerror = BCME_UNSUPPORTED;
+ break;
+ }
+
+exit:
+ return bcmerror;
+}
+
+/* Store the status of a connection attempt for later retrieval by an iovar */
+void dhd_store_conn_status(u32 event, u32 status, u32 reason)
+{
+ /* Do not overwrite a WLC_E_PRUNE with a WLC_E_SET_SSID
+ * because an encryption/rsn mismatch results in both events, and
+ * the important information is in the WLC_E_PRUNE.
+ */
+ if (!(event == WLC_E_SET_SSID && status == WLC_E_STATUS_FAIL &&
+ dhd_conn_event == WLC_E_PRUNE)) {
+ dhd_conn_event = event;
+ dhd_conn_status = status;
+ dhd_conn_reason = reason;
+ }
+}
+
+bool dhd_prec_enq(dhd_pub_t *dhdp, struct pktq *q, void *pkt, int prec)
+{
+ void *p;
+ int eprec = -1; /* precedence to evict from */
+ bool discard_oldest;
+
+ /* Fast case, precedence queue is not full and we are also not
+ * exceeding total queue length
+ */
+ if (!pktq_pfull(q, prec) && !pktq_full(q)) {
+ pktq_penq(q, prec, pkt);
+ return true;
+ }
+
+ /* Determine precedence from which to evict packet, if any */
+ if (pktq_pfull(q, prec))
+ eprec = prec;
+ else if (pktq_full(q)) {
+ p = pktq_peek_tail(q, &eprec);
+ ASSERT(p);
+ if (eprec > prec)
+ return false;
+ }
+
+ /* Evict if needed */
+ if (eprec >= 0) {
+ /* Detect queueing to unconfigured precedence */
+ ASSERT(!pktq_pempty(q, eprec));
+ discard_oldest = AC_BITMAP_TST(dhdp->wme_dp, eprec);
+ if (eprec == prec && !discard_oldest)
+ return false; /* refuse newer (incoming) packet */
+ /* Evict packet according to discard policy */
+ p = discard_oldest ? pktq_pdeq(q, eprec) : pktq_pdeq_tail(q,
+ eprec);
+ if (p == NULL) {
+ DHD_ERROR(("%s: pktq_penq() failed, oldest %d.",
+ __func__, discard_oldest));
+ ASSERT(p);
+ }
+
+ PKTFREE(dhdp->osh, p, true);
+ }
+
+ /* Enqueue */
+ p = pktq_penq(q, prec, pkt);
+ if (p == NULL) {
+ DHD_ERROR(("%s: pktq_penq() failed.", __func__));
+ ASSERT(p);
+ }
+
+ return true;
+}
+
+static int
+dhd_iovar_op(dhd_pub_t *dhd_pub, const char *name,
+ void *params, int plen, void *arg, int len, bool set)
+{
+ int bcmerror = 0;
+ int val_size;
+ const bcm_iovar_t *vi = NULL;
+ u32 actionid;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ASSERT(name);
+ ASSERT(len >= 0);
+
+ /* Get MUST have return space */
+ ASSERT(set || (arg && len));
+
+ /* Set does NOT take qualifiers */
+ ASSERT(!set || (!params && !plen));
+
+ vi = bcm_iovar_lookup(dhd_iovars, name);
+ if (vi == NULL) {
+ bcmerror = BCME_UNSUPPORTED;
+ goto exit;
+ }
+
+ DHD_CTL(("%s: %s %s, len %d plen %d\n", __func__,
+ name, (set ? "set" : "get"), len, plen));
+
+ /* set up 'params' pointer in case this is a set command so that
+ * the convenience int and bool code can be common to set and get
+ */
+ if (params == NULL) {
+ params = arg;
+ plen = len;
+ }
+
+ if (vi->type == IOVT_VOID)
+ val_size = 0;
+ else if (vi->type == IOVT_BUFFER)
+ val_size = len;
+ else
+ /* all other types are integer sized */
+ val_size = sizeof(int);
+
+ actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid);
+ bcmerror =
+ dhd_doiovar(dhd_pub, vi, actionid, name, params, plen, arg, len,
+ val_size);
+
+exit:
+ return bcmerror;
+}
+
+int dhd_ioctl(dhd_pub_t *dhd_pub, dhd_ioctl_t *ioc, void *buf, uint buflen)
+{
+ int bcmerror = 0;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (!buf)
+ return BCME_BADARG;
+
+ switch (ioc->cmd) {
+ case DHD_GET_MAGIC:
+ if (buflen < sizeof(int))
+ bcmerror = BCME_BUFTOOSHORT;
+ else
+ *(int *)buf = DHD_IOCTL_MAGIC;
+ break;
+
+ case DHD_GET_VERSION:
+ if (buflen < sizeof(int))
+ bcmerror = -BCME_BUFTOOSHORT;
+ else
+ *(int *)buf = DHD_IOCTL_VERSION;
+ break;
+
+ case DHD_GET_VAR:
+ case DHD_SET_VAR:{
+ char *arg;
+ uint arglen;
+
+ /* scan past the name to any arguments */
+ for (arg = buf, arglen = buflen; *arg && arglen;
+ arg++, arglen--)
+ ;
+
+ if (*arg) {
+ bcmerror = BCME_BUFTOOSHORT;
+ break;
+ }
+
+ /* account for the NUL terminator */
+ arg++, arglen--;
+
+ /* call with the appropriate arguments */
+ if (ioc->cmd == DHD_GET_VAR)
+ bcmerror =
+ dhd_iovar_op(dhd_pub, buf, arg, arglen, buf,
+ buflen, IOV_GET);
+ else
+ bcmerror =
+ dhd_iovar_op(dhd_pub, buf, NULL, 0, arg,
+ arglen, IOV_SET);
+ if (bcmerror != BCME_UNSUPPORTED)
+ break;
+
+ /* not in generic table, try protocol module */
+ if (ioc->cmd == DHD_GET_VAR)
+ bcmerror = dhd_prot_iovar_op(dhd_pub, buf, arg,
+ arglen, buf,
+ buflen, IOV_GET);
+ else
+ bcmerror = dhd_prot_iovar_op(dhd_pub, buf,
+ NULL, 0, arg,
+ arglen, IOV_SET);
+ if (bcmerror != BCME_UNSUPPORTED)
+ break;
+
+ /* if still not found, try bus module */
+ if (ioc->cmd == DHD_GET_VAR)
+ bcmerror = dhd_bus_iovar_op(dhd_pub, buf,
+ arg, arglen, buf,
+ buflen, IOV_GET);
+ else
+ bcmerror = dhd_bus_iovar_op(dhd_pub, buf,
+ NULL, 0, arg,
+ arglen, IOV_SET);
+
+ break;
+ }
+
+ default:
+ bcmerror = BCME_UNSUPPORTED;
+ }
+
+ return bcmerror;
+}
+
+#ifdef SHOW_EVENTS
+static void wl_show_host_event(wl_event_msg_t *event, void *event_data)
+{
+ uint i, status, reason;
+ bool group = false, flush_txq = false, link = false;
+ char *auth_str, *event_name;
+ unsigned char *buf;
+ char err_msg[256], eabuf[ETHER_ADDR_STR_LEN];
+ static struct {
+ uint event;
+ char *event_name;
+ } event_names[] = {
+ {
+ WLC_E_SET_SSID, "SET_SSID"}, {
+ WLC_E_JOIN, "JOIN"}, {
+ WLC_E_START, "START"}, {
+ WLC_E_AUTH, "AUTH"}, {
+ WLC_E_AUTH_IND, "AUTH_IND"}, {
+ WLC_E_DEAUTH, "DEAUTH"}, {
+ WLC_E_DEAUTH_IND, "DEAUTH_IND"}, {
+ WLC_E_ASSOC, "ASSOC"}, {
+ WLC_E_ASSOC_IND, "ASSOC_IND"}, {
+ WLC_E_REASSOC, "REASSOC"}, {
+ WLC_E_REASSOC_IND, "REASSOC_IND"}, {
+ WLC_E_DISASSOC, "DISASSOC"}, {
+ WLC_E_DISASSOC_IND, "DISASSOC_IND"}, {
+ WLC_E_QUIET_START, "START_QUIET"}, {
+ WLC_E_QUIET_END, "END_QUIET"}, {
+ WLC_E_BEACON_RX, "BEACON_RX"}, {
+ WLC_E_LINK, "LINK"}, {
+ WLC_E_MIC_ERROR, "MIC_ERROR"}, {
+ WLC_E_NDIS_LINK, "NDIS_LINK"}, {
+ WLC_E_ROAM, "ROAM"}, {
+ WLC_E_TXFAIL, "TXFAIL"}, {
+ WLC_E_PMKID_CACHE, "PMKID_CACHE"}, {
+ WLC_E_RETROGRADE_TSF, "RETROGRADE_TSF"}, {
+ WLC_E_PRUNE, "PRUNE"}, {
+ WLC_E_AUTOAUTH, "AUTOAUTH"}, {
+ WLC_E_EAPOL_MSG, "EAPOL_MSG"}, {
+ WLC_E_SCAN_COMPLETE, "SCAN_COMPLETE"}, {
+ WLC_E_ADDTS_IND, "ADDTS_IND"}, {
+ WLC_E_DELTS_IND, "DELTS_IND"}, {
+ WLC_E_BCNSENT_IND, "BCNSENT_IND"}, {
+ WLC_E_BCNRX_MSG, "BCNRX_MSG"}, {
+ WLC_E_BCNLOST_MSG, "BCNLOST_MSG"}, {
+ WLC_E_ROAM_PREP, "ROAM_PREP"}, {
+ WLC_E_PFN_NET_FOUND, "PNO_NET_FOUND"}, {
+ WLC_E_PFN_NET_LOST, "PNO_NET_LOST"}, {
+ WLC_E_RESET_COMPLETE, "RESET_COMPLETE"}, {
+ WLC_E_JOIN_START, "JOIN_START"}, {
+ WLC_E_ROAM_START, "ROAM_START"}, {
+ WLC_E_ASSOC_START, "ASSOC_START"}, {
+ WLC_E_IBSS_ASSOC, "IBSS_ASSOC"}, {
+ WLC_E_RADIO, "RADIO"}, {
+ WLC_E_PSM_WATCHDOG, "PSM_WATCHDOG"}, {
+ WLC_E_PROBREQ_MSG, "PROBREQ_MSG"}, {
+ WLC_E_SCAN_CONFIRM_IND, "SCAN_CONFIRM_IND"}, {
+ WLC_E_PSK_SUP, "PSK_SUP"}, {
+ WLC_E_COUNTRY_CODE_CHANGED, "COUNTRY_CODE_CHANGED"}, {
+ WLC_E_EXCEEDED_MEDIUM_TIME, "EXCEEDED_MEDIUM_TIME"}, {
+ WLC_E_ICV_ERROR, "ICV_ERROR"}, {
+ WLC_E_UNICAST_DECODE_ERROR, "UNICAST_DECODE_ERROR"}, {
+ WLC_E_MULTICAST_DECODE_ERROR, "MULTICAST_DECODE_ERROR"}, {
+ WLC_E_TRACE, "TRACE"}, {
+ WLC_E_ACTION_FRAME, "ACTION FRAME"}, {
+ WLC_E_ACTION_FRAME_COMPLETE, "ACTION FRAME TX COMPLETE"}, {
+ WLC_E_IF, "IF"}, {
+ WLC_E_RSSI, "RSSI"}, {
+ WLC_E_PFN_SCAN_COMPLETE, "SCAN_COMPLETE"}
+ };
+ uint event_type, flags, auth_type, datalen;
+ event_type = ntoh32(event->event_type);
+ flags = ntoh16(event->flags);
+ status = ntoh32(event->status);
+ reason = ntoh32(event->reason);
+ auth_type = ntoh32(event->auth_type);
+ datalen = ntoh32(event->datalen);
+ /* debug dump of event messages */
+ sprintf(eabuf, "%pM", event->addr.octet);
+
+ event_name = "UNKNOWN";
+ for (i = 0; i < ARRAY_SIZE(event_names); i++) {
+ if (event_names[i].event == event_type)
+ event_name = event_names[i].event_name;
+ }
+
+ DHD_EVENT(("EVENT: %s, event ID = %d\n", event_name, event_type));
+
+ if (flags & WLC_EVENT_MSG_LINK)
+ link = true;
+ if (flags & WLC_EVENT_MSG_GROUP)
+ group = true;
+ if (flags & WLC_EVENT_MSG_FLUSHTXQ)
+ flush_txq = true;
+
+ switch (event_type) {
+ case WLC_E_START:
+ case WLC_E_DEAUTH:
+ case WLC_E_DISASSOC:
+ DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf));
+ break;
+
+ case WLC_E_ASSOC_IND:
+ case WLC_E_REASSOC_IND:
+ DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf));
+ break;
+
+ case WLC_E_ASSOC:
+ case WLC_E_REASSOC:
+ if (status == WLC_E_STATUS_SUCCESS) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, SUCCESS\n",
+ event_name, eabuf));
+ } else if (status == WLC_E_STATUS_TIMEOUT) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, TIMEOUT\n",
+ event_name, eabuf));
+ } else if (status == WLC_E_STATUS_FAIL) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, FAILURE, reason %d\n",
+ event_name, eabuf, (int)reason));
+ } else {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, unexpected status "
+ "%d\n", event_name, eabuf, (int)status));
+ }
+ break;
+
+ case WLC_E_DEAUTH_IND:
+ case WLC_E_DISASSOC_IND:
+ DHD_EVENT(("MACEVENT: %s, MAC %s, reason %d\n", event_name,
+ eabuf, (int)reason));
+ break;
+
+ case WLC_E_AUTH:
+ case WLC_E_AUTH_IND:
+ if (auth_type == DOT11_OPEN_SYSTEM)
+ auth_str = "Open System";
+ else if (auth_type == DOT11_SHARED_KEY)
+ auth_str = "Shared Key";
+ else {
+ sprintf(err_msg, "AUTH unknown: %d", (int)auth_type);
+ auth_str = err_msg;
+ }
+ if (event_type == WLC_E_AUTH_IND) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, %s\n", event_name,
+ eabuf, auth_str));
+ } else if (status == WLC_E_STATUS_SUCCESS) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, %s, SUCCESS\n",
+ event_name, eabuf, auth_str));
+ } else if (status == WLC_E_STATUS_TIMEOUT) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, %s, TIMEOUT\n",
+ event_name, eabuf, auth_str));
+ } else if (status == WLC_E_STATUS_FAIL) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s, %s, FAILURE, "
+ "reason %d\n",
+ event_name, eabuf, auth_str, (int)reason));
+ }
+
+ break;
+
+ case WLC_E_JOIN:
+ case WLC_E_ROAM:
+ case WLC_E_SET_SSID:
+ if (status == WLC_E_STATUS_SUCCESS) {
+ DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name,
+ eabuf));
+ } else if (status == WLC_E_STATUS_FAIL) {
+ DHD_EVENT(("MACEVENT: %s, failed\n", event_name));
+ } else if (status == WLC_E_STATUS_NO_NETWORKS) {
+ DHD_EVENT(("MACEVENT: %s, no networks found\n",
+ event_name));
+ } else {
+ DHD_EVENT(("MACEVENT: %s, unexpected status %d\n",
+ event_name, (int)status));
+ }
+ break;
+
+ case WLC_E_BEACON_RX:
+ if (status == WLC_E_STATUS_SUCCESS) {
+ DHD_EVENT(("MACEVENT: %s, SUCCESS\n", event_name));
+ } else if (status == WLC_E_STATUS_FAIL) {
+ DHD_EVENT(("MACEVENT: %s, FAIL\n", event_name));
+ } else {
+ DHD_EVENT(("MACEVENT: %s, status %d\n", event_name,
+ status));
+ }
+ break;
+
+ case WLC_E_LINK:
+ DHD_EVENT(("MACEVENT: %s %s\n", event_name,
+ link ? "UP" : "DOWN"));
+ break;
+
+ case WLC_E_MIC_ERROR:
+ DHD_EVENT(("MACEVENT: %s, MAC %s, Group %d, Flush %d\n",
+ event_name, eabuf, group, flush_txq));
+ break;
+
+ case WLC_E_ICV_ERROR:
+ case WLC_E_UNICAST_DECODE_ERROR:
+ case WLC_E_MULTICAST_DECODE_ERROR:
+ DHD_EVENT(("MACEVENT: %s, MAC %s\n", event_name, eabuf));
+ break;
+
+ case WLC_E_TXFAIL:
+ DHD_EVENT(("MACEVENT: %s, RA %s\n", event_name, eabuf));
+ break;
+
+ case WLC_E_SCAN_COMPLETE:
+ case WLC_E_PMKID_CACHE:
+ DHD_EVENT(("MACEVENT: %s\n", event_name));
+ break;
+
+ case WLC_E_PFN_NET_FOUND:
+ case WLC_E_PFN_NET_LOST:
+ case WLC_E_PFN_SCAN_COMPLETE:
+ DHD_EVENT(("PNOEVENT: %s\n", event_name));
+ break;
+
+ case WLC_E_PSK_SUP:
+ case WLC_E_PRUNE:
+ DHD_EVENT(("MACEVENT: %s, status %d, reason %d\n",
+ event_name, (int)status, (int)reason));
+ break;
+
+ case WLC_E_TRACE:
+ {
+ static u32 seqnum_prev;
+ msgtrace_hdr_t hdr;
+ u32 nblost;
+ char *s, *p;
+
+ buf = (unsigned char *) event_data;
+ memcpy(&hdr, buf, MSGTRACE_HDRLEN);
+
+ if (hdr.version != MSGTRACE_VERSION) {
+ printf
+ ("\nMACEVENT: %s [unsupported version --> "
+ "dhd version:%d dongle version:%d]\n",
+ event_name, MSGTRACE_VERSION, hdr.version);
+ /* Reset datalen to avoid display below */
+ datalen = 0;
+ break;
+ }
+
+ /* There are 2 bytes available at the end of data */
+ buf[MSGTRACE_HDRLEN + ntoh16(hdr.len)] = '\0';
+
+ if (ntoh32(hdr.discarded_bytes)
+ || ntoh32(hdr.discarded_printf)) {
+ printf
+ ("\nWLC_E_TRACE: [Discarded traces in dongle -->"
+ "discarded_bytes %d discarded_printf %d]\n",
+ ntoh32(hdr.discarded_bytes),
+ ntoh32(hdr.discarded_printf));
+ }
+
+ nblost = ntoh32(hdr.seqnum) - seqnum_prev - 1;
+ if (nblost > 0) {
+ printf
+ ("\nWLC_E_TRACE: [Event lost --> seqnum %d nblost %d\n",
+ ntoh32(hdr.seqnum), nblost);
+ }
+ seqnum_prev = ntoh32(hdr.seqnum);
+
+ /* Display the trace buffer. Advance from \n to \n to
+ * avoid display big
+ * printf (issue with Linux printk )
+ */
+ p = (char *)&buf[MSGTRACE_HDRLEN];
+ while ((s = strstr(p, "\n")) != NULL) {
+ *s = '\0';
+ printf("%s\n", p);
+ p = s + 1;
+ }
+ printf("%s\n", p);
+
+ /* Reset datalen to avoid display below */
+ datalen = 0;
+ }
+ break;
+
+ case WLC_E_RSSI:
+ DHD_EVENT(("MACEVENT: %s %d\n", event_name,
+ ntoh32(*((int *)event_data))));
+ break;
+
+ default:
+ DHD_EVENT(("MACEVENT: %s %d, MAC %s, status %d, reason %d, "
+ "auth %d\n", event_name, event_type, eabuf,
+ (int)status, (int)reason, (int)auth_type));
+ break;
+ }
+
+ /* show any appended data */
+ if (datalen) {
+ buf = (unsigned char *) event_data;
+ DHD_EVENT((" data (%d) : ", datalen));
+ for (i = 0; i < datalen; i++)
+ DHD_EVENT((" 0x%02x ", *buf++));
+ DHD_EVENT(("\n"));
+ }
+}
+#endif /* SHOW_EVENTS */
+
+int
+wl_host_event(struct dhd_info *dhd, int *ifidx, void *pktdata,
+ wl_event_msg_t *event, void **data_ptr)
+{
+ /* check whether packet is a BRCM event pkt */
+ bcm_event_t *pvt_data = (bcm_event_t *) pktdata;
+ char *event_data;
+ u32 type, status;
+ u16 flags;
+ int evlen;
+
+ if (bcmp(BRCM_OUI, &pvt_data->bcm_hdr.oui[0], DOT11_OUI_LEN)) {
+ DHD_ERROR(("%s: mismatched OUI, bailing\n", __func__));
+ return BCME_ERROR;
+ }
+
+ /* BRCM event pkt may be unaligned - use xxx_ua to load user_subtype. */
+ if (ntoh16_ua((void *)&pvt_data->bcm_hdr.usr_subtype) !=
+ BCMILCP_BCM_SUBTYPE_EVENT) {
+ DHD_ERROR(("%s: mismatched subtype, bailing\n", __func__));
+ return BCME_ERROR;
+ }
+
+ *data_ptr = &pvt_data[1];
+ event_data = *data_ptr;
+
+ /* memcpy since BRCM event pkt may be unaligned. */
+ memcpy(event, &pvt_data->event, sizeof(wl_event_msg_t));
+
+ type = ntoh32_ua((void *)&event->event_type);
+ flags = ntoh16_ua((void *)&event->flags);
+ status = ntoh32_ua((void *)&event->status);
+ evlen = ntoh32_ua((void *)&event->datalen) + sizeof(bcm_event_t);
+
+ switch (type) {
+ case WLC_E_IF:
+ {
+ dhd_if_event_t *ifevent = (dhd_if_event_t *) event_data;
+ DHD_TRACE(("%s: if event\n", __func__));
+
+ if (ifevent->ifidx > 0 &&
+ ifevent->ifidx < DHD_MAX_IFS) {
+ if (ifevent->action == WLC_E_IF_ADD)
+ dhd_add_if(dhd, ifevent->ifidx,
+ NULL, event->ifname,
+ pvt_data->eth.ether_dhost,
+ ifevent->flags,
+ ifevent->bssidx);
+ else
+ dhd_del_if(dhd, ifevent->ifidx);
+ } else {
+ DHD_ERROR(("%s: Invalid ifidx %d for %s\n",
+ __func__, ifevent->ifidx,
+ event->ifname));
+ }
+ }
+ /* send up the if event: btamp user needs it */
+ *ifidx = dhd_ifname2idx(dhd, event->ifname);
+ /* push up to external supp/auth */
+ dhd_event(dhd, (char *)pvt_data, evlen, *ifidx);
+ break;
+
+#ifdef P2P
+ case WLC_E_NDIS_LINK:
+ break;
+#endif
+ /* fall through */
+ /* These are what external supplicant/authenticator wants */
+ case WLC_E_LINK:
+ case WLC_E_ASSOC_IND:
+ case WLC_E_REASSOC_IND:
+ case WLC_E_DISASSOC_IND:
+ case WLC_E_MIC_ERROR:
+ default:
+ /* Fall through: this should get _everything_ */
+
+ *ifidx = dhd_ifname2idx(dhd, event->ifname);
+ /* push up to external supp/auth */
+ dhd_event(dhd, (char *)pvt_data, evlen, *ifidx);
+ DHD_TRACE(("%s: MAC event %d, flags %x, status %x\n",
+ __func__, type, flags, status));
+
+ /* put it back to WLC_E_NDIS_LINK */
+ if (type == WLC_E_NDIS_LINK) {
+ u32 temp;
+
+ temp = ntoh32_ua((void *)&event->event_type);
+ DHD_TRACE(("Converted to WLC_E_LINK type %d\n", temp));
+
+ temp = ntoh32(WLC_E_NDIS_LINK);
+ memcpy((void *)(&pvt_data->event.event_type), &temp,
+ sizeof(pvt_data->event.event_type));
+ }
+ break;
+ }
+
+#ifdef SHOW_EVENTS
+ wl_show_host_event(event, event_data);
+#endif /* SHOW_EVENTS */
+
+ return BCME_OK;
+}
+
+void wl_event_to_host_order(wl_event_msg_t *evt)
+{
+ /* Event struct members passed from dongle to host are stored
+ * in network
+ * byte order. Convert all members to host-order.
+ */
+ evt->event_type = ntoh32(evt->event_type);
+ evt->flags = ntoh16(evt->flags);
+ evt->status = ntoh32(evt->status);
+ evt->reason = ntoh32(evt->reason);
+ evt->auth_type = ntoh32(evt->auth_type);
+ evt->datalen = ntoh32(evt->datalen);
+ evt->version = ntoh16(evt->version);
+}
+
+void print_buf(void *pbuf, int len, int bytes_per_line)
+{
+ int i, j = 0;
+ unsigned char *buf = pbuf;
+
+ if (bytes_per_line == 0)
+ bytes_per_line = len;
+
+ for (i = 0; i < len; i++) {
+ printf("%2.2x", *buf++);
+ j++;
+ if (j == bytes_per_line) {
+ printf("\n");
+ j = 0;
+ } else {
+ printf(":");
+ }
+ }
+ printf("\n");
+}
+
+/* Convert user's input in hex pattern to byte-size mask */
+static int wl_pattern_atoh(char *src, char *dst)
+{
+ int i;
+ if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) {
+ DHD_ERROR(("Mask invalid format. Needs to start with 0x\n"));
+ return -1;
+ }
+ src = src + 2; /* Skip past 0x */
+ if (strlen(src) % 2 != 0) {
+ DHD_ERROR(("Mask invalid format. Length must be even.\n"));
+ return -1;
+ }
+ for (i = 0; *src != '\0'; i++) {
+ char num[3];
+ strncpy(num, src, 2);
+ num[2] = '\0';
+ dst[i] = (u8) simple_strtoul(num, NULL, 16);
+ src += 2;
+ }
+ return i;
+}
+
+void
+dhd_pktfilter_offload_enable(dhd_pub_t *dhd, char *arg, int enable,
+ int master_mode)
+{
+ char *argv[8];
+ int i = 0;
+ const char *str;
+ int buf_len;
+ int str_len;
+ char *arg_save = 0, *arg_org = 0;
+ int rc;
+ char buf[128];
+ wl_pkt_filter_enable_t enable_parm;
+ wl_pkt_filter_enable_t *pkt_filterp;
+
+ arg_save = kmalloc(strlen(arg) + 1, GFP_ATOMIC);
+ if (!arg_save) {
+ DHD_ERROR(("%s: kmalloc failed\n", __func__));
+ goto fail;
+ }
+ arg_org = arg_save;
+ memcpy(arg_save, arg, strlen(arg) + 1);
+
+ argv[i] = strsep(&arg_save, " ");
+
+ i = 0;
+ if (NULL == argv[i]) {
+ DHD_ERROR(("No args provided\n"));
+ goto fail;
+ }
+
+ str = "pkt_filter_enable";
+ str_len = strlen(str);
+ strncpy(buf, str, str_len);
+ buf[str_len] = '\0';
+ buf_len = str_len + 1;
+
+ pkt_filterp = (wl_pkt_filter_enable_t *) (buf + str_len + 1);
+
+ /* Parse packet filter id. */
+ enable_parm.id = htod32(simple_strtoul(argv[i], NULL, 0));
+
+ /* Parse enable/disable value. */
+ enable_parm.enable = htod32(enable);
+
+ buf_len += sizeof(enable_parm);
+ memcpy((char *)pkt_filterp, &enable_parm, sizeof(enable_parm));
+
+ /* Enable/disable the specified filter. */
+ rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, buf_len);
+ rc = rc >= 0 ? 0 : rc;
+ if (rc)
+ DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n",
+ __func__, arg, rc));
+ else
+ DHD_TRACE(("%s: successfully added pktfilter %s\n",
+ __func__, arg));
+
+ /* Contorl the master mode */
+ bcm_mkiovar("pkt_filter_mode", (char *)&master_mode, 4, buf,
+ sizeof(buf));
+ rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, sizeof(buf));
+ rc = rc >= 0 ? 0 : rc;
+ if (rc)
+ DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n",
+ __func__, arg, rc));
+
+fail:
+ if (arg_org)
+ kfree(arg_org);
+}
+
+void dhd_pktfilter_offload_set(dhd_pub_t *dhd, char *arg)
+{
+ const char *str;
+ wl_pkt_filter_t pkt_filter;
+ wl_pkt_filter_t *pkt_filterp;
+ int buf_len;
+ int str_len;
+ int rc;
+ u32 mask_size;
+ u32 pattern_size;
+ char *argv[8], *buf = 0;
+ int i = 0;
+ char *arg_save = 0, *arg_org = 0;
+#define BUF_SIZE 2048
+
+ arg_save = kmalloc(strlen(arg) + 1, GFP_ATOMIC);
+ if (!arg_save) {
+ DHD_ERROR(("%s: kmalloc failed\n", __func__));
+ goto fail;
+ }
+
+ arg_org = arg_save;
+
+ buf = kmalloc(BUF_SIZE, GFP_ATOMIC);
+ if (!buf) {
+ DHD_ERROR(("%s: kmalloc failed\n", __func__));
+ goto fail;
+ }
+
+ memcpy(arg_save, arg, strlen(arg) + 1);
+
+ if (strlen(arg) > BUF_SIZE) {
+ DHD_ERROR(("Not enough buffer %d < %d\n", (int)strlen(arg),
+ (int)sizeof(buf)));
+ goto fail;
+ }
+
+ argv[i] = strsep(&arg_save, " ");
+ while (argv[i++])
+ argv[i] = strsep(&arg_save, " ");
+
+ i = 0;
+ if (NULL == argv[i]) {
+ DHD_ERROR(("No args provided\n"));
+ goto fail;
+ }
+
+ str = "pkt_filter_add";
+ str_len = strlen(str);
+ strncpy(buf, str, str_len);
+ buf[str_len] = '\0';
+ buf_len = str_len + 1;
+
+ pkt_filterp = (wl_pkt_filter_t *) (buf + str_len + 1);
+
+ /* Parse packet filter id. */
+ pkt_filter.id = htod32(simple_strtoul(argv[i], NULL, 0));
+
+ if (NULL == argv[++i]) {
+ DHD_ERROR(("Polarity not provided\n"));
+ goto fail;
+ }
+
+ /* Parse filter polarity. */
+ pkt_filter.negate_match = htod32(simple_strtoul(argv[i], NULL, 0));
+
+ if (NULL == argv[++i]) {
+ DHD_ERROR(("Filter type not provided\n"));
+ goto fail;
+ }
+
+ /* Parse filter type. */
+ pkt_filter.type = htod32(simple_strtoul(argv[i], NULL, 0));
+
+ if (NULL == argv[++i]) {
+ DHD_ERROR(("Offset not provided\n"));
+ goto fail;
+ }
+
+ /* Parse pattern filter offset. */
+ pkt_filter.u.pattern.offset = htod32(simple_strtoul(argv[i], NULL, 0));
+
+ if (NULL == argv[++i]) {
+ DHD_ERROR(("Bitmask not provided\n"));
+ goto fail;
+ }
+
+ /* Parse pattern filter mask. */
+ mask_size =
+ htod32(wl_pattern_atoh
+ (argv[i], (char *)pkt_filterp->u.pattern.mask_and_pattern));
+
+ if (NULL == argv[++i]) {
+ DHD_ERROR(("Pattern not provided\n"));
+ goto fail;
+ }
+
+ /* Parse pattern filter pattern. */
+ pattern_size =
+ htod32(wl_pattern_atoh(argv[i],
+ (char *)&pkt_filterp->u.pattern.
+ mask_and_pattern[mask_size]));
+
+ if (mask_size != pattern_size) {
+ DHD_ERROR(("Mask and pattern not the same size\n"));
+ goto fail;
+ }
+
+ pkt_filter.u.pattern.size_bytes = mask_size;
+ buf_len += WL_PKT_FILTER_FIXED_LEN;
+ buf_len += (WL_PKT_FILTER_PATTERN_FIXED_LEN + 2 * mask_size);
+
+ /* Keep-alive attributes are set in local
+ * variable (keep_alive_pkt), and
+ ** then memcpy'ed into buffer (keep_alive_pktp) since there is no
+ ** guarantee that the buffer is properly aligned.
+ */
+ memcpy((char *)pkt_filterp,
+ &pkt_filter,
+ WL_PKT_FILTER_FIXED_LEN + WL_PKT_FILTER_PATTERN_FIXED_LEN);
+
+ rc = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, buf_len);
+ rc = rc >= 0 ? 0 : rc;
+
+ if (rc)
+ DHD_TRACE(("%s: failed to add pktfilter %s, retcode = %d\n",
+ __func__, arg, rc));
+ else
+ DHD_TRACE(("%s: successfully added pktfilter %s\n",
+ __func__, arg));
+
+fail:
+ if (arg_org)
+ kfree(arg_org);
+
+ if (buf)
+ kfree(buf);
+}
+
+void dhd_arp_offload_set(dhd_pub_t *dhd, int arp_mode)
+{
+ char iovbuf[32];
+ int retcode;
+
+ bcm_mkiovar("arp_ol", (char *)&arp_mode, 4, iovbuf, sizeof(iovbuf));
+ retcode = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ retcode = retcode >= 0 ? 0 : retcode;
+ if (retcode)
+ DHD_TRACE(("%s: failed to set ARP offload mode to 0x%x, "
+ "retcode = %d\n", __func__, arp_mode, retcode));
+ else
+ DHD_TRACE(("%s: successfully set ARP offload mode to 0x%x\n",
+ __func__, arp_mode));
+}
+
+void dhd_arp_offload_enable(dhd_pub_t *dhd, int arp_enable)
+{
+ char iovbuf[32];
+ int retcode;
+
+ bcm_mkiovar("arpoe", (char *)&arp_enable, 4, iovbuf, sizeof(iovbuf));
+ retcode = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ retcode = retcode >= 0 ? 0 : retcode;
+ if (retcode)
+ DHD_TRACE(("%s: failed to enabe ARP offload to %d, "
+ "retcode = %d\n", __func__, arp_enable, retcode));
+ else
+ DHD_TRACE(("%s: successfully enabed ARP offload to %d\n",
+ __func__, arp_enable));
+}
+
+int dhd_preinit_ioctls(dhd_pub_t *dhd)
+{
+ char iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for
+ "event_msgs" + '\0' + bitvec */
+ uint up = 0;
+ char buf[128], *ptr;
+ uint power_mode = PM_FAST;
+ u32 dongle_align = DHD_SDALIGN;
+ u32 glom = 0;
+ uint bcn_timeout = 3;
+ int scan_assoc_time = 40;
+ int scan_unassoc_time = 40;
+#ifdef GET_CUSTOM_MAC_ENABLE
+ int ret = 0;
+ struct ether_addr ea_addr;
+#endif /* GET_CUSTOM_MAC_ENABLE */
+
+ dhd_os_proto_block(dhd);
+
+#ifdef GET_CUSTOM_MAC_ENABLE
+ /* Read MAC address from external customer place
+ ** NOTE that default mac address has to be present in
+ ** otp or nvram file to bring up
+ ** firmware but unique per board mac address maybe provided by
+ ** customer code
+ */
+ ret = dhd_custom_get_mac_address(ea_addr.octet);
+ if (!ret) {
+ bcm_mkiovar("cur_etheraddr", (void *)&ea_addr, ETHER_ADDR_LEN,
+ buf, sizeof(buf));
+ ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, buf, sizeof(buf));
+ if (ret < 0) {
+ DHD_ERROR(("%s: can't set MAC address , error=%d\n",
+ __func__, ret));
+ } else
+ memcpy(dhd->mac.octet, (void *)&ea_addr,
+ ETHER_ADDR_LEN);
+ }
+#endif /* GET_CUSTOM_MAC_ENABLE */
+
+ /* Set Country code */
+ if (dhd->country_code[0] != 0) {
+ if (dhdcdc_set_ioctl(dhd, 0, WLC_SET_COUNTRY,
+ dhd->country_code,
+ sizeof(dhd->country_code)) < 0) {
+ DHD_ERROR(("%s: country code setting failed\n",
+ __func__));
+ }
+ }
+
+ /* query for 'ver' to get version info from firmware */
+ memset(buf, 0, sizeof(buf));
+ ptr = buf;
+ bcm_mkiovar("ver", 0, 0, buf, sizeof(buf));
+ dhdcdc_query_ioctl(dhd, 0, WLC_GET_VAR, buf, sizeof(buf));
+ strsep(&ptr, "\n");
+ /* Print fw version info */
+ DHD_ERROR(("Firmware version = %s\n", buf));
+
+ /* Set PowerSave mode */
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM, (char *)&power_mode,
+ sizeof(power_mode));
+
+ /* Match Host and Dongle rx alignment */
+ bcm_mkiovar("bus:txglomalign", (char *)&dongle_align, 4, iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ /* disable glom option per default */
+ bcm_mkiovar("bus:txglom", (char *)&glom, 4, iovbuf, sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ /* Setup timeout if Beacons are lost and roam is off to report
+ link down */
+ bcm_mkiovar("bcn_timeout", (char *)&bcn_timeout, 4, iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ /* Enable/Disable build-in roaming to allowed ext supplicant to take
+ of romaing */
+ bcm_mkiovar("roam_off", (char *)&dhd_roam, 4, iovbuf, sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ /* Force STA UP */
+ if (dhd_radio_up)
+ dhdcdc_set_ioctl(dhd, 0, WLC_UP, (char *)&up, sizeof(up));
+
+ /* Setup event_msgs */
+ bcm_mkiovar("event_msgs", dhd->eventmask, WL_EVENTING_MASK_LEN, iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_SCAN_CHANNEL_TIME,
+ (char *)&scan_assoc_time, sizeof(scan_assoc_time));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_SCAN_UNASSOC_TIME,
+ (char *)&scan_unassoc_time, sizeof(scan_unassoc_time));
+
+#ifdef ARP_OFFLOAD_SUPPORT
+ /* Set and enable ARP offload feature */
+ if (dhd_arp_enable)
+ dhd_arp_offload_set(dhd, dhd_arp_mode);
+ dhd_arp_offload_enable(dhd, dhd_arp_enable);
+#endif /* ARP_OFFLOAD_SUPPORT */
+
+#ifdef PKT_FILTER_SUPPORT
+ {
+ int i;
+ /* Set up pkt filter */
+ if (dhd_pkt_filter_enable) {
+ for (i = 0; i < dhd->pktfilter_count; i++) {
+ dhd_pktfilter_offload_set(dhd,
+ dhd->pktfilter[i]);
+ dhd_pktfilter_offload_enable(dhd,
+ dhd->pktfilter[i],
+ dhd_pkt_filter_init,
+ dhd_master_mode);
+ }
+ }
+ }
+#endif /* PKT_FILTER_SUPPORT */
+
+ dhd_os_proto_unblock(dhd);
+
+ return 0;
+}
+
+#ifdef SIMPLE_ISCAN
+uint iscan_thread_id;
+iscan_buf_t *iscan_chain;
+
+iscan_buf_t *dhd_iscan_allocate_buf(dhd_pub_t *dhd, iscan_buf_t **iscanbuf)
+{
+ iscan_buf_t *iscanbuf_alloc = 0;
+ iscan_buf_t *iscanbuf_head;
+
+ dhd_iscan_lock();
+
+ iscanbuf_alloc = kmalloc(sizeof(iscan_buf_t), GFP_ATOMIC);
+ if (iscanbuf_alloc == NULL)
+ goto fail;
+
+ iscanbuf_alloc->next = NULL;
+ iscanbuf_head = *iscanbuf;
+
+ DHD_ISCAN(("%s: addr of allocated node = 0x%X"
+ "addr of iscanbuf_head = 0x%X dhd = 0x%X\n",
+ __func__, iscanbuf_alloc, iscanbuf_head, dhd));
+
+ if (iscanbuf_head == NULL) {
+ *iscanbuf = iscanbuf_alloc;
+ DHD_ISCAN(("%s: Head is allocated\n", __func__));
+ goto fail;
+ }
+
+ while (iscanbuf_head->next)
+ iscanbuf_head = iscanbuf_head->next;
+
+ iscanbuf_head->next = iscanbuf_alloc;
+
+fail:
+ dhd_iscan_unlock();
+ return iscanbuf_alloc;
+}
+
+void dhd_iscan_free_buf(void *dhdp, iscan_buf_t *iscan_delete)
+{
+ iscan_buf_t *iscanbuf_free = 0;
+ iscan_buf_t *iscanbuf_prv = 0;
+ iscan_buf_t *iscanbuf_cur = iscan_chain;
+ dhd_pub_t *dhd = dhd_bus_pub(dhdp);
+
+ dhd_iscan_lock();
+ /* If iscan_delete is null then delete the entire
+ * chain or else delete specific one provided
+ */
+ if (!iscan_delete) {
+ while (iscanbuf_cur) {
+ iscanbuf_free = iscanbuf_cur;
+ iscanbuf_cur = iscanbuf_cur->next;
+ iscanbuf_free->next = 0;
+ kfree(iscanbuf_free);
+ }
+ iscan_chain = 0;
+ } else {
+ while (iscanbuf_cur) {
+ if (iscanbuf_cur == iscan_delete)
+ break;
+ iscanbuf_prv = iscanbuf_cur;
+ iscanbuf_cur = iscanbuf_cur->next;
+ }
+ if (iscanbuf_prv)
+ iscanbuf_prv->next = iscan_delete->next;
+
+ iscan_delete->next = 0;
+ kfree(iscan_delete);
+
+ if (!iscanbuf_prv)
+ iscan_chain = 0;
+ }
+ dhd_iscan_unlock();
+}
+
+iscan_buf_t *dhd_iscan_result_buf(void)
+{
+ return iscan_chain;
+}
+
+/*
+* print scan cache
+* print partial iscan_skip list differently
+*/
+int dhd_iscan_print_cache(iscan_buf_t *iscan_skip)
+{
+ int i = 0, l = 0;
+ iscan_buf_t *iscan_cur;
+ wl_iscan_results_t *list;
+ wl_scan_results_t *results;
+ wl_bss_info_t UNALIGNED *bi;
+
+ dhd_iscan_lock();
+
+ iscan_cur = dhd_iscan_result_buf();
+
+ while (iscan_cur) {
+ list = (wl_iscan_results_t *)iscan_cur->iscan_buf;
+ if (!list)
+ break;
+
+ results = (wl_scan_results_t *)&list->results;
+ if (!results)
+ break;
+
+ if (results->version != WL_BSS_INFO_VERSION) {
+ DHD_ISCAN(("%s: results->version %d != "
+ "WL_BSS_INFO_VERSION\n",
+ __func__, results->version));
+ goto done;
+ }
+
+ bi = results->bss_info;
+ for (i = 0; i < results->count; i++) {
+ if (!bi)
+ break;
+
+ DHD_ISCAN(("%s[%2.2d:%2.2d] %X:%X:%X:%X:%X:%X\n",
+ iscan_cur != iscan_skip ? "BSS" : "bss", l,
+ i, bi->BSSID.octet[0], bi->BSSID.octet[1],
+ bi->BSSID.octet[2], bi->BSSID.octet[3],
+ bi->BSSID.octet[4], bi->BSSID.octet[5]));
+
+ bi = (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32(bi->length));
+ }
+ iscan_cur = iscan_cur->next;
+ l++;
+ }
+
+done:
+ dhd_iscan_unlock();
+ return 0;
+}
+
+/*
+* delete disappeared AP from specific scan cache but skip partial
+* list in iscan_skip
+*/
+int dhd_iscan_delete_bss(void *dhdp, void *addr, iscan_buf_t *iscan_skip)
+{
+ int i = 0, j = 0, l = 0;
+ iscan_buf_t *iscan_cur;
+ wl_iscan_results_t *list;
+ wl_scan_results_t *results;
+ wl_bss_info_t UNALIGNED *bi, *bi_new, *bi_next;
+
+ unsigned char *s_addr = addr;
+
+ dhd_iscan_lock();
+ DHD_ISCAN(("%s: BSS to remove %X:%X:%X:%X:%X:%X\n",
+ __func__, s_addr[0], s_addr[1], s_addr[2],
+ s_addr[3], s_addr[4], s_addr[5]));
+
+ iscan_cur = dhd_iscan_result_buf();
+
+ while (iscan_cur) {
+ if (iscan_cur != iscan_skip) {
+ list = (wl_iscan_results_t *)iscan_cur->iscan_buf;
+ if (!list)
+ break;
+
+ results = (wl_scan_results_t *)&list->results;
+ if (!results)
+ break;
+
+ if (results->version != WL_BSS_INFO_VERSION) {
+ DHD_ERROR(("%s: results->version %d != "
+ "WL_BSS_INFO_VERSION\n",
+ __func__, results->version));
+ goto done;
+ }
+
+ bi = results->bss_info;
+ for (i = 0; i < results->count; i++) {
+ if (!bi)
+ break;
+
+ if (!memcmp
+ (bi->BSSID.octet, addr, ETHER_ADDR_LEN)) {
+ DHD_ISCAN(("%s: Del BSS[%2.2d:%2.2d] "
+ "%X:%X:%X:%X:%X:%X\n",
+ __func__, l, i, bi->BSSID.octet[0],
+ bi->BSSID.octet[1], bi->BSSID.octet[2],
+ bi->BSSID.octet[3], bi->BSSID.octet[4],
+ bi->BSSID.octet[5]));
+
+ bi_new = bi;
+ bi = (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32
+ (bi->length));
+/*
+ if(bi && bi_new) {
+ bcopy(bi, bi_new, results->buflen -
+ dtoh32(bi_new->length));
+ results->buflen -= dtoh32(bi_new->length);
+ }
+*/
+ results->buflen -=
+ dtoh32(bi_new->length);
+ results->count--;
+
+ for (j = i; j < results->count; j++) {
+ if (bi && bi_new) {
+ DHD_ISCAN(("%s: Moved up BSS[%2.2d:%2.2d]" "%X:%X:%X:%X:%X:%X\n",
+ __func__, l, j,
+ bi->BSSID.octet[0],
+ bi->BSSID.octet[1],
+ bi->BSSID.octet[2],
+ bi->BSSID.octet[3],
+ bi->BSSID.octet[4],
+ bi->BSSID.octet[5]));
+
+ bi_next =
+ (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32
+ (bi->length));
+ bcopy(bi, bi_new,
+ dtoh32
+ (bi->length));
+ bi_new =
+ (wl_bss_info_t *)((unsigned long)bi_new +
+ dtoh32
+ (bi_new->
+ length));
+ bi = bi_next;
+ }
+ }
+
+ if (results->count == 0) {
+ /* Prune now empty partial
+ scan list */
+ dhd_iscan_free_buf(dhdp,
+ iscan_cur);
+ goto done;
+ }
+ break;
+ }
+ bi = (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32(bi->length));
+ }
+ }
+ iscan_cur = iscan_cur->next;
+ l++;
+ }
+
+done:
+ dhd_iscan_unlock();
+ return 0;
+}
+
+int dhd_iscan_remove_duplicates(void *dhdp, iscan_buf_t *iscan_cur)
+{
+ int i = 0;
+ wl_iscan_results_t *list;
+ wl_scan_results_t *results;
+ wl_bss_info_t UNALIGNED *bi, *bi_new, *bi_next;
+
+ dhd_iscan_lock();
+
+ DHD_ISCAN(("%s: Scan cache before delete\n", __func__));
+ dhd_iscan_print_cache(iscan_cur);
+
+ if (!iscan_cur)
+ goto done;
+
+ list = (wl_iscan_results_t *)iscan_cur->iscan_buf;
+ if (!list)
+ goto done;
+
+ results = (wl_scan_results_t *)&list->results;
+ if (!results)
+ goto done;
+
+ if (results->version != WL_BSS_INFO_VERSION) {
+ DHD_ERROR(("%s: results->version %d != WL_BSS_INFO_VERSION\n",
+ __func__, results->version));
+ goto done;
+ }
+
+ bi = results->bss_info;
+ for (i = 0; i < results->count; i++) {
+ if (!bi)
+ break;
+
+ DHD_ISCAN(("%s: Find dups for BSS[%2.2d] %X:%X:%X:%X:%X:%X\n",
+ __func__, i, bi->BSSID.octet[0],
+ bi->BSSID.octet[1], bi->BSSID.octet[2],
+ bi->BSSID.octet[3], bi->BSSID.octet[4],
+ bi->BSSID.octet[5]));
+
+ dhd_iscan_delete_bss(dhdp, bi->BSSID.octet, iscan_cur);
+
+ bi = (wl_bss_info_t *)((unsigned long)bi + dtoh32(bi->length));
+ }
+
+done:
+ DHD_ISCAN(("%s: Scan cache after delete\n", __func__));
+ dhd_iscan_print_cache(iscan_cur);
+ dhd_iscan_unlock();
+ return 0;
+}
+
+void dhd_iscan_ind_scan_confirm(void *dhdp, bool status)
+{
+
+ dhd_ind_scan_confirm(dhdp, status);
+}
+
+int dhd_iscan_request(void *dhdp, u16 action)
+{
+ int rc;
+ wl_iscan_params_t params;
+ dhd_pub_t *dhd = dhd_bus_pub(dhdp);
+ char buf[WLC_IOCTL_SMLEN];
+
+ memset(&params, 0, sizeof(wl_iscan_params_t));
+ memcpy(&params.params.bssid, &ether_bcast, ETHER_ADDR_LEN);
+
+ params.params.bss_type = DOT11_BSSTYPE_ANY;
+ params.params.scan_type = DOT11_SCANTYPE_ACTIVE;
+
+ params.params.nprobes = htod32(-1);
+ params.params.active_time = htod32(-1);
+ params.params.passive_time = htod32(-1);
+ params.params.home_time = htod32(-1);
+ params.params.channel_num = htod32(0);
+
+ params.version = htod32(ISCAN_REQ_VERSION);
+ params.action = htod16(action);
+ params.scan_duration = htod16(0);
+
+ bcm_mkiovar("iscan", (char *)&params, sizeof(wl_iscan_params_t), buf,
+ WLC_IOCTL_SMLEN);
+ rc = dhd_wl_ioctl(dhdp, WLC_SET_VAR, buf, WLC_IOCTL_SMLEN);
+
+ return rc;
+}
+
+static int dhd_iscan_get_partial_result(void *dhdp, uint *scan_count)
+{
+ wl_iscan_results_t *list_buf;
+ wl_iscan_results_t list;
+ wl_scan_results_t *results;
+ iscan_buf_t *iscan_cur;
+ int status = -1;
+ dhd_pub_t *dhd = dhd_bus_pub(dhdp);
+ int rc;
+
+ iscan_cur = dhd_iscan_allocate_buf(dhd, &iscan_chain);
+ if (!iscan_cur) {
+ DHD_ERROR(("%s: Failed to allocate node\n", __func__));
+ dhd_iscan_free_buf(dhdp, 0);
+ dhd_iscan_request(dhdp, WL_SCAN_ACTION_ABORT);
+ goto fail;
+ }
+
+ dhd_iscan_lock();
+
+ memset(iscan_cur->iscan_buf, 0, WLC_IW_ISCAN_MAXLEN);
+ list_buf = (wl_iscan_results_t *) iscan_cur->iscan_buf;
+ results = &list_buf->results;
+ results->buflen = WL_ISCAN_RESULTS_FIXED_SIZE;
+ results->version = 0;
+ results->count = 0;
+
+ memset(&list, 0, sizeof(list));
+ list.results.buflen = htod32(WLC_IW_ISCAN_MAXLEN);
+ bcm_mkiovar("iscanresults", (char *)&list, WL_ISCAN_RESULTS_FIXED_SIZE,
+ iscan_cur->iscan_buf, WLC_IW_ISCAN_MAXLEN);
+ rc = dhd_wl_ioctl(dhdp, WLC_GET_VAR, iscan_cur->iscan_buf,
+ WLC_IW_ISCAN_MAXLEN);
+
+ results->buflen = dtoh32(results->buflen);
+ results->version = dtoh32(results->version);
+ *scan_count = results->count = dtoh32(results->count);
+ status = dtoh32(list_buf->status);
+
+ dhd_iscan_unlock();
+
+ if (!(*scan_count))
+ dhd_iscan_free_buf(dhdp, iscan_cur);
+ else
+ dhd_iscan_remove_duplicates(dhdp, iscan_cur);
+
+fail:
+ return status;
+}
+#endif /* SIMPLE_ISCAN */
+
+#ifdef PNO_SUPPORT
+int dhd_pno_clean(dhd_pub_t *dhd)
+{
+ char iovbuf[128];
+ int pfn_enabled = 0;
+ int iov_len = 0;
+ int ret;
+
+ /* Disable pfn */
+ iov_len =
+ bcm_mkiovar("pfn", (char *)&pfn_enabled, 4, iovbuf, sizeof(iovbuf));
+ ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (ret >= 0) {
+ /* clear pfn */
+ iov_len = bcm_mkiovar("pfnclear", 0, 0, iovbuf, sizeof(iovbuf));
+ if (iov_len) {
+ ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ iov_len);
+ if (ret < 0) {
+ DHD_ERROR(("%s failed code %d\n", __func__,
+ ret));
+ }
+ } else {
+ ret = -1;
+ DHD_ERROR(("%s failed code %d\n", __func__, iov_len));
+ }
+ } else
+ DHD_ERROR(("%s failed code %d\n", __func__, ret));
+
+ return ret;
+}
+
+int dhd_pno_enable(dhd_pub_t *dhd, int pfn_enabled)
+{
+ char iovbuf[128];
+ int ret = -1;
+
+ if ((!dhd) && ((pfn_enabled != 0) || (pfn_enabled != 1))) {
+ DHD_ERROR(("%s error exit\n", __func__));
+ return ret;
+ }
+
+ /* Enable/disable PNO */
+ ret = bcm_mkiovar("pfn", (char *)&pfn_enabled, 4, iovbuf,
+ sizeof(iovbuf));
+ if (ret > 0) {
+ ret = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+ if (ret < 0) {
+ DHD_ERROR(("%s failed for error=%d\n", __func__, ret));
+ return ret;
+ } else {
+ dhd->pno_enable = pfn_enabled;
+ DHD_TRACE(("%s set pno as %d\n", __func__,
+ dhd->pno_enable));
+ }
+ } else
+ DHD_ERROR(("%s failed err=%d\n", __func__, ret));
+
+ return ret;
+}
+
+/* Function to execute combined scan */
+int
+dhd_pno_set(dhd_pub_t *dhd, wlc_ssid_t *ssids_local, int nssid, unsigned char scan_fr)
+{
+ int err = -1;
+ char iovbuf[128];
+ int k, i;
+ wl_pfn_param_t pfn_param;
+ wl_pfn_t pfn_element;
+
+ DHD_TRACE(("%s nssid=%d nchan=%d\n", __func__, nssid, scan_fr));
+
+ if ((!dhd) && (!ssids_local)) {
+ DHD_ERROR(("%s error exit\n", __func__));
+ err = -1;
+ }
+
+ /* Check for broadcast ssid */
+ for (k = 0; k < nssid; k++) {
+ if (!ssids_local[k].SSID_len) {
+ DHD_ERROR(("%d: Broadcast SSID is ilegal for PNO "
+ "setting\n", k));
+ return err;
+ }
+ }
+/* #define PNO_DUMP 1 */
+#ifdef PNO_DUMP
+ {
+ int j;
+ for (j = 0; j < nssid; j++) {
+ DHD_ERROR(("%d: scan for %s size =%d\n", j,
+ ssids_local[j].SSID,
+ ssids_local[j].SSID_len));
+ }
+ }
+#endif /* PNO_DUMP */
+
+ /* clean up everything */
+ err = dhd_pno_clean(dhd);
+ if (err < 0) {
+ DHD_ERROR(("%s failed error=%d\n", __func__, err));
+ return err;
+ }
+ memset(&pfn_param, 0, sizeof(pfn_param));
+ memset(&pfn_element, 0, sizeof(pfn_element));
+
+ /* set pfn parameters */
+ pfn_param.version = htod32(PFN_VERSION);
+ pfn_param.flags = htod16((PFN_LIST_ORDER << SORT_CRITERIA_BIT));
+
+ /* set up pno scan fr */
+ if (scan_fr != 0)
+ pfn_param.scan_freq = htod32(scan_fr);
+
+ bcm_mkiovar("pfn_set", (char *)&pfn_param, sizeof(pfn_param), iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+
+ /* set all pfn ssid */
+ for (i = 0; i < nssid; i++) {
+
+ pfn_element.bss_type = htod32(DOT11_BSSTYPE_INFRASTRUCTURE);
+ pfn_element.auth = (DOT11_OPEN_SYSTEM);
+ pfn_element.wpa_auth = htod32(WPA_AUTH_PFN_ANY);
+ pfn_element.wsec = htod32(0);
+ pfn_element.infra = htod32(1);
+
+ memcpy((char *)pfn_element.ssid.SSID, ssids_local[i].SSID,
+ ssids_local[i].SSID_len);
+ pfn_element.ssid.SSID_len = ssids_local[i].SSID_len;
+
+ err = bcm_mkiovar("pfn_add", (char *)&pfn_element,
+ sizeof(pfn_element), iovbuf, sizeof(iovbuf));
+ if (err > 0) {
+ err = dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+ if (err < 0) {
+ DHD_ERROR(("%s failed for i=%d error=%d\n",
+ __func__, i, err));
+ return err;
+ }
+ } else
+ DHD_ERROR(("%s failed err=%d\n", __func__, err));
+ }
+
+ /* Enable PNO */
+ /* dhd_pno_enable(dhd, 1); */
+ return err;
+}
+
+int dhd_pno_get_status(dhd_pub_t *dhd)
+{
+ int ret = -1;
+
+ if (!dhd)
+ return ret;
+ else
+ return dhd->pno_enable;
+}
+
+#endif /* PNO_SUPPORT */
+
+/* Androd ComboSCAN support */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c b/drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c
new file mode 100644
index 000000000000..f647034f36d6
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linuxver.h>
+#include <osl.h>
+#include <bcmutils.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+
+#include <wlioctl.h>
+#include <wl_iw.h>
+
+#define WL_ERROR(x) printf x
+#define WL_TRACE(x)
+
+#ifdef CUSTOMER_HW
+extern void bcm_wlan_power_off(int);
+extern void bcm_wlan_power_on(int);
+#endif /* CUSTOMER_HW */
+#ifdef CUSTOMER_HW2
+int wifi_set_carddetect(int on);
+int wifi_set_power(int on, unsigned long msec);
+int wifi_get_irq_number(unsigned long *irq_flags_ptr);
+#endif
+
+#if defined(OOB_INTR_ONLY)
+
+#if defined(BCMLXSDMMC)
+extern int sdioh_mmc_irq(int irq);
+#endif /* (BCMLXSDMMC) */
+
+#ifdef CUSTOMER_HW3
+#include <mach/gpio.h>
+#endif
+
+/* Customer specific Host GPIO defintion */
+static int dhd_oob_gpio_num = -1; /* GG 19 */
+
+module_param(dhd_oob_gpio_num, int, 0644);
+MODULE_PARM_DESC(dhd_oob_gpio_num, "DHD oob gpio number");
+
+int dhd_customer_oob_irq_map(unsigned long *irq_flags_ptr)
+{
+ int host_oob_irq = 0;
+
+#ifdef CUSTOMER_HW2
+ host_oob_irq = wifi_get_irq_number(irq_flags_ptr);
+
+#else /* for NOT CUSTOMER_HW2 */
+#if defined(CUSTOM_OOB_GPIO_NUM)
+ if (dhd_oob_gpio_num < 0)
+ dhd_oob_gpio_num = CUSTOM_OOB_GPIO_NUM;
+#endif
+
+ if (dhd_oob_gpio_num < 0) {
+ WL_ERROR(("%s: ERROR customer specific Host GPIO is NOT defined\n",
+ __func__));
+ return dhd_oob_gpio_num;
+ }
+
+ WL_ERROR(("%s: customer specific Host GPIO number is (%d)\n",
+ __func__, dhd_oob_gpio_num));
+
+#if defined CUSTOMER_HW
+ host_oob_irq = MSM_GPIO_TO_INT(dhd_oob_gpio_num);
+#elif defined CUSTOMER_HW3
+ gpio_request(dhd_oob_gpio_num, "oob irq");
+ host_oob_irq = gpio_to_irq(dhd_oob_gpio_num);
+ gpio_direction_input(dhd_oob_gpio_num);
+#endif /* CUSTOMER_HW */
+#endif /* CUSTOMER_HW2 */
+
+ return host_oob_irq;
+}
+#endif /* defined(OOB_INTR_ONLY) */
+
+/* Customer function to control hw specific wlan gpios */
+void dhd_customer_gpio_wlan_ctrl(int onoff)
+{
+ switch (onoff) {
+ case WLAN_RESET_OFF:
+ WL_TRACE(("%s: call customer specific GPIO to insert WLAN RESET\n",
+ __func__));
+#ifdef CUSTOMER_HW
+ bcm_wlan_power_off(2);
+#endif /* CUSTOMER_HW */
+#ifdef CUSTOMER_HW2
+ wifi_set_power(0, 0);
+#endif
+ WL_ERROR(("=========== WLAN placed in RESET ========\n"));
+ break;
+
+ case WLAN_RESET_ON:
+ WL_TRACE(("%s: callc customer specific GPIO to remove WLAN RESET\n",
+ __func__));
+#ifdef CUSTOMER_HW
+ bcm_wlan_power_on(2);
+#endif /* CUSTOMER_HW */
+#ifdef CUSTOMER_HW2
+ wifi_set_power(1, 0);
+#endif
+ WL_ERROR(("=========== WLAN going back to live ========\n"));
+ break;
+
+ case WLAN_POWER_OFF:
+ WL_TRACE(("%s: call customer specific GPIO to turn off WL_REG_ON\n",
+ __func__));
+#ifdef CUSTOMER_HW
+ bcm_wlan_power_off(1);
+#endif /* CUSTOMER_HW */
+ break;
+
+ case WLAN_POWER_ON:
+ WL_TRACE(("%s: call customer specific GPIO to turn on WL_REG_ON\n",
+ __func__));
+#ifdef CUSTOMER_HW
+ bcm_wlan_power_on(1);
+#endif /* CUSTOMER_HW */
+ /* Lets customer power to get stable */
+ udelay(200);
+ break;
+ }
+}
+
+#ifdef GET_CUSTOM_MAC_ENABLE
+/* Function to get custom MAC address */
+int dhd_custom_get_mac_address(unsigned char *buf)
+{
+ WL_TRACE(("%s Enter\n", __func__));
+ if (!buf)
+ return -EINVAL;
+
+ /* Customer access to MAC address stored outside of DHD driver */
+
+#ifdef EXAMPLE_GET_MAC
+ /* EXAMPLE code */
+ {
+ struct ether_addr ea_example = {
+ {0x00, 0x11, 0x22, 0x33, 0x44, 0xFF} };
+ bcopy((char *)&ea_example, buf, sizeof(struct ether_addr));
+ }
+#endif /* EXAMPLE_GET_MAC */
+
+ return 0;
+}
+#endif /* GET_CUSTOM_MAC_ENABLE */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_dbg.h b/drivers/staging/brcm80211/brcmfmac/dhd_dbg.h
new file mode 100644
index 000000000000..cd2578ad3552
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_dbg.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _dhd_dbg_
+#define _dhd_dbg_
+
+#if defined(DHD_DEBUG)
+
+#define DHD_ERROR(args) \
+ do {if ((dhd_msg_level & DHD_ERROR_VAL) && (net_ratelimit())) \
+ printf args; } while (0)
+#define DHD_TRACE(args) do {if (dhd_msg_level & DHD_TRACE_VAL) \
+ printf args; } while (0)
+#define DHD_INFO(args) do {if (dhd_msg_level & DHD_INFO_VAL) \
+ printf args; } while (0)
+#define DHD_DATA(args) do {if (dhd_msg_level & DHD_DATA_VAL) \
+ printf args; } while (0)
+#define DHD_CTL(args) do {if (dhd_msg_level & DHD_CTL_VAL) \
+ printf args; } while (0)
+#define DHD_TIMER(args) do {if (dhd_msg_level & DHD_TIMER_VAL) \
+ printf args; } while (0)
+#define DHD_HDRS(args) do {if (dhd_msg_level & DHD_HDRS_VAL) \
+ printf args; } while (0)
+#define DHD_BYTES(args) do {if (dhd_msg_level & DHD_BYTES_VAL) \
+ printf args; } while (0)
+#define DHD_INTR(args) do {if (dhd_msg_level & DHD_INTR_VAL) \
+ printf args; } while (0)
+#define DHD_GLOM(args) do {if (dhd_msg_level & DHD_GLOM_VAL) \
+ printf args; } while (0)
+#define DHD_EVENT(args) do {if (dhd_msg_level & DHD_EVENT_VAL) \
+ printf args; } while (0)
+#define DHD_BTA(args) do {if (dhd_msg_level & DHD_BTA_VAL) \
+ printf args; } while (0)
+#define DHD_ISCAN(args) do {if (dhd_msg_level & DHD_ISCAN_VAL) \
+ printf args; } while (0)
+
+#define DHD_ERROR_ON() (dhd_msg_level & DHD_ERROR_VAL)
+#define DHD_TRACE_ON() (dhd_msg_level & DHD_TRACE_VAL)
+#define DHD_INFO_ON() (dhd_msg_level & DHD_INFO_VAL)
+#define DHD_DATA_ON() (dhd_msg_level & DHD_DATA_VAL)
+#define DHD_CTL_ON() (dhd_msg_level & DHD_CTL_VAL)
+#define DHD_TIMER_ON() (dhd_msg_level & DHD_TIMER_VAL)
+#define DHD_HDRS_ON() (dhd_msg_level & DHD_HDRS_VAL)
+#define DHD_BYTES_ON() (dhd_msg_level & DHD_BYTES_VAL)
+#define DHD_INTR_ON() (dhd_msg_level & DHD_INTR_VAL)
+#define DHD_GLOM_ON() (dhd_msg_level & DHD_GLOM_VAL)
+#define DHD_EVENT_ON() (dhd_msg_level & DHD_EVENT_VAL)
+#define DHD_BTA_ON() (dhd_msg_level & DHD_BTA_VAL)
+#define DHD_ISCAN_ON() (dhd_msg_level & DHD_ISCAN_VAL)
+
+#else /* (defined BCMDBG) || (defined DHD_DEBUG) */
+
+#define DHD_ERROR(args) do {if (net_ratelimit()) printf args; } while (0)
+#define DHD_TRACE(args)
+#define DHD_INFO(args)
+#define DHD_DATA(args)
+#define DHD_CTL(args)
+#define DHD_TIMER(args)
+#define DHD_HDRS(args)
+#define DHD_BYTES(args)
+#define DHD_INTR(args)
+#define DHD_GLOM(args)
+#define DHD_EVENT(args)
+#define DHD_BTA(args)
+#define DHD_ISCAN(args)
+
+#define DHD_ERROR_ON() 0
+#define DHD_TRACE_ON() 0
+#define DHD_INFO_ON() 0
+#define DHD_DATA_ON() 0
+#define DHD_CTL_ON() 0
+#define DHD_TIMER_ON() 0
+#define DHD_HDRS_ON() 0
+#define DHD_BYTES_ON() 0
+#define DHD_INTR_ON() 0
+#define DHD_GLOM_ON() 0
+#define DHD_EVENT_ON() 0
+#define DHD_BTA_ON() 0
+#define DHD_ISCAN_ON() 0
+#endif /* defined(DHD_DEBUG) */
+
+#define DHD_LOG(args)
+
+#define DHD_NONE(args)
+extern int dhd_msg_level;
+
+/* Defines msg bits */
+#include <dhdioctl.h>
+
+#endif /* _dhd_dbg_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_linux.c b/drivers/staging/brcm80211/brcmfmac/dhd_linux.c
new file mode 100644
index 000000000000..bbbe7c5f7492
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_linux.c
@@ -0,0 +1,2929 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef CONFIG_WIFI_CONTROL_FUNC
+#include <linux/platform_device.h>
+#endif
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/random.h>
+#include <linux/spinlock.h>
+#include <linux/ethtool.h>
+#include <linux/fcntl.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <bcmdefs.h>
+#include <linuxver.h>
+#include <osl.h>
+#include <bcmutils.h>
+#include <bcmendian.h>
+
+#include <proto/ethernet.h>
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhd_bus.h>
+#include <dhd_proto.h>
+#include <dhd_dbg.h>
+
+#include <wl_cfg80211.h>
+
+#define EPI_VERSION_STR "4.218.248.5"
+
+#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC)
+#include <linux/wifi_tiwlan.h>
+
+struct semaphore wifi_control_sem;
+
+struct dhd_bus *g_bus;
+
+static struct wifi_platform_data *wifi_control_data;
+static struct resource *wifi_irqres;
+
+int wifi_get_irq_number(unsigned long *irq_flags_ptr)
+{
+ if (wifi_irqres) {
+ *irq_flags_ptr = wifi_irqres->flags & IRQF_TRIGGER_MASK;
+ return (int)wifi_irqres->start;
+ }
+#ifdef CUSTOM_OOB_GPIO_NUM
+ return CUSTOM_OOB_GPIO_NUM;
+#else
+ return -1;
+#endif
+}
+
+int wifi_set_carddetect(int on)
+{
+ printk(KERN_ERR "%s = %d\n", __func__, on);
+ if (wifi_control_data && wifi_control_data->set_carddetect)
+ wifi_control_data->set_carddetect(on);
+ return 0;
+}
+
+int wifi_set_power(int on, unsigned long msec)
+{
+ printk(KERN_ERR "%s = %d\n", __func__, on);
+ if (wifi_control_data && wifi_control_data->set_power)
+ wifi_control_data->set_power(on);
+ if (msec)
+ mdelay(msec);
+ return 0;
+}
+
+int wifi_set_reset(int on, unsigned long msec)
+{
+ printk(KERN_ERR "%s = %d\n", __func__, on);
+ if (wifi_control_data && wifi_control_data->set_reset)
+ wifi_control_data->set_reset(on);
+ if (msec)
+ mdelay(msec);
+ return 0;
+}
+
+static int wifi_probe(struct platform_device *pdev)
+{
+ struct wifi_platform_data *wifi_ctrl =
+ (struct wifi_platform_data *)(pdev->dev.platform_data);
+
+ printk(KERN_ERR "## %s\n", __func__);
+ wifi_irqres =
+ platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "bcm4329_wlan_irq");
+ wifi_control_data = wifi_ctrl;
+
+ wifi_set_power(1, 0); /* Power On */
+ wifi_set_carddetect(1); /* CardDetect (0->1) */
+
+ up(&wifi_control_sem);
+ return 0;
+}
+
+static int wifi_remove(struct platform_device *pdev)
+{
+ struct wifi_platform_data *wifi_ctrl =
+ (struct wifi_platform_data *)(pdev->dev.platform_data);
+
+ printk(KERN_ERR "## %s\n", __func__);
+ wifi_control_data = wifi_ctrl;
+
+ wifi_set_carddetect(0); /* CardDetect (1->0) */
+ wifi_set_power(0, 0); /* Power Off */
+
+ up(&wifi_control_sem);
+ return 0;
+}
+
+static int wifi_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ DHD_TRACE(("##> %s\n", __func__));
+ return 0;
+}
+
+static int wifi_resume(struct platform_device *pdev)
+{
+ DHD_TRACE(("##> %s\n", __func__));
+ return 0;
+}
+
+static struct platform_driver wifi_device = {
+ .probe = wifi_probe,
+ .remove = wifi_remove,
+ .suspend = wifi_suspend,
+ .resume = wifi_resume,
+ .driver = {
+ .name = "bcm4329_wlan",
+ }
+};
+
+int wifi_add_dev(void)
+{
+ DHD_TRACE(("## Calling platform_driver_register\n"));
+ return platform_driver_register(&wifi_device);
+}
+
+void wifi_del_dev(void)
+{
+ DHD_TRACE(("## Unregister platform_driver_register\n"));
+ platform_driver_unregister(&wifi_device);
+}
+#endif /* defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */
+
+#if defined(CONFIG_PM_SLEEP)
+#include <linux/suspend.h>
+volatile bool dhd_mmc_suspend = false;
+DECLARE_WAIT_QUEUE_HEAD(dhd_dpc_wait);
+#endif /* defined(CONFIG_PM_SLEEP) */
+
+#if defined(OOB_INTR_ONLY)
+extern void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable);
+#endif /* defined(OOB_INTR_ONLY) */
+
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Broadcom 802.11n wireless LAN fullmac driver.");
+MODULE_SUPPORTED_DEVICE("Broadcom 802.11n WLAN fullmac cards");
+MODULE_LICENSE("Dual BSD/GPL");
+
+#define DRV_MODULE_NAME "brcmfmac"
+
+/* Linux wireless extension support */
+#if defined(CONFIG_WIRELESS_EXT)
+#include <wl_iw.h>
+extern wl_iw_extra_params_t g_wl_iw_params;
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+#include <linux/earlysuspend.h>
+extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf,
+ uint len);
+#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
+
+#ifdef PKT_FILTER_SUPPORT
+extern void dhd_pktfilter_offload_set(dhd_pub_t *dhd, char *arg);
+extern void dhd_pktfilter_offload_enable(dhd_pub_t *dhd, char *arg, int enable,
+ int master_mode);
+#endif
+
+/* Interface control information */
+typedef struct dhd_if {
+ struct dhd_info *info; /* back pointer to dhd_info */
+ /* OS/stack specifics */
+ struct net_device *net;
+ struct net_device_stats stats;
+ int idx; /* iface idx in dongle */
+ int state; /* interface state */
+ uint subunit; /* subunit */
+ u8 mac_addr[ETHER_ADDR_LEN]; /* assigned MAC address */
+ bool attached; /* Delayed attachment when unset */
+ bool txflowcontrol; /* Per interface flow control indicator */
+ char name[IFNAMSIZ]; /* linux interface name */
+} dhd_if_t;
+
+/* Local private structure (extension of pub) */
+typedef struct dhd_info {
+#if defined(CONFIG_WIRELESS_EXT)
+ wl_iw_t iw; /* wireless extensions state (must be first) */
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+ dhd_pub_t pub;
+
+ /* OS/stack specifics */
+ dhd_if_t *iflist[DHD_MAX_IFS];
+
+ struct semaphore proto_sem;
+ wait_queue_head_t ioctl_resp_wait;
+ struct timer_list timer;
+ bool wd_timer_valid;
+ struct tasklet_struct tasklet;
+ spinlock_t sdlock;
+ spinlock_t txqlock;
+ /* Thread based operation */
+ bool threads_only;
+ struct semaphore sdsem;
+ struct task_struct *watchdog_tsk;
+ struct semaphore watchdog_sem;
+ struct task_struct *dpc_tsk;
+ struct semaphore dpc_sem;
+
+ /* Thread to issue ioctl for multicast */
+ struct task_struct *sysioc_tsk;
+ struct semaphore sysioc_sem;
+ bool set_multicast;
+ bool set_macaddress;
+ struct ether_addr macvalue;
+ wait_queue_head_t ctrl_wait;
+ atomic_t pend_8021x_cnt;
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ struct early_suspend early_suspend;
+#endif /* CONFIG_HAS_EARLYSUSPEND */
+} dhd_info_t;
+
+/* Definitions to provide path to the firmware and nvram
+ * example nvram_path[MOD_PARAM_PATHLEN]="/projects/wlan/nvram.txt"
+ */
+char firmware_path[MOD_PARAM_PATHLEN];
+char nvram_path[MOD_PARAM_PATHLEN];
+
+/* load firmware and/or nvram values from the filesystem */
+module_param_string(firmware_path, firmware_path, MOD_PARAM_PATHLEN, 0);
+module_param_string(nvram_path, nvram_path, MOD_PARAM_PATHLEN, 0);
+
+/* Error bits */
+module_param(dhd_msg_level, int, 0);
+
+/* Spawn a thread for system ioctls (set mac, set mcast) */
+uint dhd_sysioc = true;
+module_param(dhd_sysioc, uint, 0);
+
+/* Watchdog interval */
+uint dhd_watchdog_ms = 10;
+module_param(dhd_watchdog_ms, uint, 0);
+
+#ifdef DHD_DEBUG
+/* Console poll interval */
+uint dhd_console_ms;
+module_param(dhd_console_ms, uint, 0);
+#endif /* DHD_DEBUG */
+
+/* ARP offload agent mode : Enable ARP Host Auto-Reply
+and ARP Peer Auto-Reply */
+uint dhd_arp_mode = 0xb;
+module_param(dhd_arp_mode, uint, 0);
+
+/* ARP offload enable */
+uint dhd_arp_enable = true;
+module_param(dhd_arp_enable, uint, 0);
+
+/* Global Pkt filter enable control */
+uint dhd_pkt_filter_enable = true;
+module_param(dhd_pkt_filter_enable, uint, 0);
+
+/* Pkt filter init setup */
+uint dhd_pkt_filter_init;
+module_param(dhd_pkt_filter_init, uint, 0);
+
+/* Pkt filter mode control */
+uint dhd_master_mode = true;
+module_param(dhd_master_mode, uint, 1);
+
+/* Watchdog thread priority, -1 to use kernel timer */
+int dhd_watchdog_prio = 97;
+module_param(dhd_watchdog_prio, int, 0);
+
+/* DPC thread priority, -1 to use tasklet */
+int dhd_dpc_prio = 98;
+module_param(dhd_dpc_prio, int, 0);
+
+/* DPC thread priority, -1 to use tasklet */
+extern int dhd_dongle_memsize;
+module_param(dhd_dongle_memsize, int, 0);
+
+/* Contorl fw roaming */
+#ifdef CUSTOMER_HW2
+uint dhd_roam;
+#else
+uint dhd_roam = 1;
+#endif
+
+/* Control radio state */
+uint dhd_radio_up = 1;
+
+/* Network inteface name */
+char iface_name[IFNAMSIZ];
+module_param_string(iface_name, iface_name, IFNAMSIZ, 0);
+
+/* The following are specific to the SDIO dongle */
+
+/* IOCTL response timeout */
+int dhd_ioctl_timeout_msec = IOCTL_RESP_TIMEOUT;
+
+/* Idle timeout for backplane clock */
+int dhd_idletime = DHD_IDLETIME_TICKS;
+module_param(dhd_idletime, int, 0);
+
+/* Use polling */
+uint dhd_poll = false;
+module_param(dhd_poll, uint, 0);
+
+/* Use cfg80211 */
+uint dhd_cfg80211 = true;
+module_param(dhd_cfg80211, uint, 0);
+
+/* Use interrupts */
+uint dhd_intr = true;
+module_param(dhd_intr, uint, 0);
+
+/* SDIO Drive Strength (in milliamps) */
+uint dhd_sdiod_drive_strength = 6;
+module_param(dhd_sdiod_drive_strength, uint, 0);
+
+/* Tx/Rx bounds */
+extern uint dhd_txbound;
+extern uint dhd_rxbound;
+module_param(dhd_txbound, uint, 0);
+module_param(dhd_rxbound, uint, 0);
+
+/* Deferred transmits */
+extern uint dhd_deferred_tx;
+module_param(dhd_deferred_tx, uint, 0);
+
+#ifdef SDTEST
+/* Echo packet generator (pkts/s) */
+uint dhd_pktgen;
+module_param(dhd_pktgen, uint, 0);
+
+/* Echo packet len (0 => sawtooth, max 2040) */
+uint dhd_pktgen_len;
+module_param(dhd_pktgen_len, uint, 0);
+#endif
+
+#define FAVORITE_WIFI_CP (!!dhd_cfg80211)
+#define IS_CFG80211_FAVORITE() FAVORITE_WIFI_CP
+#define DBG_CFG80211_GET() ((dhd_cfg80211 & WL_DBG_MASK) >> 1)
+#define NO_FW_REQ() (dhd_cfg80211 & 0x80)
+
+/* Version string to report */
+#ifdef DHD_DEBUG
+#define DHD_COMPILED "\nCompiled in " SRCBASE
+#else
+#define DHD_COMPILED
+#endif
+
+static char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR
+#ifdef DHD_DEBUG
+"\nCompiled in " " on " __DATE__ " at " __TIME__
+#endif
+;
+
+#if defined(CONFIG_WIRELESS_EXT)
+struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev);
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+static void dhd_dpc(unsigned long data);
+/* forward decl */
+extern int dhd_wait_pend8021x(struct net_device *dev);
+
+#ifdef TOE
+#ifndef BDC
+#error TOE requires BDC
+#endif /* !BDC */
+static int dhd_toe_get(dhd_info_t *dhd, int idx, u32 *toe_ol);
+static int dhd_toe_set(dhd_info_t *dhd, int idx, u32 toe_ol);
+#endif /* TOE */
+
+static int dhd_wl_host_event(dhd_info_t *dhd, int *ifidx, void *pktdata,
+ wl_event_msg_t *event_ptr, void **data_ptr);
+
+#if defined(CONFIG_PM_SLEEP)
+static int dhd_sleep_pm_callback(struct notifier_block *nfb,
+ unsigned long action, void *ignored)
+{
+ switch (action) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ dhd_mmc_suspend = true;
+ return NOTIFY_OK;
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ dhd_mmc_suspend = false;
+ return NOTIFY_OK;
+ }
+ return 0;
+}
+
+static struct notifier_block dhd_sleep_pm_notifier = {
+ .notifier_call = dhd_sleep_pm_callback,
+ .priority = 0
+};
+
+extern int register_pm_notifier(struct notifier_block *nb);
+extern int unregister_pm_notifier(struct notifier_block *nb);
+#endif /* defined(CONFIG_PM_SLEEP) */
+ /* && defined(DHD_GPL) */
+static void dhd_set_packet_filter(int value, dhd_pub_t *dhd)
+{
+#ifdef PKT_FILTER_SUPPORT
+ DHD_TRACE(("%s: %d\n", __func__, value));
+ /* 1 - Enable packet filter, only allow unicast packet to send up */
+ /* 0 - Disable packet filter */
+ if (dhd_pkt_filter_enable) {
+ int i;
+
+ for (i = 0; i < dhd->pktfilter_count; i++) {
+ dhd_pktfilter_offload_set(dhd, dhd->pktfilter[i]);
+ dhd_pktfilter_offload_enable(dhd, dhd->pktfilter[i],
+ value, dhd_master_mode);
+ }
+ }
+#endif
+}
+
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+static int dhd_set_suspend(int value, dhd_pub_t *dhd)
+{
+ int power_mode = PM_MAX;
+ /* wl_pkt_filter_enable_t enable_parm; */
+ char iovbuf[32];
+ int bcn_li_dtim = 3;
+#ifdef CUSTOMER_HW2
+ uint roamvar = 1;
+#endif /* CUSTOMER_HW2 */
+
+ DHD_TRACE(("%s: enter, value = %d in_suspend=%d\n",
+ __func__, value, dhd->in_suspend));
+
+ if (dhd && dhd->up) {
+ if (value && dhd->in_suspend) {
+
+ /* Kernel suspended */
+ DHD_TRACE(("%s: force extra Suspend setting\n",
+ __func__));
+
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM,
+ (char *)&power_mode,
+ sizeof(power_mode));
+
+ /* Enable packet filter, only allow unicast
+ packet to send up */
+ dhd_set_packet_filter(1, dhd);
+
+ /* if dtim skip setup as default force it
+ * to wake each thrid dtim
+ * for better power saving.
+ * Note that side effect is chance to miss BC/MC
+ * packet
+ */
+ if ((dhd->dtim_skip == 0) || (dhd->dtim_skip == 1))
+ bcn_li_dtim = 3;
+ else
+ bcn_li_dtim = dhd->dtim_skip;
+ bcm_mkiovar("bcn_li_dtim", (char *)&bcn_li_dtim,
+ 4, iovbuf, sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+#ifdef CUSTOMER_HW2
+ /* Disable build-in roaming to allowed \
+ * supplicant to take of romaing
+ */
+ bcm_mkiovar("roam_off", (char *)&roamvar, 4,
+ iovbuf, sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+#endif /* CUSTOMER_HW2 */
+ } else {
+
+ /* Kernel resumed */
+ DHD_TRACE(("%s: Remove extra suspend setting\n",
+ __func__));
+
+ power_mode = PM_FAST;
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_PM,
+ (char *)&power_mode,
+ sizeof(power_mode));
+
+ /* disable pkt filter */
+ dhd_set_packet_filter(0, dhd);
+
+ /* restore pre-suspend setting for dtim_skip */
+ bcm_mkiovar("bcn_li_dtim", (char *)&dhd->dtim_skip,
+ 4, iovbuf, sizeof(iovbuf));
+
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+#ifdef CUSTOMER_HW2
+ roamvar = 0;
+ bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf,
+ sizeof(iovbuf));
+#endif /* CUSTOMER_HW2 */
+ }
+ }
+
+ return 0;
+}
+
+static void dhd_suspend_resume_helper(struct dhd_info *dhd, int val)
+{
+ dhd_pub_t *dhdp = &dhd->pub;
+
+ dhd_os_proto_block(dhdp);
+ /* Set flag when early suspend was called */
+ dhdp->in_suspend = val;
+ if (!dhdp->suspend_disable_flag)
+ dhd_set_suspend(val, dhdp);
+ dhd_os_proto_unblock(dhdp);
+}
+
+static void dhd_early_suspend(struct early_suspend *h)
+{
+ struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend);
+
+ DHD_TRACE(("%s: enter\n", __func__));
+
+ if (dhd)
+ dhd_suspend_resume_helper(dhd, 1);
+
+}
+
+static void dhd_late_resume(struct early_suspend *h)
+{
+ struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend);
+
+ DHD_TRACE(("%s: enter\n", __func__));
+
+ if (dhd)
+ dhd_suspend_resume_helper(dhd, 0);
+}
+#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
+
+/*
+ * Generalized timeout mechanism. Uses spin sleep with exponential
+ * back-off until
+ * the sleep time reaches one jiffy, then switches over to task delay. Usage:
+ *
+ * dhd_timeout_start(&tmo, usec);
+ * while (!dhd_timeout_expired(&tmo))
+ * if (poll_something())
+ * break;
+ * if (dhd_timeout_expired(&tmo))
+ * fatal();
+ */
+
+void dhd_timeout_start(dhd_timeout_t *tmo, uint usec)
+{
+ tmo->limit = usec;
+ tmo->increment = 0;
+ tmo->elapsed = 0;
+ tmo->tick = 1000000 / HZ;
+}
+
+int dhd_timeout_expired(dhd_timeout_t *tmo)
+{
+ /* Does nothing the first call */
+ if (tmo->increment == 0) {
+ tmo->increment = 1;
+ return 0;
+ }
+
+ if (tmo->elapsed >= tmo->limit)
+ return 1;
+
+ /* Add the delay that's about to take place */
+ tmo->elapsed += tmo->increment;
+
+ if (tmo->increment < tmo->tick) {
+ udelay(tmo->increment);
+ tmo->increment *= 2;
+ if (tmo->increment > tmo->tick)
+ tmo->increment = tmo->tick;
+ } else {
+ wait_queue_head_t delay_wait;
+ DECLARE_WAITQUEUE(wait, current);
+ int pending;
+ init_waitqueue_head(&delay_wait);
+ add_wait_queue(&delay_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(1);
+ pending = signal_pending(current);
+ remove_wait_queue(&delay_wait, &wait);
+ set_current_state(TASK_RUNNING);
+ if (pending)
+ return 1; /* Interrupted */
+ }
+
+ return 0;
+}
+
+static int dhd_net2idx(dhd_info_t *dhd, struct net_device *net)
+{
+ int i = 0;
+
+ ASSERT(dhd);
+ while (i < DHD_MAX_IFS) {
+ if (dhd->iflist[i] && (dhd->iflist[i]->net == net))
+ return i;
+ i++;
+ }
+
+ return DHD_BAD_IF;
+}
+
+int dhd_ifname2idx(dhd_info_t *dhd, char *name)
+{
+ int i = DHD_MAX_IFS;
+
+ ASSERT(dhd);
+
+ if (name == NULL || *name == '\0')
+ return 0;
+
+ while (--i > 0)
+ if (dhd->iflist[i]
+ && !strncmp(dhd->iflist[i]->name, name, IFNAMSIZ))
+ break;
+
+ DHD_TRACE(("%s: return idx %d for \"%s\"\n", __func__, i, name));
+
+ return i; /* default - the primary interface */
+}
+
+char *dhd_ifname(dhd_pub_t *dhdp, int ifidx)
+{
+ dhd_info_t *dhd = (dhd_info_t *) dhdp->info;
+
+ ASSERT(dhd);
+
+ if (ifidx < 0 || ifidx >= DHD_MAX_IFS) {
+ DHD_ERROR(("%s: ifidx %d out of range\n", __func__, ifidx));
+ return "<if_bad>";
+ }
+
+ if (dhd->iflist[ifidx] == NULL) {
+ DHD_ERROR(("%s: null i/f %d\n", __func__, ifidx));
+ return "<if_null>";
+ }
+
+ if (dhd->iflist[ifidx]->net)
+ return dhd->iflist[ifidx]->net->name;
+
+ return "<if_none>";
+}
+
+static void _dhd_set_multicast_list(dhd_info_t *dhd, int ifidx)
+{
+ struct net_device *dev;
+ struct netdev_hw_addr *ha;
+ u32 allmulti, cnt;
+
+ wl_ioctl_t ioc;
+ char *buf, *bufp;
+ uint buflen;
+ int ret;
+
+ ASSERT(dhd && dhd->iflist[ifidx]);
+ dev = dhd->iflist[ifidx]->net;
+ cnt = netdev_mc_count(dev);
+
+ /* Determine initial value of allmulti flag */
+ allmulti = (dev->flags & IFF_ALLMULTI) ? true : false;
+
+ /* Send down the multicast list first. */
+
+ buflen = sizeof("mcast_list") + sizeof(cnt) + (cnt * ETHER_ADDR_LEN);
+ bufp = buf = kmalloc(buflen, GFP_ATOMIC);
+ if (!bufp) {
+ DHD_ERROR(("%s: out of memory for mcast_list, cnt %d\n",
+ dhd_ifname(&dhd->pub, ifidx), cnt));
+ return;
+ }
+
+ strcpy(bufp, "mcast_list");
+ bufp += strlen("mcast_list") + 1;
+
+ cnt = htol32(cnt);
+ memcpy(bufp, &cnt, sizeof(cnt));
+ bufp += sizeof(cnt);
+
+ netdev_for_each_mc_addr(ha, dev) {
+ if (!cnt)
+ break;
+ memcpy(bufp, ha->addr, ETHER_ADDR_LEN);
+ bufp += ETHER_ADDR_LEN;
+ cnt--;
+ }
+
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = WLC_SET_VAR;
+ ioc.buf = buf;
+ ioc.len = buflen;
+ ioc.set = true;
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: set mcast_list failed, cnt %d\n",
+ dhd_ifname(&dhd->pub, ifidx), cnt));
+ allmulti = cnt ? true : allmulti;
+ }
+
+ kfree(buf);
+
+ /* Now send the allmulti setting. This is based on the setting in the
+ * net_device flags, but might be modified above to be turned on if we
+ * were trying to set some addresses and dongle rejected it...
+ */
+
+ buflen = sizeof("allmulti") + sizeof(allmulti);
+ buf = kmalloc(buflen, GFP_ATOMIC);
+ if (!buf) {
+ DHD_ERROR(("%s: out of memory for allmulti\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ return;
+ }
+ allmulti = htol32(allmulti);
+
+ if (!bcm_mkiovar
+ ("allmulti", (void *)&allmulti, sizeof(allmulti), buf, buflen)) {
+ DHD_ERROR(("%s: mkiovar failed for allmulti, datalen %d "
+ "buflen %u\n", dhd_ifname(&dhd->pub, ifidx),
+ (int)sizeof(allmulti), buflen));
+ kfree(buf);
+ return;
+ }
+
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = WLC_SET_VAR;
+ ioc.buf = buf;
+ ioc.len = buflen;
+ ioc.set = true;
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: set allmulti %d failed\n",
+ dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti)));
+ }
+
+ kfree(buf);
+
+ /* Finally, pick up the PROMISC flag as well, like the NIC
+ driver does */
+
+ allmulti = (dev->flags & IFF_PROMISC) ? true : false;
+ allmulti = htol32(allmulti);
+
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = WLC_SET_PROMISC;
+ ioc.buf = &allmulti;
+ ioc.len = sizeof(allmulti);
+ ioc.set = true;
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: set promisc %d failed\n",
+ dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti)));
+ }
+}
+
+static int
+_dhd_set_mac_address(dhd_info_t *dhd, int ifidx, struct ether_addr *addr)
+{
+ char buf[32];
+ wl_ioctl_t ioc;
+ int ret;
+
+ DHD_TRACE(("%s enter\n", __func__));
+ if (!bcm_mkiovar
+ ("cur_etheraddr", (char *)addr, ETHER_ADDR_LEN, buf, 32)) {
+ DHD_ERROR(("%s: mkiovar failed for cur_etheraddr\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ return -1;
+ }
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = WLC_SET_VAR;
+ ioc.buf = buf;
+ ioc.len = 32;
+ ioc.set = true;
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: set cur_etheraddr failed\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ } else {
+ memcpy(dhd->iflist[ifidx]->net->dev_addr, addr, ETHER_ADDR_LEN);
+ }
+
+ return ret;
+}
+
+#ifdef SOFTAP
+extern struct net_device *ap_net_dev;
+#endif
+
+static void dhd_op_if(dhd_if_t *ifp)
+{
+ dhd_info_t *dhd;
+ int ret = 0, err = 0;
+
+ ASSERT(ifp && ifp->info && ifp->idx); /* Virtual interfaces only */
+
+ dhd = ifp->info;
+
+ DHD_TRACE(("%s: idx %d, state %d\n", __func__, ifp->idx, ifp->state));
+
+ switch (ifp->state) {
+ case WLC_E_IF_ADD:
+ /*
+ * Delete the existing interface before overwriting it
+ * in case we missed the WLC_E_IF_DEL event.
+ */
+ if (ifp->net != NULL) {
+ DHD_ERROR(("%s: ERROR: netdev:%s already exists, "
+ "try free & unregister\n",
+ __func__, ifp->net->name));
+ netif_stop_queue(ifp->net);
+ unregister_netdev(ifp->net);
+ free_netdev(ifp->net);
+ }
+ /* Allocate etherdev, including space for private structure */
+ ifp->net = alloc_etherdev(sizeof(dhd));
+ if (!ifp->net) {
+ DHD_ERROR(("%s: OOM - alloc_etherdev\n", __func__));
+ ret = -ENOMEM;
+ }
+ if (ret == 0) {
+ strcpy(ifp->net->name, ifp->name);
+ memcpy(netdev_priv(ifp->net), &dhd, sizeof(dhd));
+ err = dhd_net_attach(&dhd->pub, ifp->idx);
+ if (err != 0) {
+ DHD_ERROR(("%s: dhd_net_attach failed, "
+ "err %d\n",
+ __func__, err));
+ ret = -EOPNOTSUPP;
+ } else {
+#ifdef SOFTAP
+ /* semaphore that the soft AP CODE
+ waits on */
+ extern struct semaphore ap_eth_sema;
+
+ /* save ptr to wl0.1 netdev for use
+ in wl_iw.c */
+ ap_net_dev = ifp->net;
+ /* signal to the SOFTAP 'sleeper' thread,
+ wl0.1 is ready */
+ up(&ap_eth_sema);
+#endif
+ DHD_TRACE(("\n ==== pid:%x, net_device for "
+ "if:%s created ===\n\n",
+ current->pid, ifp->net->name));
+ ifp->state = 0;
+ }
+ }
+ break;
+ case WLC_E_IF_DEL:
+ if (ifp->net != NULL) {
+ DHD_TRACE(("\n%s: got 'WLC_E_IF_DEL' state\n",
+ __func__));
+ netif_stop_queue(ifp->net);
+ unregister_netdev(ifp->net);
+ ret = DHD_DEL_IF; /* Make sure the free_netdev()
+ is called */
+ }
+ break;
+ default:
+ DHD_ERROR(("%s: bad op %d\n", __func__, ifp->state));
+ ASSERT(!ifp->state);
+ break;
+ }
+
+ if (ret < 0) {
+ if (ifp->net)
+ free_netdev(ifp->net);
+
+ dhd->iflist[ifp->idx] = NULL;
+ kfree(ifp);
+#ifdef SOFTAP
+ if (ifp->net == ap_net_dev)
+ ap_net_dev = NULL; /* NULL SOFTAP global
+ wl0.1 as well */
+#endif /* SOFTAP */
+ }
+}
+
+static int _dhd_sysioc_thread(void *data)
+{
+ dhd_info_t *dhd = (dhd_info_t *) data;
+ int i;
+#ifdef SOFTAP
+ bool in_ap = false;
+#endif
+
+ allow_signal(SIGTERM);
+
+ while (down_interruptible(&dhd->sysioc_sem) == 0) {
+ if (kthread_should_stop())
+ break;
+ for (i = 0; i < DHD_MAX_IFS; i++) {
+ if (dhd->iflist[i]) {
+#ifdef SOFTAP
+ in_ap = (ap_net_dev != NULL);
+#endif /* SOFTAP */
+ if (dhd->iflist[i]->state)
+ dhd_op_if(dhd->iflist[i]);
+#ifdef SOFTAP
+ if (dhd->iflist[i] == NULL) {
+ DHD_TRACE(("\n\n %s: interface %d "
+ "removed!\n", __func__, i));
+ continue;
+ }
+
+ if (in_ap && dhd->set_macaddress) {
+ DHD_TRACE(("attempt to set MAC for %s "
+ "in AP Mode," "blocked. \n",
+ dhd->iflist[i]->net->name));
+ dhd->set_macaddress = false;
+ continue;
+ }
+
+ if (in_ap && dhd->set_multicast) {
+ DHD_TRACE(("attempt to set MULTICAST list for %s" "in AP Mode, blocked. \n",
+ dhd->iflist[i]->net->name));
+ dhd->set_multicast = false;
+ continue;
+ }
+#endif /* SOFTAP */
+ if (dhd->set_multicast) {
+ dhd->set_multicast = false;
+ _dhd_set_multicast_list(dhd, i);
+ }
+ if (dhd->set_macaddress) {
+ dhd->set_macaddress = false;
+ _dhd_set_mac_address(dhd, i,
+ &dhd->macvalue);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int dhd_set_mac_address(struct net_device *dev, void *addr)
+{
+ int ret = 0;
+
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(dev);
+ struct sockaddr *sa = (struct sockaddr *)addr;
+ int ifidx;
+
+ ifidx = dhd_net2idx(dhd, dev);
+ if (ifidx == DHD_BAD_IF)
+ return -1;
+
+ ASSERT(dhd->sysioc_tsk);
+ memcpy(&dhd->macvalue, sa->sa_data, ETHER_ADDR_LEN);
+ dhd->set_macaddress = true;
+ up(&dhd->sysioc_sem);
+
+ return ret;
+}
+
+static void dhd_set_multicast_list(struct net_device *dev)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(dev);
+ int ifidx;
+
+ ifidx = dhd_net2idx(dhd, dev);
+ if (ifidx == DHD_BAD_IF)
+ return;
+
+ ASSERT(dhd->sysioc_tsk);
+ dhd->set_multicast = true;
+ up(&dhd->sysioc_sem);
+}
+
+int dhd_sendpkt(dhd_pub_t *dhdp, int ifidx, void *pktbuf)
+{
+ int ret;
+ dhd_info_t *dhd = (dhd_info_t *) (dhdp->info);
+
+ /* Reject if down */
+ if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN))
+ return -ENODEV;
+
+ /* Update multicast statistic */
+ if (PKTLEN(pktbuf) >= ETHER_ADDR_LEN) {
+ u8 *pktdata = (u8 *) PKTDATA(pktbuf);
+ struct ether_header *eh = (struct ether_header *)pktdata;
+
+ if (ETHER_ISMULTI(eh->ether_dhost))
+ dhdp->tx_multicast++;
+ if (ntoh16(eh->ether_type) == ETHER_TYPE_802_1X)
+ atomic_inc(&dhd->pend_8021x_cnt);
+ }
+
+ /* If the protocol uses a data header, apply it */
+ dhd_prot_hdrpush(dhdp, ifidx, pktbuf);
+
+ /* Use bus module to send data frame */
+#ifdef BCMDBUS
+ ret = dbus_send_pkt(dhdp->dbus, pktbuf, NULL /* pktinfo */);
+#else
+ WAKE_LOCK_TIMEOUT(dhdp, WAKE_LOCK_TMOUT, 25);
+ ret = dhd_bus_txdata(dhdp->bus, pktbuf);
+#endif /* BCMDBUS */
+
+ return ret;
+}
+
+static int dhd_start_xmit(struct sk_buff *skb, struct net_device *net)
+{
+ int ret;
+ void *pktbuf;
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+ int ifidx;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Reject if down */
+ if (!dhd->pub.up || (dhd->pub.busstate == DHD_BUS_DOWN)) {
+ DHD_ERROR(("%s: xmit rejected pub.up=%d busstate=%d\n",
+ __func__, dhd->pub.up, dhd->pub.busstate));
+ netif_stop_queue(net);
+ return -ENODEV;
+ }
+
+ ifidx = dhd_net2idx(dhd, net);
+ if (ifidx == DHD_BAD_IF) {
+ DHD_ERROR(("%s: bad ifidx %d\n", __func__, ifidx));
+ netif_stop_queue(net);
+ return -ENODEV;
+ }
+
+ /* Make sure there's enough room for any header */
+ if (skb_headroom(skb) < dhd->pub.hdrlen) {
+ struct sk_buff *skb2;
+
+ DHD_INFO(("%s: insufficient headroom\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ dhd->pub.tx_realloc++;
+ skb2 = skb_realloc_headroom(skb, dhd->pub.hdrlen);
+ dev_kfree_skb(skb);
+ skb = skb2;
+ if (skb == NULL) {
+ DHD_ERROR(("%s: skb_realloc_headroom failed\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ ret = -ENOMEM;
+ goto done;
+ }
+ }
+
+ /* Convert to packet */
+ pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb);
+ if (!pktbuf) {
+ DHD_ERROR(("%s: PKTFRMNATIVE failed\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ dev_kfree_skb_any(skb);
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ ret = dhd_sendpkt(&dhd->pub, ifidx, pktbuf);
+
+done:
+ if (ret)
+ dhd->pub.dstats.tx_dropped++;
+ else
+ dhd->pub.tx_packets++;
+
+ /* Return ok: we always eat the packet */
+ return 0;
+}
+
+void dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool state)
+{
+ struct net_device *net;
+ dhd_info_t *dhd = dhdp->info;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ dhdp->txoff = state;
+ ASSERT(dhd && dhd->iflist[ifidx]);
+ net = dhd->iflist[ifidx]->net;
+ if (state == ON)
+ netif_stop_queue(net);
+ else
+ netif_wake_queue(net);
+}
+
+void dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *pktbuf, int numpkt)
+{
+ dhd_info_t *dhd = (dhd_info_t *) dhdp->info;
+ struct sk_buff *skb;
+ unsigned char *eth;
+ uint len;
+ void *data, *pnext, *save_pktbuf;
+ int i;
+ dhd_if_t *ifp;
+ wl_event_msg_t event;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ save_pktbuf = pktbuf;
+
+ for (i = 0; pktbuf && i < numpkt; i++, pktbuf = pnext) {
+
+ pnext = PKTNEXT(pktbuf);
+ PKTSETNEXT(pktbuf, NULL);
+
+ skb = PKTTONATIVE(dhdp->osh, pktbuf);
+
+ /* Get the protocol, maintain skb around eth_type_trans()
+ * The main reason for this hack is for the limitation of
+ * Linux 2.4 where 'eth_type_trans' uses the
+ * 'net->hard_header_len'
+ * to perform skb_pull inside vs ETH_HLEN. Since to avoid
+ * coping of the packet coming from the network stack to add
+ * BDC, Hardware header etc, during network interface
+ * registration
+ * we set the 'net->hard_header_len' to ETH_HLEN + extra space
+ * required
+ * for BDC, Hardware header etc. and not just the ETH_HLEN
+ */
+ eth = skb->data;
+ len = skb->len;
+
+ ifp = dhd->iflist[ifidx];
+ if (ifp == NULL)
+ ifp = dhd->iflist[0];
+
+ ASSERT(ifp);
+ skb->dev = ifp->net;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
+ if (skb->pkt_type == PACKET_MULTICAST)
+ dhd->pub.rx_multicast++;
+
+ skb->data = eth;
+ skb->len = len;
+
+ /* Strip header, count, deliver upward */
+ skb_pull(skb, ETH_HLEN);
+
+ /* Process special event packets and then discard them */
+ if (ntoh16(skb->protocol) == ETHER_TYPE_BRCM)
+ dhd_wl_host_event(dhd, &ifidx,
+ skb->mac_header,
+ &event, &data);
+
+ ASSERT(ifidx < DHD_MAX_IFS && dhd->iflist[ifidx]);
+ if (dhd->iflist[ifidx] && !dhd->iflist[ifidx]->state)
+ ifp = dhd->iflist[ifidx];
+
+ if (ifp->net)
+ ifp->net->last_rx = jiffies;
+
+ dhdp->dstats.rx_bytes += skb->len;
+ dhdp->rx_packets++; /* Local count */
+
+ if (in_interrupt()) {
+ netif_rx(skb);
+ } else {
+ /* If the receive is not processed inside an ISR,
+ * the softirqd must be woken explicitly to service
+ * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled
+ * by netif_rx_ni(), but in earlier kernels, we need
+ * to do it manually.
+ */
+ netif_rx_ni(skb);
+ }
+ }
+}
+
+void dhd_event(struct dhd_info *dhd, char *evpkt, int evlen, int ifidx)
+{
+ /* Linux version has nothing to do */
+ return;
+}
+
+void dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success)
+{
+ uint ifidx;
+ dhd_info_t *dhd = (dhd_info_t *) (dhdp->info);
+ struct ether_header *eh;
+ u16 type;
+
+ dhd_prot_hdrpull(dhdp, &ifidx, txp);
+
+ eh = (struct ether_header *)PKTDATA(txp);
+ type = ntoh16(eh->ether_type);
+
+ if (type == ETHER_TYPE_802_1X)
+ atomic_dec(&dhd->pend_8021x_cnt);
+
+}
+
+static struct net_device_stats *dhd_get_stats(struct net_device *net)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+ dhd_if_t *ifp;
+ int ifidx;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ifidx = dhd_net2idx(dhd, net);
+ if (ifidx == DHD_BAD_IF)
+ return NULL;
+
+ ifp = dhd->iflist[ifidx];
+ ASSERT(dhd && ifp);
+
+ if (dhd->pub.up) {
+ /* Use the protocol to get dongle stats */
+ dhd_prot_dstats(&dhd->pub);
+ }
+
+ /* Copy dongle stats to net device stats */
+ ifp->stats.rx_packets = dhd->pub.dstats.rx_packets;
+ ifp->stats.tx_packets = dhd->pub.dstats.tx_packets;
+ ifp->stats.rx_bytes = dhd->pub.dstats.rx_bytes;
+ ifp->stats.tx_bytes = dhd->pub.dstats.tx_bytes;
+ ifp->stats.rx_errors = dhd->pub.dstats.rx_errors;
+ ifp->stats.tx_errors = dhd->pub.dstats.tx_errors;
+ ifp->stats.rx_dropped = dhd->pub.dstats.rx_dropped;
+ ifp->stats.tx_dropped = dhd->pub.dstats.tx_dropped;
+ ifp->stats.multicast = dhd->pub.dstats.multicast;
+
+ return &ifp->stats;
+}
+
+static int dhd_watchdog_thread(void *data)
+{
+ dhd_info_t *dhd = (dhd_info_t *) data;
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_WATCHDOG, "dhd_watchdog_thread");
+
+ /* This thread doesn't need any user-level access,
+ * so get rid of all our resources
+ */
+#ifdef DHD_SCHED
+ if (dhd_watchdog_prio > 0) {
+ struct sched_param param;
+ param.sched_priority = (dhd_watchdog_prio < MAX_RT_PRIO) ?
+ dhd_watchdog_prio : (MAX_RT_PRIO - 1);
+ setScheduler(current, SCHED_FIFO, &param);
+ }
+#endif /* DHD_SCHED */
+
+ allow_signal(SIGTERM);
+ /* Run until signal received */
+ while (1) {
+ if (kthread_should_stop())
+ break;
+ if (down_interruptible(&dhd->watchdog_sem) == 0) {
+ if (dhd->pub.dongle_reset == false) {
+ WAKE_LOCK(&dhd->pub, WAKE_LOCK_WATCHDOG);
+ /* Call the bus module watchdog */
+ dhd_bus_watchdog(&dhd->pub);
+ WAKE_UNLOCK(&dhd->pub, WAKE_LOCK_WATCHDOG);
+ }
+ /* Count the tick for reference */
+ dhd->pub.tickcnt++;
+ } else
+ break;
+ }
+
+ WAKE_LOCK_DESTROY(&dhd->pub, WAKE_LOCK_WATCHDOG);
+ return 0;
+}
+
+static void dhd_watchdog(unsigned long data)
+{
+ dhd_info_t *dhd = (dhd_info_t *) data;
+
+ if (dhd->watchdog_tsk) {
+ up(&dhd->watchdog_sem);
+
+ /* Reschedule the watchdog */
+ if (dhd->wd_timer_valid) {
+ mod_timer(&dhd->timer,
+ jiffies + dhd_watchdog_ms * HZ / 1000);
+ }
+ return;
+ }
+
+ /* Call the bus module watchdog */
+ dhd_bus_watchdog(&dhd->pub);
+
+ /* Count the tick for reference */
+ dhd->pub.tickcnt++;
+
+ /* Reschedule the watchdog */
+ if (dhd->wd_timer_valid)
+ mod_timer(&dhd->timer, jiffies + dhd_watchdog_ms * HZ / 1000);
+}
+
+static int dhd_dpc_thread(void *data)
+{
+ dhd_info_t *dhd = (dhd_info_t *) data;
+
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_DPC, "dhd_dpc_thread");
+ /* This thread doesn't need any user-level access,
+ * so get rid of all our resources
+ */
+#ifdef DHD_SCHED
+ if (dhd_dpc_prio > 0) {
+ struct sched_param param;
+ param.sched_priority =
+ (dhd_dpc_prio <
+ MAX_RT_PRIO) ? dhd_dpc_prio : (MAX_RT_PRIO - 1);
+ setScheduler(current, SCHED_FIFO, &param);
+ }
+#endif /* DHD_SCHED */
+
+ allow_signal(SIGTERM);
+ /* Run until signal received */
+ while (1) {
+ if (kthread_should_stop())
+ break;
+ if (down_interruptible(&dhd->dpc_sem) == 0) {
+ /* Call bus dpc unless it indicated down
+ (then clean stop) */
+ if (dhd->pub.busstate != DHD_BUS_DOWN) {
+ WAKE_LOCK(&dhd->pub, WAKE_LOCK_DPC);
+ if (dhd_bus_dpc(dhd->pub.bus)) {
+ up(&dhd->dpc_sem);
+ WAKE_LOCK_TIMEOUT(&dhd->pub,
+ WAKE_LOCK_TMOUT, 25);
+ }
+ WAKE_UNLOCK(&dhd->pub, WAKE_LOCK_DPC);
+ } else {
+ dhd_bus_stop(dhd->pub.bus, true);
+ }
+ } else
+ break;
+ }
+
+ WAKE_LOCK_DESTROY(&dhd->pub, WAKE_LOCK_DPC);
+ return 0;
+}
+
+static void dhd_dpc(unsigned long data)
+{
+ dhd_info_t *dhd;
+
+ dhd = (dhd_info_t *) data;
+
+ /* Call bus dpc unless it indicated down (then clean stop) */
+ if (dhd->pub.busstate != DHD_BUS_DOWN) {
+ if (dhd_bus_dpc(dhd->pub.bus))
+ tasklet_schedule(&dhd->tasklet);
+ } else {
+ dhd_bus_stop(dhd->pub.bus, true);
+ }
+}
+
+void dhd_sched_dpc(dhd_pub_t *dhdp)
+{
+ dhd_info_t *dhd = (dhd_info_t *) dhdp->info;
+
+ if (dhd->dpc_tsk) {
+ up(&dhd->dpc_sem);
+ return;
+ }
+
+ tasklet_schedule(&dhd->tasklet);
+}
+
+#ifdef TOE
+/* Retrieve current toe component enables, which are kept
+ as a bitmap in toe_ol iovar */
+static int dhd_toe_get(dhd_info_t *dhd, int ifidx, u32 *toe_ol)
+{
+ wl_ioctl_t ioc;
+ char buf[32];
+ int ret;
+
+ memset(&ioc, 0, sizeof(ioc));
+
+ ioc.cmd = WLC_GET_VAR;
+ ioc.buf = buf;
+ ioc.len = (uint) sizeof(buf);
+ ioc.set = false;
+
+ strcpy(buf, "toe_ol");
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ /* Check for older dongle image that doesn't support toe_ol */
+ if (ret == -EIO) {
+ DHD_ERROR(("%s: toe not supported by device\n",
+ dhd_ifname(&dhd->pub, ifidx)));
+ return -EOPNOTSUPP;
+ }
+
+ DHD_INFO(("%s: could not get toe_ol: ret=%d\n",
+ dhd_ifname(&dhd->pub, ifidx), ret));
+ return ret;
+ }
+
+ memcpy(toe_ol, buf, sizeof(u32));
+ return 0;
+}
+
+/* Set current toe component enables in toe_ol iovar,
+ and set toe global enable iovar */
+static int dhd_toe_set(dhd_info_t *dhd, int ifidx, u32 toe_ol)
+{
+ wl_ioctl_t ioc;
+ char buf[32];
+ int toe, ret;
+
+ memset(&ioc, 0, sizeof(ioc));
+
+ ioc.cmd = WLC_SET_VAR;
+ ioc.buf = buf;
+ ioc.len = (uint) sizeof(buf);
+ ioc.set = true;
+
+ /* Set toe_ol as requested */
+
+ strcpy(buf, "toe_ol");
+ memcpy(&buf[sizeof("toe_ol")], &toe_ol, sizeof(u32));
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: could not set toe_ol: ret=%d\n",
+ dhd_ifname(&dhd->pub, ifidx), ret));
+ return ret;
+ }
+
+ /* Enable toe globally only if any components are enabled. */
+
+ toe = (toe_ol != 0);
+
+ strcpy(buf, "toe");
+ memcpy(&buf[sizeof("toe")], &toe, sizeof(u32));
+
+ ret = dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (ret < 0) {
+ DHD_ERROR(("%s: could not set toe: ret=%d\n",
+ dhd_ifname(&dhd->pub, ifidx), ret));
+ return ret;
+ }
+
+ return 0;
+}
+#endif /* TOE */
+
+static void dhd_ethtool_get_drvinfo(struct net_device *net,
+ struct ethtool_drvinfo *info)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+
+ sprintf(info->driver, DRV_MODULE_NAME);
+ sprintf(info->version, "%lu", dhd->pub.drv_version);
+ sprintf(info->fw_version, "%s", wl_cfg80211_get_fwname());
+ sprintf(info->bus_info, "%s", dev_name(&wl_cfg80211_get_sdio_func()->dev));
+}
+
+struct ethtool_ops dhd_ethtool_ops = {
+ .get_drvinfo = dhd_ethtool_get_drvinfo
+};
+
+static int dhd_ethtool(dhd_info_t *dhd, void *uaddr)
+{
+ struct ethtool_drvinfo info;
+ char drvname[sizeof(info.driver)];
+ u32 cmd;
+#ifdef TOE
+ struct ethtool_value edata;
+ u32 toe_cmpnt, csum_dir;
+ int ret;
+#endif
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* all ethtool calls start with a cmd word */
+ if (copy_from_user(&cmd, uaddr, sizeof(u32)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case ETHTOOL_GDRVINFO:
+ /* Copy out any request driver name */
+ if (copy_from_user(&info, uaddr, sizeof(info)))
+ return -EFAULT;
+ strncpy(drvname, info.driver, sizeof(info.driver));
+ drvname[sizeof(info.driver) - 1] = '\0';
+
+ /* clear struct for return */
+ memset(&info, 0, sizeof(info));
+ info.cmd = cmd;
+
+ /* if dhd requested, identify ourselves */
+ if (strcmp(drvname, "?dhd") == 0) {
+ sprintf(info.driver, "dhd");
+ strcpy(info.version, EPI_VERSION_STR);
+ }
+
+ /* otherwise, require dongle to be up */
+ else if (!dhd->pub.up) {
+ DHD_ERROR(("%s: dongle is not up\n", __func__));
+ return -ENODEV;
+ }
+
+ /* finally, report dongle driver type */
+ else if (dhd->pub.iswl)
+ sprintf(info.driver, "wl");
+ else
+ sprintf(info.driver, "xx");
+
+ sprintf(info.version, "%lu", dhd->pub.drv_version);
+ if (copy_to_user(uaddr, &info, sizeof(info)))
+ return -EFAULT;
+ DHD_CTL(("%s: given %*s, returning %s\n", __func__,
+ (int)sizeof(drvname), drvname, info.driver));
+ break;
+
+#ifdef TOE
+ /* Get toe offload components from dongle */
+ case ETHTOOL_GRXCSUM:
+ case ETHTOOL_GTXCSUM:
+ ret = dhd_toe_get(dhd, 0, &toe_cmpnt);
+ if (ret < 0)
+ return ret;
+
+ csum_dir =
+ (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL;
+
+ edata.cmd = cmd;
+ edata.data = (toe_cmpnt & csum_dir) ? 1 : 0;
+
+ if (copy_to_user(uaddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ break;
+
+ /* Set toe offload components in dongle */
+ case ETHTOOL_SRXCSUM:
+ case ETHTOOL_STXCSUM:
+ if (copy_from_user(&edata, uaddr, sizeof(edata)))
+ return -EFAULT;
+
+ /* Read the current settings, update and write back */
+ ret = dhd_toe_get(dhd, 0, &toe_cmpnt);
+ if (ret < 0)
+ return ret;
+
+ csum_dir =
+ (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL;
+
+ if (edata.data != 0)
+ toe_cmpnt |= csum_dir;
+ else
+ toe_cmpnt &= ~csum_dir;
+
+ ret = dhd_toe_set(dhd, 0, toe_cmpnt);
+ if (ret < 0)
+ return ret;
+
+ /* If setting TX checksum mode, tell Linux the new mode */
+ if (cmd == ETHTOOL_STXCSUM) {
+ if (edata.data)
+ dhd->iflist[0]->net->features |=
+ NETIF_F_IP_CSUM;
+ else
+ dhd->iflist[0]->net->features &=
+ ~NETIF_F_IP_CSUM;
+ }
+
+ break;
+#endif /* TOE */
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int dhd_ioctl_entry(struct net_device *net, struct ifreq *ifr, int cmd)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+ dhd_ioctl_t ioc;
+ int bcmerror = 0;
+ int buflen = 0;
+ void *buf = NULL;
+ uint driver = 0;
+ int ifidx;
+ bool is_set_key_cmd;
+
+ ifidx = dhd_net2idx(dhd, net);
+ DHD_TRACE(("%s: ifidx %d, cmd 0x%04x\n", __func__, ifidx, cmd));
+
+ if (ifidx == DHD_BAD_IF)
+ return -1;
+
+#if defined(CONFIG_WIRELESS_EXT)
+ /* linux wireless extensions */
+ if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST)) {
+ /* may recurse, do NOT lock */
+ return wl_iw_ioctl(net, ifr, cmd);
+ }
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+ if (cmd == SIOCETHTOOL)
+ return dhd_ethtool(dhd, (void *)ifr->ifr_data);
+
+ if (cmd != SIOCDEVPRIVATE)
+ return -EOPNOTSUPP;
+
+ memset(&ioc, 0, sizeof(ioc));
+
+ /* Copy the ioc control structure part of ioctl request */
+ if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t))) {
+ bcmerror = -BCME_BADADDR;
+ goto done;
+ }
+
+ /* Copy out any buffer passed */
+ if (ioc.buf) {
+ buflen = min_t(int, ioc.len, DHD_IOCTL_MAXLEN);
+ /* optimization for direct ioctl calls from kernel */
+ /*
+ if (segment_eq(get_fs(), KERNEL_DS)) {
+ buf = ioc.buf;
+ } else {
+ */
+ {
+ buf = kmalloc(buflen, GFP_ATOMIC);
+ if (!buf) {
+ bcmerror = -BCME_NOMEM;
+ goto done;
+ }
+ if (copy_from_user(buf, ioc.buf, buflen)) {
+ bcmerror = -BCME_BADADDR;
+ goto done;
+ }
+ }
+ }
+
+ /* To differentiate between wl and dhd read 4 more byes */
+ if ((copy_from_user(&driver, (char *)ifr->ifr_data + sizeof(wl_ioctl_t),
+ sizeof(uint)) != 0)) {
+ bcmerror = -BCME_BADADDR;
+ goto done;
+ }
+
+ if (!capable(CAP_NET_ADMIN)) {
+ bcmerror = -BCME_EPERM;
+ goto done;
+ }
+
+ /* check for local dhd ioctl and handle it */
+ if (driver == DHD_IOCTL_MAGIC) {
+ bcmerror = dhd_ioctl((void *)&dhd->pub, &ioc, buf, buflen);
+ if (bcmerror)
+ dhd->pub.bcmerror = bcmerror;
+ goto done;
+ }
+
+ /* send to dongle (must be up, and wl) */
+ if ((dhd->pub.busstate != DHD_BUS_DATA)) {
+ DHD_ERROR(("%s DONGLE_DOWN,__func__\n", __func__));
+ bcmerror = BCME_DONGLE_DOWN;
+ goto done;
+ }
+
+ if (!dhd->pub.iswl) {
+ bcmerror = BCME_DONGLE_DOWN;
+ goto done;
+ }
+
+ /* Intercept WLC_SET_KEY IOCTL - serialize M4 send and set key IOCTL to
+ * prevent M4 encryption.
+ */
+ is_set_key_cmd = ((ioc.cmd == WLC_SET_KEY) ||
+ ((ioc.cmd == WLC_SET_VAR) &&
+ !(strncmp("wsec_key", ioc.buf, 9))) ||
+ ((ioc.cmd == WLC_SET_VAR) &&
+ !(strncmp("bsscfg:wsec_key", ioc.buf, 15))));
+ if (is_set_key_cmd)
+ dhd_wait_pend8021x(net);
+
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_IOCTL, "dhd_ioctl_entry");
+ WAKE_LOCK(&dhd->pub, WAKE_LOCK_IOCTL);
+
+ bcmerror =
+ dhd_prot_ioctl(&dhd->pub, ifidx, (wl_ioctl_t *)&ioc, buf, buflen);
+
+ WAKE_UNLOCK(&dhd->pub, WAKE_LOCK_IOCTL);
+ WAKE_LOCK_DESTROY(&dhd->pub, WAKE_LOCK_IOCTL);
+done:
+ if (!bcmerror && buf && ioc.buf) {
+ if (copy_to_user(ioc.buf, buf, buflen))
+ bcmerror = -EFAULT;
+ }
+
+ if (buf)
+ kfree(buf);
+
+ return OSL_ERROR(bcmerror);
+}
+
+static int dhd_stop(struct net_device *net)
+{
+#if !defined(IGNORE_ETH0_DOWN)
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+ if (IS_CFG80211_FAVORITE()) {
+ wl_cfg80211_down();
+ }
+ if (dhd->pub.up == 0)
+ return 0;
+
+ /* Set state and stop OS transmissions */
+ dhd->pub.up = 0;
+ netif_stop_queue(net);
+#else
+ DHD_ERROR(("BYPASS %s:due to BRCM compilation : under investigation\n",
+ __func__));
+#endif /* !defined(IGNORE_ETH0_DOWN) */
+
+ return 0;
+}
+
+static int dhd_open(struct net_device *net)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net);
+#ifdef TOE
+ u32 toe_ol;
+#endif
+ int ifidx = dhd_net2idx(dhd, net);
+ s32 ret = 0;
+
+ DHD_TRACE(("%s: ifidx %d\n", __func__, ifidx));
+
+ if (ifidx == 0) { /* do it only for primary eth0 */
+
+ /* try to bring up bus */
+ ret = dhd_bus_start(&dhd->pub);
+ if (ret != 0) {
+ DHD_ERROR(("%s: failed with code %d\n", __func__, ret));
+ return -1;
+ }
+ atomic_set(&dhd->pend_8021x_cnt, 0);
+
+ memcpy(net->dev_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN);
+
+#ifdef TOE
+ /* Get current TOE mode from dongle */
+ if (dhd_toe_get(dhd, ifidx, &toe_ol) >= 0
+ && (toe_ol & TOE_TX_CSUM_OL) != 0)
+ dhd->iflist[ifidx]->net->features |= NETIF_F_IP_CSUM;
+ else
+ dhd->iflist[ifidx]->net->features &= ~NETIF_F_IP_CSUM;
+#endif
+ }
+ /* Allow transmit calls */
+ netif_start_queue(net);
+ dhd->pub.up = 1;
+ if (IS_CFG80211_FAVORITE()) {
+ if (unlikely(wl_cfg80211_up())) {
+ DHD_ERROR(("%s: failed to bring up cfg80211\n",
+ __func__));
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+osl_t *dhd_osl_attach(void *pdev, uint bustype)
+{
+ return osl_attach(pdev, bustype, true);
+}
+
+void dhd_osl_detach(osl_t *osh)
+{
+ osl_detach(osh);
+}
+
+int
+dhd_add_if(dhd_info_t *dhd, int ifidx, void *handle, char *name,
+ u8 *mac_addr, u32 flags, u8 bssidx)
+{
+ dhd_if_t *ifp;
+
+ DHD_TRACE(("%s: idx %d, handle->%p\n", __func__, ifidx, handle));
+
+ ASSERT(dhd && (ifidx < DHD_MAX_IFS));
+
+ ifp = dhd->iflist[ifidx];
+ if (!ifp && !(ifp = kmalloc(sizeof(dhd_if_t), GFP_ATOMIC))) {
+ DHD_ERROR(("%s: OOM - dhd_if_t\n", __func__));
+ return -ENOMEM;
+ }
+
+ memset(ifp, 0, sizeof(dhd_if_t));
+ ifp->info = dhd;
+ dhd->iflist[ifidx] = ifp;
+ strlcpy(ifp->name, name, IFNAMSIZ);
+ if (mac_addr != NULL)
+ memcpy(&ifp->mac_addr, mac_addr, ETHER_ADDR_LEN);
+
+ if (handle == NULL) {
+ ifp->state = WLC_E_IF_ADD;
+ ifp->idx = ifidx;
+ ASSERT(dhd->sysioc_tsk);
+ up(&dhd->sysioc_sem);
+ } else
+ ifp->net = (struct net_device *)handle;
+
+ return 0;
+}
+
+void dhd_del_if(dhd_info_t *dhd, int ifidx)
+{
+ dhd_if_t *ifp;
+
+ DHD_TRACE(("%s: idx %d\n", __func__, ifidx));
+
+ ASSERT(dhd && ifidx && (ifidx < DHD_MAX_IFS));
+ ifp = dhd->iflist[ifidx];
+ if (!ifp) {
+ DHD_ERROR(("%s: Null interface\n", __func__));
+ return;
+ }
+
+ ifp->state = WLC_E_IF_DEL;
+ ifp->idx = ifidx;
+ ASSERT(dhd->sysioc_tsk);
+ up(&dhd->sysioc_sem);
+}
+
+dhd_pub_t *dhd_attach(osl_t *osh, struct dhd_bus *bus, uint bus_hdrlen)
+{
+ dhd_info_t *dhd = NULL;
+ struct net_device *net;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+ /* updates firmware nvram path if it was provided as module
+ paramters */
+ if ((firmware_path != NULL) && (firmware_path[0] != '\0'))
+ strcpy(fw_path, firmware_path);
+ if ((nvram_path != NULL) && (nvram_path[0] != '\0'))
+ strcpy(nv_path, nvram_path);
+
+ /* Allocate etherdev, including space for private structure */
+ net = alloc_etherdev(sizeof(dhd));
+ if (!net) {
+ DHD_ERROR(("%s: OOM - alloc_etherdev\n", __func__));
+ goto fail;
+ }
+
+ /* Allocate primary dhd_info */
+ dhd = kmalloc(sizeof(dhd_info_t), GFP_ATOMIC);
+ if (!dhd) {
+ DHD_ERROR(("%s: OOM - alloc dhd_info\n", __func__));
+ goto fail;
+ }
+
+ memset(dhd, 0, sizeof(dhd_info_t));
+
+ /*
+ * Save the dhd_info into the priv
+ */
+ memcpy(netdev_priv(net), &dhd, sizeof(dhd));
+ dhd->pub.osh = osh;
+
+ /* Set network interface name if it was provided as module parameter */
+ if (iface_name[0]) {
+ int len;
+ char ch;
+ strncpy(net->name, iface_name, IFNAMSIZ);
+ net->name[IFNAMSIZ - 1] = 0;
+ len = strlen(net->name);
+ ch = net->name[len - 1];
+ if ((ch > '9' || ch < '0') && (len < IFNAMSIZ - 2))
+ strcat(net->name, "%d");
+ }
+
+ if (dhd_add_if(dhd, 0, (void *)net, net->name, NULL, 0, 0) ==
+ DHD_BAD_IF)
+ goto fail;
+
+ net->netdev_ops = NULL;
+ sema_init(&dhd->proto_sem, 1);
+ /* Initialize other structure content */
+ init_waitqueue_head(&dhd->ioctl_resp_wait);
+ init_waitqueue_head(&dhd->ctrl_wait);
+
+ /* Initialize the spinlocks */
+ spin_lock_init(&dhd->sdlock);
+ spin_lock_init(&dhd->txqlock);
+
+ /* Link to info module */
+ dhd->pub.info = dhd;
+
+ /* Link to bus module */
+ dhd->pub.bus = bus;
+ dhd->pub.hdrlen = bus_hdrlen;
+
+ /* Attach and link in the protocol */
+ if (dhd_prot_attach(&dhd->pub) != 0) {
+ DHD_ERROR(("dhd_prot_attach failed\n"));
+ goto fail;
+ }
+#if defined(CONFIG_WIRELESS_EXT)
+ /* Attach and link in the iw */
+ if (wl_iw_attach(net, (void *)&dhd->pub) != 0) {
+ DHD_ERROR(("wl_iw_attach failed\n"));
+ goto fail;
+ }
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+ /* Attach and link in the cfg80211 */
+ if (IS_CFG80211_FAVORITE()) {
+ if (unlikely(wl_cfg80211_attach(net, &dhd->pub))) {
+ DHD_ERROR(("wl_cfg80211_attach failed\n"));
+ goto fail;
+ }
+ if (!NO_FW_REQ()) {
+ strcpy(fw_path, wl_cfg80211_get_fwname());
+ strcpy(nv_path, wl_cfg80211_get_nvramname());
+ }
+ wl_cfg80211_dbg_level(DBG_CFG80211_GET());
+ }
+
+ /* Set up the watchdog timer */
+ init_timer(&dhd->timer);
+ dhd->timer.data = (unsigned long) dhd;
+ dhd->timer.function = dhd_watchdog;
+
+ /* Initialize thread based operation and lock */
+ sema_init(&dhd->sdsem, 1);
+ if ((dhd_watchdog_prio >= 0) && (dhd_dpc_prio >= 0))
+ dhd->threads_only = true;
+ else
+ dhd->threads_only = false;
+
+ if (dhd_dpc_prio >= 0) {
+ /* Initialize watchdog thread */
+ sema_init(&dhd->watchdog_sem, 0);
+ dhd->watchdog_tsk = kthread_run(dhd_watchdog_thread, dhd,
+ "dhd_watchdog");
+ if (IS_ERR(dhd->watchdog_tsk)) {
+ printk(KERN_WARNING
+ "dhd_watchdog thread failed to start\n");
+ dhd->watchdog_tsk = NULL;
+ }
+ } else {
+ dhd->watchdog_tsk = NULL;
+ }
+
+ /* Set up the bottom half handler */
+ if (dhd_dpc_prio >= 0) {
+ /* Initialize DPC thread */
+ sema_init(&dhd->dpc_sem, 0);
+ dhd->dpc_tsk = kthread_run(dhd_dpc_thread, dhd, "dhd_dpc");
+ if (IS_ERR(dhd->dpc_tsk)) {
+ printk(KERN_WARNING
+ "dhd_dpc thread failed to start\n");
+ dhd->dpc_tsk = NULL;
+ }
+ } else {
+ tasklet_init(&dhd->tasklet, dhd_dpc, (unsigned long) dhd);
+ dhd->dpc_tsk = NULL;
+ }
+
+ if (dhd_sysioc) {
+ sema_init(&dhd->sysioc_sem, 0);
+ dhd->sysioc_tsk = kthread_run(_dhd_sysioc_thread, dhd,
+ "_dhd_sysioc");
+ if (IS_ERR(dhd->sysioc_tsk)) {
+ printk(KERN_WARNING
+ "_dhd_sysioc thread failed to start\n");
+ dhd->sysioc_tsk = NULL;
+ }
+ } else
+ dhd->sysioc_tsk = NULL;
+
+ /*
+ * Save the dhd_info into the priv
+ */
+ memcpy(netdev_priv(net), &dhd, sizeof(dhd));
+
+#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC)
+ g_bus = bus;
+#endif
+#if defined(CONFIG_PM_SLEEP)
+ register_pm_notifier(&dhd_sleep_pm_notifier);
+#endif /* defined(CONFIG_PM_SLEEP) */
+ /* && defined(DHD_GPL) */
+ /* Init lock suspend to prevent kernel going to suspend */
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_TMOUT, "dhd_wake_lock");
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_LINK_DOWN_TMOUT,
+ "dhd_wake_lock_link_dw_event");
+ WAKE_LOCK_INIT(&dhd->pub, WAKE_LOCK_PNO_FIND_TMOUT,
+ "dhd_wake_lock_link_pno_find_event");
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ dhd->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20;
+ dhd->early_suspend.suspend = dhd_early_suspend;
+ dhd->early_suspend.resume = dhd_late_resume;
+ register_early_suspend(&dhd->early_suspend);
+#endif
+
+ return &dhd->pub;
+
+fail:
+ if (net)
+ free_netdev(net);
+ if (dhd)
+ dhd_detach(&dhd->pub);
+
+ return NULL;
+}
+
+int dhd_bus_start(dhd_pub_t *dhdp)
+{
+ int ret = -1;
+ dhd_info_t *dhd = (dhd_info_t *) dhdp->info;
+#ifdef EMBEDDED_PLATFORM
+ char iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+#endif /* EMBEDDED_PLATFORM */
+
+ ASSERT(dhd);
+
+ DHD_TRACE(("%s:\n", __func__));
+
+ /* try to download image and nvram to the dongle */
+ if (dhd->pub.busstate == DHD_BUS_DOWN) {
+ WAKE_LOCK_INIT(dhdp, WAKE_LOCK_DOWNLOAD, "dhd_bus_start");
+ WAKE_LOCK(dhdp, WAKE_LOCK_DOWNLOAD);
+ if (!(dhd_bus_download_firmware(dhd->pub.bus, dhd->pub.osh,
+ fw_path, nv_path))) {
+ DHD_ERROR(("%s: dhdsdio_probe_download failed. "
+ "firmware = %s nvram = %s\n",
+ __func__, fw_path, nv_path));
+ WAKE_UNLOCK(dhdp, WAKE_LOCK_DOWNLOAD);
+ WAKE_LOCK_DESTROY(dhdp, WAKE_LOCK_DOWNLOAD);
+ return -1;
+ }
+
+ WAKE_UNLOCK(dhdp, WAKE_LOCK_DOWNLOAD);
+ WAKE_LOCK_DESTROY(dhdp, WAKE_LOCK_DOWNLOAD);
+ }
+
+ /* Start the watchdog timer */
+ dhd->pub.tickcnt = 0;
+ dhd_os_wd_timer(&dhd->pub, dhd_watchdog_ms);
+
+ /* Bring up the bus */
+ ret = dhd_bus_init(&dhd->pub, true);
+ if (ret != 0) {
+ DHD_ERROR(("%s, dhd_bus_init failed %d\n", __func__, ret));
+ return ret;
+ }
+#if defined(OOB_INTR_ONLY)
+ /* Host registration for OOB interrupt */
+ if (bcmsdh_register_oob_intr(dhdp)) {
+ del_timer_sync(&dhd->timer);
+ dhd->wd_timer_valid = false;
+ DHD_ERROR(("%s Host failed to resgister for OOB\n", __func__));
+ return -ENODEV;
+ }
+
+ /* Enable oob at firmware */
+ dhd_enable_oob_intr(dhd->pub.bus, true);
+#endif /* defined(OOB_INTR_ONLY) */
+
+ /* If bus is not ready, can't come up */
+ if (dhd->pub.busstate != DHD_BUS_DATA) {
+ del_timer_sync(&dhd->timer);
+ dhd->wd_timer_valid = false;
+ DHD_ERROR(("%s failed bus is not ready\n", __func__));
+ return -ENODEV;
+ }
+#ifdef EMBEDDED_PLATFORM
+ bcm_mkiovar("event_msgs", dhdp->eventmask, WL_EVENTING_MASK_LEN, iovbuf,
+ sizeof(iovbuf));
+ dhdcdc_query_ioctl(dhdp, 0, WLC_GET_VAR, iovbuf, sizeof(iovbuf));
+ bcopy(iovbuf, dhdp->eventmask, WL_EVENTING_MASK_LEN);
+
+ setbit(dhdp->eventmask, WLC_E_SET_SSID);
+ setbit(dhdp->eventmask, WLC_E_PRUNE);
+ setbit(dhdp->eventmask, WLC_E_AUTH);
+ setbit(dhdp->eventmask, WLC_E_REASSOC);
+ setbit(dhdp->eventmask, WLC_E_REASSOC_IND);
+ setbit(dhdp->eventmask, WLC_E_DEAUTH_IND);
+ setbit(dhdp->eventmask, WLC_E_DISASSOC_IND);
+ setbit(dhdp->eventmask, WLC_E_DISASSOC);
+ setbit(dhdp->eventmask, WLC_E_JOIN);
+ setbit(dhdp->eventmask, WLC_E_ASSOC_IND);
+ setbit(dhdp->eventmask, WLC_E_PSK_SUP);
+ setbit(dhdp->eventmask, WLC_E_LINK);
+ setbit(dhdp->eventmask, WLC_E_NDIS_LINK);
+ setbit(dhdp->eventmask, WLC_E_MIC_ERROR);
+ setbit(dhdp->eventmask, WLC_E_PMKID_CACHE);
+ setbit(dhdp->eventmask, WLC_E_TXFAIL);
+ setbit(dhdp->eventmask, WLC_E_JOIN_START);
+ setbit(dhdp->eventmask, WLC_E_SCAN_COMPLETE);
+#ifdef PNO_SUPPORT
+ setbit(dhdp->eventmask, WLC_E_PFN_NET_FOUND);
+#endif /* PNO_SUPPORT */
+
+/* enable dongle roaming event */
+
+ dhdp->pktfilter_count = 1;
+ /* Setup filter to allow only unicast */
+ dhdp->pktfilter[0] = "100 0 0 0 0x01 0x00";
+#endif /* EMBEDDED_PLATFORM */
+
+ /* Bus is ready, do any protocol initialization */
+ ret = dhd_prot_init(&dhd->pub);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int
+dhd_iovar(dhd_pub_t *pub, int ifidx, char *name, char *cmd_buf, uint cmd_len,
+ int set)
+{
+ char buf[strlen(name) + 1 + cmd_len];
+ int len = sizeof(buf);
+ wl_ioctl_t ioc;
+ int ret;
+
+ len = bcm_mkiovar(name, cmd_buf, cmd_len, buf, len);
+
+ memset(&ioc, 0, sizeof(ioc));
+
+ ioc.cmd = set ? WLC_SET_VAR : WLC_GET_VAR;
+ ioc.buf = buf;
+ ioc.len = len;
+ ioc.set = set;
+
+ ret = dhd_prot_ioctl(pub, ifidx, &ioc, ioc.buf, ioc.len);
+ if (!set && ret >= 0)
+ memcpy(cmd_buf, buf, cmd_len);
+
+ return ret;
+}
+
+static struct net_device_ops dhd_ops_pri = {
+ .ndo_open = dhd_open,
+ .ndo_stop = dhd_stop,
+ .ndo_get_stats = dhd_get_stats,
+ .ndo_do_ioctl = dhd_ioctl_entry,
+ .ndo_start_xmit = dhd_start_xmit,
+ .ndo_set_mac_address = dhd_set_mac_address,
+ .ndo_set_multicast_list = dhd_set_multicast_list
+};
+
+static struct net_device_ops dhd_ops_virt = {
+ .ndo_get_stats = dhd_get_stats,
+ .ndo_do_ioctl = dhd_ioctl_entry,
+ .ndo_start_xmit = dhd_start_xmit,
+ .ndo_set_mac_address = dhd_set_mac_address,
+ .ndo_set_multicast_list = dhd_set_multicast_list
+};
+
+int dhd_net_attach(dhd_pub_t *dhdp, int ifidx)
+{
+ dhd_info_t *dhd = (dhd_info_t *) dhdp->info;
+ struct net_device *net;
+ u8 temp_addr[ETHER_ADDR_LEN] = {
+ 0x00, 0x90, 0x4c, 0x11, 0x22, 0x33};
+
+ DHD_TRACE(("%s: ifidx %d\n", __func__, ifidx));
+
+ ASSERT(dhd && dhd->iflist[ifidx]);
+
+ net = dhd->iflist[ifidx]->net;
+ ASSERT(net);
+
+ ASSERT(!net->netdev_ops);
+ net->netdev_ops = &dhd_ops_virt;
+
+ net->netdev_ops = &dhd_ops_pri;
+
+ /*
+ * We have to use the primary MAC for virtual interfaces
+ */
+ if (ifidx != 0) {
+ /* for virtual interfaces use the primary MAC */
+ memcpy(temp_addr, dhd->pub.mac.octet, ETHER_ADDR_LEN);
+
+ }
+
+ if (ifidx == 1) {
+ DHD_TRACE(("%s ACCESS POINT MAC: \n", __func__));
+ /* ACCESSPOINT INTERFACE CASE */
+ temp_addr[0] |= 0X02; /* set bit 2 ,
+ - Locally Administered address */
+
+ }
+ net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen;
+ net->ethtool_ops = &dhd_ethtool_ops;
+
+#if defined(CONFIG_WIRELESS_EXT)
+ if (!IS_CFG80211_FAVORITE()) {
+#if WIRELESS_EXT < 19
+ net->get_wireless_stats = dhd_get_wireless_stats;
+#endif /* WIRELESS_EXT < 19 */
+#if WIRELESS_EXT > 12
+ net->wireless_handlers =
+ (struct iw_handler_def *)&wl_iw_handler_def;
+#endif /* WIRELESS_EXT > 12 */
+ }
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+ dhd->pub.rxsz = net->mtu + net->hard_header_len + dhd->pub.hdrlen;
+
+ memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN);
+
+ if (register_netdev(net) != 0) {
+ DHD_ERROR(("%s: couldn't register the net device\n",
+ __func__));
+ goto fail;
+ }
+
+ printf("%s: Broadcom Dongle Host Driver\n", net->name);
+
+ return 0;
+
+fail:
+ net->netdev_ops = NULL;
+ return BCME_ERROR;
+}
+
+void dhd_bus_detach(dhd_pub_t *dhdp)
+{
+ dhd_info_t *dhd;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (dhdp) {
+ dhd = (dhd_info_t *) dhdp->info;
+ if (dhd) {
+ /* Stop the protocol module */
+ dhd_prot_stop(&dhd->pub);
+
+ /* Stop the bus module */
+ dhd_bus_stop(dhd->pub.bus, true);
+#if defined(OOB_INTR_ONLY)
+ bcmsdh_unregister_oob_intr();
+#endif /* defined(OOB_INTR_ONLY) */
+
+ /* Clear the watchdog timer */
+ del_timer_sync(&dhd->timer);
+ dhd->wd_timer_valid = false;
+ }
+ }
+}
+
+void dhd_detach(dhd_pub_t *dhdp)
+{
+ dhd_info_t *dhd;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (dhdp) {
+ dhd = (dhd_info_t *) dhdp->info;
+ if (dhd) {
+ dhd_if_t *ifp;
+ int i;
+
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+ if (dhd->early_suspend.suspend)
+ unregister_early_suspend(&dhd->early_suspend);
+#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
+
+ for (i = 1; i < DHD_MAX_IFS; i++)
+ if (dhd->iflist[i])
+ dhd_del_if(dhd, i);
+
+ ifp = dhd->iflist[0];
+ ASSERT(ifp);
+ if (ifp->net->netdev_ops == &dhd_ops_pri) {
+ dhd_stop(ifp->net);
+ unregister_netdev(ifp->net);
+ }
+
+ if (dhd->watchdog_tsk) {
+ send_sig(SIGTERM, dhd->watchdog_tsk, 1);
+ kthread_stop(dhd->watchdog_tsk);
+ dhd->watchdog_tsk = NULL;
+ }
+
+ if (dhd->dpc_tsk) {
+ send_sig(SIGTERM, dhd->dpc_tsk, 1);
+ kthread_stop(dhd->dpc_tsk);
+ dhd->dpc_tsk = NULL;
+ } else
+ tasklet_kill(&dhd->tasklet);
+
+ if (dhd->sysioc_tsk) {
+ send_sig(SIGTERM, dhd->sysioc_tsk, 1);
+ kthread_stop(dhd->sysioc_tsk);
+ dhd->sysioc_tsk = NULL;
+ }
+
+ dhd_bus_detach(dhdp);
+
+ if (dhdp->prot)
+ dhd_prot_detach(dhdp);
+
+#if defined(CONFIG_WIRELESS_EXT)
+ wl_iw_detach();
+#endif /* (CONFIG_WIRELESS_EXT) */
+
+ if (IS_CFG80211_FAVORITE())
+ wl_cfg80211_detach();
+
+#if defined(CONFIG_PM_SLEEP)
+ unregister_pm_notifier(&dhd_sleep_pm_notifier);
+#endif /* defined(CONFIG_PM_SLEEP) */
+ /* && defined(DHD_GPL) */
+ WAKE_LOCK_DESTROY(dhdp, WAKE_LOCK_TMOUT);
+ WAKE_LOCK_DESTROY(dhdp, WAKE_LOCK_LINK_DOWN_TMOUT);
+ WAKE_LOCK_DESTROY(dhdp, WAKE_LOCK_PNO_FIND_TMOUT);
+ free_netdev(ifp->net);
+ kfree(ifp);
+ kfree(dhd);
+ }
+ }
+}
+
+static void __exit dhd_module_cleanup(void)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ dhd_bus_unregister();
+#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC)
+ wifi_del_dev();
+#endif
+ /* Call customer gpio to turn off power with WL_REG_ON signal */
+ dhd_customer_gpio_wlan_ctrl(WLAN_POWER_OFF);
+}
+
+static int __init dhd_module_init(void)
+{
+ int error;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Sanity check on the module parameters */
+ do {
+ /* Both watchdog and DPC as tasklets are ok */
+ if ((dhd_watchdog_prio < 0) && (dhd_dpc_prio < 0))
+ break;
+
+ /* If both watchdog and DPC are threads, TX must be deferred */
+ if ((dhd_watchdog_prio >= 0) && (dhd_dpc_prio >= 0)
+ && dhd_deferred_tx)
+ break;
+
+ DHD_ERROR(("Invalid module parameters.\n"));
+ return -EINVAL;
+ } while (0);
+ /* Call customer gpio to turn on power with WL_REG_ON signal */
+ dhd_customer_gpio_wlan_ctrl(WLAN_POWER_ON);
+
+#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC)
+ sema_init(&wifi_control_sem, 0);
+
+ error = wifi_add_dev();
+ if (error) {
+ DHD_ERROR(("%s: platform_driver_register failed\n", __func__));
+ goto faild;
+ }
+
+ /* Waiting callback after platform_driver_register is done or
+ exit with error */
+ if (down_timeout(&wifi_control_sem, msecs_to_jiffies(1000)) != 0) {
+ printk(KERN_ERR "%s: platform_driver_register timeout\n",
+ __func__);
+ /* remove device */
+ wifi_del_dev();
+ goto faild;
+ }
+#endif /* #if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */
+
+ error = dhd_bus_register();
+
+ if (!error)
+ printf("\n%s\n", dhd_version);
+ else {
+ DHD_ERROR(("%s: sdio_register_driver failed\n", __func__));
+ goto faild;
+ }
+ return error;
+
+faild:
+ /* turn off power and exit */
+ dhd_customer_gpio_wlan_ctrl(WLAN_POWER_OFF);
+ return -EINVAL;
+}
+
+module_init(dhd_module_init);
+module_exit(dhd_module_cleanup);
+
+/*
+ * OS specific functions required to implement DHD driver in OS independent way
+ */
+int dhd_os_proto_block(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd = (dhd_info_t *) (pub->info);
+
+ if (dhd) {
+ down(&dhd->proto_sem);
+ return 1;
+ }
+ return 0;
+}
+
+int dhd_os_proto_unblock(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd = (dhd_info_t *) (pub->info);
+
+ if (dhd) {
+ up(&dhd->proto_sem);
+ return 1;
+ }
+
+ return 0;
+}
+
+unsigned int dhd_os_get_ioctl_resp_timeout(void)
+{
+ return (unsigned int)dhd_ioctl_timeout_msec;
+}
+
+void dhd_os_set_ioctl_resp_timeout(unsigned int timeout_msec)
+{
+ dhd_ioctl_timeout_msec = (int)timeout_msec;
+}
+
+int dhd_os_ioctl_resp_wait(dhd_pub_t *pub, uint *condition, bool *pending)
+{
+ dhd_info_t *dhd = (dhd_info_t *) (pub->info);
+ DECLARE_WAITQUEUE(wait, current);
+ int timeout = dhd_ioctl_timeout_msec;
+
+ /* Convert timeout in millsecond to jiffies */
+ timeout = timeout * HZ / 1000;
+
+ /* Wait until control frame is available */
+ add_wait_queue(&dhd->ioctl_resp_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ while (!(*condition) && (!signal_pending(current) && timeout))
+ timeout = schedule_timeout(timeout);
+
+ if (signal_pending(current))
+ *pending = true;
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&dhd->ioctl_resp_wait, &wait);
+
+ return timeout;
+}
+
+int dhd_os_ioctl_resp_wake(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd = (dhd_info_t *) (pub->info);
+
+ if (waitqueue_active(&dhd->ioctl_resp_wait))
+ wake_up_interruptible(&dhd->ioctl_resp_wait);
+
+ return 0;
+}
+
+void dhd_os_wd_timer(void *bus, uint wdtick)
+{
+ dhd_pub_t *pub = bus;
+ static uint save_dhd_watchdog_ms;
+ dhd_info_t *dhd = (dhd_info_t *) pub->info;
+
+ /* don't start the wd until fw is loaded */
+ if (pub->busstate == DHD_BUS_DOWN)
+ return;
+
+ /* Totally stop the timer */
+ if (!wdtick && dhd->wd_timer_valid == true) {
+ del_timer_sync(&dhd->timer);
+ dhd->wd_timer_valid = false;
+ save_dhd_watchdog_ms = wdtick;
+ return;
+ }
+
+ if (wdtick) {
+ dhd_watchdog_ms = (uint) wdtick;
+
+ if (save_dhd_watchdog_ms != dhd_watchdog_ms) {
+
+ if (dhd->wd_timer_valid == true)
+ /* Stop timer and restart at new value */
+ del_timer_sync(&dhd->timer);
+
+ /* Create timer again when watchdog period is
+ dynamically changed or in the first instance
+ */
+ dhd->timer.expires =
+ jiffies + dhd_watchdog_ms * HZ / 1000;
+ add_timer(&dhd->timer);
+
+ } else {
+ /* Re arm the timer, at last watchdog period */
+ mod_timer(&dhd->timer,
+ jiffies + dhd_watchdog_ms * HZ / 1000);
+ }
+
+ dhd->wd_timer_valid = true;
+ save_dhd_watchdog_ms = wdtick;
+ }
+}
+
+void *dhd_os_open_image(char *filename)
+{
+ struct file *fp;
+
+ if (IS_CFG80211_FAVORITE() && !NO_FW_REQ())
+ return wl_cfg80211_request_fw(filename);
+
+ fp = filp_open(filename, O_RDONLY, 0);
+ /*
+ * 2.6.11 (FC4) supports filp_open() but later revs don't?
+ * Alternative:
+ * fp = open_namei(AT_FDCWD, filename, O_RD, 0);
+ * ???
+ */
+ if (IS_ERR(fp))
+ fp = NULL;
+
+ return fp;
+}
+
+int dhd_os_get_image_block(char *buf, int len, void *image)
+{
+ struct file *fp = (struct file *)image;
+ int rdlen;
+
+ if (IS_CFG80211_FAVORITE() && !NO_FW_REQ())
+ return wl_cfg80211_read_fw(buf, len);
+
+ if (!image)
+ return 0;
+
+ rdlen = kernel_read(fp, fp->f_pos, buf, len);
+ if (rdlen > 0)
+ fp->f_pos += rdlen;
+
+ return rdlen;
+}
+
+void dhd_os_close_image(void *image)
+{
+ if (IS_CFG80211_FAVORITE() && !NO_FW_REQ())
+ return wl_cfg80211_release_fw();
+ if (image)
+ filp_close((struct file *)image, NULL);
+}
+
+void dhd_os_sdlock(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd;
+
+ dhd = (dhd_info_t *) (pub->info);
+
+ if (dhd->threads_only)
+ down(&dhd->sdsem);
+ else
+ spin_lock_bh(&dhd->sdlock);
+}
+
+void dhd_os_sdunlock(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd;
+
+ dhd = (dhd_info_t *) (pub->info);
+
+ if (dhd->threads_only)
+ up(&dhd->sdsem);
+ else
+ spin_unlock_bh(&dhd->sdlock);
+}
+
+void dhd_os_sdlock_txq(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd;
+
+ dhd = (dhd_info_t *) (pub->info);
+ spin_lock_bh(&dhd->txqlock);
+}
+
+void dhd_os_sdunlock_txq(dhd_pub_t *pub)
+{
+ dhd_info_t *dhd;
+
+ dhd = (dhd_info_t *) (pub->info);
+ spin_unlock_bh(&dhd->txqlock);
+}
+
+void dhd_os_sdlock_rxq(dhd_pub_t *pub)
+{
+}
+
+void dhd_os_sdunlock_rxq(dhd_pub_t *pub)
+{
+}
+
+void dhd_os_sdtxlock(dhd_pub_t *pub)
+{
+ dhd_os_sdlock(pub);
+}
+
+void dhd_os_sdtxunlock(dhd_pub_t *pub)
+{
+ dhd_os_sdunlock(pub);
+}
+
+#if defined(CONFIG_WIRELESS_EXT)
+struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev)
+{
+ int res = 0;
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(dev);
+
+ res = wl_iw_get_wireless_stats(dev, &dhd->iw.wstats);
+
+ if (res == 0)
+ return &dhd->iw.wstats;
+ else
+ return NULL;
+}
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+static int
+dhd_wl_host_event(dhd_info_t *dhd, int *ifidx, void *pktdata,
+ wl_event_msg_t *event, void **data)
+{
+ int bcmerror = 0;
+
+ ASSERT(dhd != NULL);
+
+ bcmerror = wl_host_event(dhd, ifidx, pktdata, event, data);
+ if (bcmerror != BCME_OK)
+ return bcmerror;
+
+#if defined(CONFIG_WIRELESS_EXT)
+ if (!IS_CFG80211_FAVORITE()) {
+ if ((dhd->iflist[*ifidx] == NULL)
+ || (dhd->iflist[*ifidx]->net == NULL)) {
+ DHD_ERROR(("%s Exit null pointer\n", __func__));
+ return bcmerror;
+ }
+
+ if (dhd->iflist[*ifidx]->net)
+ wl_iw_event(dhd->iflist[*ifidx]->net, event, *data);
+ }
+#endif /* defined(CONFIG_WIRELESS_EXT) */
+
+ if (IS_CFG80211_FAVORITE()) {
+ ASSERT(dhd->iflist[*ifidx] != NULL);
+ ASSERT(dhd->iflist[*ifidx]->net != NULL);
+ if (dhd->iflist[*ifidx]->net)
+ wl_cfg80211_event(dhd->iflist[*ifidx]->net, event,
+ *data);
+ }
+
+ return bcmerror;
+}
+
+/* send up locally generated event */
+void dhd_sendup_event(dhd_pub_t *dhdp, wl_event_msg_t *event, void *data)
+{
+ switch (ntoh32(event->event_type)) {
+ default:
+ break;
+ }
+}
+
+void dhd_wait_for_event(dhd_pub_t *dhd, bool *lockvar)
+{
+ struct dhd_info *dhdinfo = dhd->info;
+ dhd_os_sdunlock(dhd);
+ wait_event_interruptible_timeout(dhdinfo->ctrl_wait,
+ (*lockvar == false), HZ * 2);
+ dhd_os_sdlock(dhd);
+ return;
+}
+
+void dhd_wait_event_wakeup(dhd_pub_t *dhd)
+{
+ struct dhd_info *dhdinfo = dhd->info;
+ if (waitqueue_active(&dhdinfo->ctrl_wait))
+ wake_up_interruptible(&dhdinfo->ctrl_wait);
+ return;
+}
+
+int dhd_dev_reset(struct net_device *dev, u8 flag)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ /* Turning off watchdog */
+ if (flag)
+ dhd_os_wd_timer(&dhd->pub, 0);
+
+ dhd_bus_devreset(&dhd->pub, flag);
+
+ /* Turning on watchdog back */
+ if (!flag)
+ dhd_os_wd_timer(&dhd->pub, dhd_watchdog_ms);
+ DHD_ERROR(("%s: WLAN OFF DONE\n", __func__));
+
+ return 1;
+}
+
+int net_os_set_suspend_disable(struct net_device *dev, int val)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+ int ret = 0;
+
+ if (dhd) {
+ ret = dhd->pub.suspend_disable_flag;
+ dhd->pub.suspend_disable_flag = val;
+ }
+ return ret;
+}
+
+int net_os_set_suspend(struct net_device *dev, int val)
+{
+ int ret = 0;
+#if defined(CONFIG_HAS_EARLYSUSPEND)
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ if (dhd) {
+ dhd_os_proto_block(&dhd->pub);
+ ret = dhd_set_suspend(val, &dhd->pub);
+ dhd_os_proto_unblock(&dhd->pub);
+ }
+#endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
+ return ret;
+}
+
+int net_os_set_dtim_skip(struct net_device *dev, int val)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(dev);
+
+ if (dhd)
+ dhd->pub.dtim_skip = val;
+
+ return 0;
+}
+
+int net_os_set_packet_filter(struct net_device *dev, int val)
+{
+ dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(dev);
+ int ret = 0;
+
+ /* Packet filtering is set only if we still in early-suspend and
+ * we need either to turn it ON or turn it OFF
+ * We can always turn it OFF in case of early-suspend, but we turn it
+ * back ON only if suspend_disable_flag was not set
+ */
+ if (dhd && dhd->pub.up) {
+ dhd_os_proto_block(&dhd->pub);
+ if (dhd->pub.in_suspend) {
+ if (!val || (val && !dhd->pub.suspend_disable_flag))
+ dhd_set_packet_filter(val, &dhd->pub);
+ }
+ dhd_os_proto_unblock(&dhd->pub);
+ }
+ return ret;
+}
+
+void dhd_dev_init_ioctl(struct net_device *dev)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ dhd_preinit_ioctls(&dhd->pub);
+}
+
+#ifdef PNO_SUPPORT
+/* Linux wrapper to call common dhd_pno_clean */
+int dhd_dev_pno_reset(struct net_device *dev)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ return dhd_pno_clean(&dhd->pub);
+}
+
+/* Linux wrapper to call common dhd_pno_enable */
+int dhd_dev_pno_enable(struct net_device *dev, int pfn_enabled)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ return dhd_pno_enable(&dhd->pub, pfn_enabled);
+}
+
+/* Linux wrapper to call common dhd_pno_set */
+int
+dhd_dev_pno_set(struct net_device *dev, wlc_ssid_t *ssids_local, int nssid,
+ unsigned char scan_fr)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ return dhd_pno_set(&dhd->pub, ssids_local, nssid, scan_fr);
+}
+
+/* Linux wrapper to get pno status */
+int dhd_dev_get_pno_status(struct net_device *dev)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+
+ return dhd_pno_get_status(&dhd->pub);
+}
+
+#endif /* PNO_SUPPORT */
+
+static int dhd_get_pend_8021x_cnt(dhd_info_t *dhd)
+{
+ return atomic_read(&dhd->pend_8021x_cnt);
+}
+
+#define MAX_WAIT_FOR_8021X_TX 10
+
+int dhd_wait_pend8021x(struct net_device *dev)
+{
+ dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
+ int timeout = 10 * HZ / 1000;
+ int ntimes = MAX_WAIT_FOR_8021X_TX;
+ int pend = dhd_get_pend_8021x_cnt(dhd);
+
+ while (ntimes && pend) {
+ if (pend) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(timeout);
+ set_current_state(TASK_RUNNING);
+ ntimes--;
+ }
+ pend = dhd_get_pend_8021x_cnt(dhd);
+ }
+ return pend;
+}
+
+#ifdef DHD_DEBUG
+int write_to_file(dhd_pub_t *dhd, u8 *buf, int size)
+{
+ int ret = 0;
+ struct file *fp;
+ mm_segment_t old_fs;
+ loff_t pos = 0;
+
+ /* change to KERNEL_DS address limit */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ /* open file to write */
+ fp = filp_open("/tmp/mem_dump", O_WRONLY | O_CREAT, 0640);
+ if (!fp) {
+ printf("%s: open file error\n", __func__);
+ ret = -1;
+ goto exit;
+ }
+
+ /* Write buf to file */
+ fp->f_op->write(fp, buf, size, &pos);
+
+exit:
+ /* free buf before return */
+ kfree(buf);
+ /* close file before return */
+ if (fp)
+ filp_close(fp, current->files);
+ /* restore previous address limit */
+ set_fs(old_fs);
+
+ return ret;
+}
+#endif /* DHD_DEBUG */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_linux_sched.c b/drivers/staging/brcm80211/brcmfmac/dhd_linux_sched.c
new file mode 100644
index 000000000000..bf8df9801030
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_linux_sched.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linuxver.h>
+
+int setScheduler(struct task_struct *p, int policy, struct sched_param *param)
+{
+ int rc = 0;
+ rc = sched_setscheduler(p, policy, param);
+ return rc;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_proto.h b/drivers/staging/brcm80211/brcmfmac/dhd_proto.h
new file mode 100644
index 000000000000..cc42fa4a9140
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_proto.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _dhd_proto_h_
+#define _dhd_proto_h_
+
+#include <dhdioctl.h>
+#include <wlioctl.h>
+
+#ifndef IOCTL_RESP_TIMEOUT
+#define IOCTL_RESP_TIMEOUT 2000 /* In milli second */
+#endif
+
+#ifndef IOCTL_CHIP_ACTIVE_TIMEOUT
+#define IOCTL_CHIP_ACTIVE_TIMEOUT 10 /* In milli second */
+#endif
+
+/*
+ * Exported from the dhd protocol module (dhd_cdc, dhd_rndis)
+ */
+
+/* Linkage, sets prot link and updates hdrlen in pub */
+extern int dhd_prot_attach(dhd_pub_t *dhdp);
+
+/* Unlink, frees allocated protocol memory (including dhd_prot) */
+extern void dhd_prot_detach(dhd_pub_t *dhdp);
+
+/* Initialize protocol: sync w/dongle state.
+ * Sets dongle media info (iswl, drv_version, mac address).
+ */
+extern int dhd_prot_init(dhd_pub_t *dhdp);
+
+/* Stop protocol: sync w/dongle state. */
+extern void dhd_prot_stop(dhd_pub_t *dhdp);
+
+extern bool dhd_proto_fcinfo(dhd_pub_t *dhd, void *pktbuf, u8 *fcbits);
+
+/* Add any protocol-specific data header.
+ * Caller must reserve prot_hdrlen prepend space.
+ */
+extern void dhd_prot_hdrpush(dhd_pub_t *, int ifidx, void *txp);
+
+/* Remove any protocol-specific data header. */
+extern int dhd_prot_hdrpull(dhd_pub_t *, int *ifidx, void *rxp);
+
+/* Use protocol to issue ioctl to dongle */
+extern int dhd_prot_ioctl(dhd_pub_t *dhd, int ifidx, wl_ioctl_t *ioc,
+ void *buf, int len);
+
+/* Check for and handle local prot-specific iovar commands */
+extern int dhd_prot_iovar_op(dhd_pub_t *dhdp, const char *name,
+ void *params, int plen, void *arg, int len,
+ bool set);
+
+/* Add prot dump output to a buffer */
+extern void dhd_prot_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf);
+
+/* Update local copy of dongle statistics */
+extern void dhd_prot_dstats(dhd_pub_t *dhdp);
+
+extern int dhd_ioctl(dhd_pub_t *dhd_pub, dhd_ioctl_t *ioc, void *buf,
+ uint buflen);
+
+extern int dhd_preinit_ioctls(dhd_pub_t *dhd);
+
+/********************************
+ * For version-string expansion *
+ */
+#if defined(BDC)
+#define DHD_PROTOCOL "bdc"
+#elif defined(CDC)
+#define DHD_PROTOCOL "cdc"
+#elif defined(RNDIS)
+#define DHD_PROTOCOL "rndis"
+#else
+#define DHD_PROTOCOL "unknown"
+#endif /* proto */
+
+#endif /* _dhd_proto_h_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/dhd_sdio.c b/drivers/staging/brcm80211/brcmfmac/dhd_sdio.c
new file mode 100644
index 000000000000..b2281d9dfdcf
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dhd_sdio.c
@@ -0,0 +1,6103 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/types.h>
+#include <bcmdefs.h>
+#include <osl.h>
+#include <bcmsdh.h>
+
+#ifdef BCMEMBEDIMAGE
+#include BCMEMBEDIMAGE
+#endif /* BCMEMBEDIMAGE */
+
+#include <bcmdefs.h>
+#include <bcmutils.h>
+#include <bcmendian.h>
+#include <bcmdevs.h>
+
+#include <siutils.h>
+#include <hndpmu.h>
+#include <hndsoc.h>
+#ifdef DHD_DEBUG
+#include <hndrte_armtrap.h>
+#include <hndrte_cons.h>
+#endif /* DHD_DEBUG */
+#include <sbchipc.h>
+#include <sbhnddma.h>
+
+#include <sdio.h>
+#include <sbsdio.h>
+#include <sbsdpcmdev.h>
+#include <bcmsdpcm.h>
+
+#include <proto/ethernet.h>
+#include <proto/802.1d.h>
+#include <proto/802.11.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhd_bus.h>
+#include <dhd_proto.h>
+#include <dhd_dbg.h>
+#include <dhdioctl.h>
+#include <sdiovar.h>
+#include <siutils_priv.h>
+
+#ifndef DHDSDIO_MEM_DUMP_FNAME
+#define DHDSDIO_MEM_DUMP_FNAME "mem_dump"
+#endif
+
+#define QLEN 256 /* bulk rx and tx queue lengths */
+#define FCHI (QLEN - 10)
+#define FCLOW (FCHI / 2)
+#define PRIOMASK 7
+
+#define TXRETRIES 2 /* # of retries for tx frames */
+
+#if defined(CONFIG_MACH_SANDGATE2G)
+#define DHD_RXBOUND 250 /* Default for max rx frames in
+ one scheduling */
+#else
+#define DHD_RXBOUND 50 /* Default for max rx frames in
+ one scheduling */
+#endif /* defined(CONFIG_MACH_SANDGATE2G) */
+
+#define DHD_TXBOUND 20 /* Default for max tx frames in
+ one scheduling */
+
+#define DHD_TXMINMAX 1 /* Max tx frames if rx still pending */
+
+#define MEMBLOCK 2048 /* Block size used for downloading
+ of dongle image */
+#define MAX_DATA_BUF (32 * 1024) /* Must be large enough to hold
+ biggest possible glom */
+
+/* Packet alignment for most efficient SDIO (can change based on platform) */
+#ifndef DHD_SDALIGN
+#define DHD_SDALIGN 32
+#endif
+#if !ISPOWEROF2(DHD_SDALIGN)
+#error DHD_SDALIGN is not a power of 2!
+#endif
+
+#ifndef DHD_FIRSTREAD
+#define DHD_FIRSTREAD 32
+#endif
+#if !ISPOWEROF2(DHD_FIRSTREAD)
+#error DHD_FIRSTREAD is not a power of 2!
+#endif
+
+/* Total length of frame header for dongle protocol */
+#define SDPCM_HDRLEN (SDPCM_FRAMETAG_LEN + SDPCM_SWHEADER_LEN)
+#ifdef SDTEST
+#define SDPCM_RESERVE (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN)
+#else
+#define SDPCM_RESERVE (SDPCM_HDRLEN + DHD_SDALIGN)
+#endif
+
+/* Space for header read, limit for data packets */
+#ifndef MAX_HDR_READ
+#define MAX_HDR_READ 32
+#endif
+#if !ISPOWEROF2(MAX_HDR_READ)
+#error MAX_HDR_READ is not a power of 2!
+#endif
+
+#define MAX_RX_DATASZ 2048
+
+/* Maximum milliseconds to wait for F2 to come up */
+#define DHD_WAIT_F2RDY 3000
+
+/* Bump up limit on waiting for HT to account for first startup;
+ * if the image is doing a CRC calculation before programming the PMU
+ * for HT availability, it could take a couple hundred ms more, so
+ * max out at a 1 second (1000000us).
+ */
+#if (PMU_MAX_TRANSITION_DLY <= 1000000)
+#undef PMU_MAX_TRANSITION_DLY
+#define PMU_MAX_TRANSITION_DLY 1000000
+#endif
+
+/* Value for ChipClockCSR during initial setup */
+#define DHD_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | \
+ SBSDIO_ALP_AVAIL_REQ)
+#define DHD_INIT_CLKCTL2 (SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP)
+
+/* Flags for SDH calls */
+#define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED)
+
+/* Packet free applicable unconditionally for sdio and sdspi. Conditional if
+ * bufpool was present for gspi bus.
+ */
+#define PKTFREE2() if ((bus->bus != SPI_BUS) || bus->usebufpool) \
+ PKTFREE(bus->dhd->osh, pkt, false);
+DHD_SPINWAIT_SLEEP_INIT(sdioh_spinwait_sleep);
+extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf,
+ uint len);
+
+#ifdef DHD_DEBUG
+/* Device console log buffer state */
+typedef struct dhd_console {
+ uint count; /* Poll interval msec counter */
+ uint log_addr; /* Log struct address (fixed) */
+ hndrte_log_t log; /* Log struct (host copy) */
+ uint bufsize; /* Size of log buffer */
+ u8 *buf; /* Log buffer (host copy) */
+ uint last; /* Last buffer read index */
+} dhd_console_t;
+#endif /* DHD_DEBUG */
+
+/* Private data for SDIO bus interaction */
+typedef struct dhd_bus {
+ dhd_pub_t *dhd;
+
+ bcmsdh_info_t *sdh; /* Handle for BCMSDH calls */
+ si_t *sih; /* Handle for SI calls */
+ char *vars; /* Variables (from CIS and/or other) */
+ uint varsz; /* Size of variables buffer */
+ u32 sbaddr; /* Current SB window pointer (-1, invalid) */
+
+ sdpcmd_regs_t *regs; /* Registers for SDIO core */
+ uint sdpcmrev; /* SDIO core revision */
+ uint armrev; /* CPU core revision */
+ uint ramrev; /* SOCRAM core revision */
+ u32 ramsize; /* Size of RAM in SOCRAM (bytes) */
+ u32 orig_ramsize; /* Size of RAM in SOCRAM (bytes) */
+
+ u32 bus; /* gSPI or SDIO bus */
+ u32 hostintmask; /* Copy of Host Interrupt Mask */
+ u32 intstatus; /* Intstatus bits (events) pending */
+ bool dpc_sched; /* Indicates DPC schedule (intrpt rcvd) */
+ bool fcstate; /* State of dongle flow-control */
+
+ u16 cl_devid; /* cached devid for dhdsdio_probe_attach() */
+ char *fw_path; /* module_param: path to firmware image */
+ char *nv_path; /* module_param: path to nvram vars file */
+ const char *nvram_params; /* user specified nvram params. */
+
+ uint blocksize; /* Block size of SDIO transfers */
+ uint roundup; /* Max roundup limit */
+
+ struct pktq txq; /* Queue length used for flow-control */
+ u8 flowcontrol; /* per prio flow control bitmask */
+ u8 tx_seq; /* Transmit sequence number (next) */
+ u8 tx_max; /* Maximum transmit sequence allowed */
+
+ u8 hdrbuf[MAX_HDR_READ + DHD_SDALIGN];
+ u8 *rxhdr; /* Header of current rx frame (in hdrbuf) */
+ u16 nextlen; /* Next Read Len from last header */
+ u8 rx_seq; /* Receive sequence number (expected) */
+ bool rxskip; /* Skip receive (awaiting NAK ACK) */
+
+ void *glomd; /* Packet containing glomming descriptor */
+ void *glom; /* Packet chain for glommed superframe */
+ uint glomerr; /* Glom packet read errors */
+
+ u8 *rxbuf; /* Buffer for receiving control packets */
+ uint rxblen; /* Allocated length of rxbuf */
+ u8 *rxctl; /* Aligned pointer into rxbuf */
+ u8 *databuf; /* Buffer for receiving big glom packet */
+ u8 *dataptr; /* Aligned pointer into databuf */
+ uint rxlen; /* Length of valid data in buffer */
+
+ u8 sdpcm_ver; /* Bus protocol reported by dongle */
+
+ bool intr; /* Use interrupts */
+ bool poll; /* Use polling */
+ bool ipend; /* Device interrupt is pending */
+ bool intdis; /* Interrupts disabled by isr */
+ uint intrcount; /* Count of device interrupt callbacks */
+ uint lastintrs; /* Count as of last watchdog timer */
+ uint spurious; /* Count of spurious interrupts */
+ uint pollrate; /* Ticks between device polls */
+ uint polltick; /* Tick counter */
+ uint pollcnt; /* Count of active polls */
+
+#ifdef DHD_DEBUG
+ dhd_console_t console; /* Console output polling support */
+ uint console_addr; /* Console address from shared struct */
+#endif /* DHD_DEBUG */
+
+ uint regfails; /* Count of R_REG/W_REG failures */
+
+ uint clkstate; /* State of sd and backplane clock(s) */
+ bool activity; /* Activity flag for clock down */
+ s32 idletime; /* Control for activity timeout */
+ s32 idlecount; /* Activity timeout counter */
+ s32 idleclock; /* How to set bus driver when idle */
+ s32 sd_divisor; /* Speed control to bus driver */
+ s32 sd_mode; /* Mode control to bus driver */
+ s32 sd_rxchain; /* If bcmsdh api accepts PKT chains */
+ bool use_rxchain; /* If dhd should use PKT chains */
+ bool sleeping; /* Is SDIO bus sleeping? */
+ bool rxflow_mode; /* Rx flow control mode */
+ bool rxflow; /* Is rx flow control on */
+ uint prev_rxlim_hit; /* Is prev rx limit exceeded
+ (per dpc schedule) */
+ bool alp_only; /* Don't use HT clock (ALP only) */
+/* Field to decide if rx of control frames happen in rxbuf or lb-pool */
+ bool usebufpool;
+
+#ifdef SDTEST
+ /* external loopback */
+ bool ext_loop;
+ u8 loopid;
+
+ /* pktgen configuration */
+ uint pktgen_freq; /* Ticks between bursts */
+ uint pktgen_count; /* Packets to send each burst */
+ uint pktgen_print; /* Bursts between count displays */
+ uint pktgen_total; /* Stop after this many */
+ uint pktgen_minlen; /* Minimum packet data len */
+ uint pktgen_maxlen; /* Maximum packet data len */
+ uint pktgen_mode; /* Configured mode: tx, rx, or echo */
+ uint pktgen_stop; /* Number of tx failures causing stop */
+
+ /* active pktgen fields */
+ uint pktgen_tick; /* Tick counter for bursts */
+ uint pktgen_ptick; /* Burst counter for printing */
+ uint pktgen_sent; /* Number of test packets generated */
+ uint pktgen_rcvd; /* Number of test packets received */
+ uint pktgen_fail; /* Number of failed send attempts */
+ u16 pktgen_len; /* Length of next packet to send */
+#endif /* SDTEST */
+
+ /* Some additional counters */
+ uint tx_sderrs; /* Count of tx attempts with sd errors */
+ uint fcqueued; /* Tx packets that got queued */
+ uint rxrtx; /* Count of rtx requests (NAK to dongle) */
+ uint rx_toolong; /* Receive frames too long to receive */
+ uint rxc_errors; /* SDIO errors when reading control frames */
+ uint rx_hdrfail; /* SDIO errors on header reads */
+ uint rx_badhdr; /* Bad received headers (roosync?) */
+ uint rx_badseq; /* Mismatched rx sequence number */
+ uint fc_rcvd; /* Number of flow-control events received */
+ uint fc_xoff; /* Number which turned on flow-control */
+ uint fc_xon; /* Number which turned off flow-control */
+ uint rxglomfail; /* Failed deglom attempts */
+ uint rxglomframes; /* Number of glom frames (superframes) */
+ uint rxglompkts; /* Number of packets from glom frames */
+ uint f2rxhdrs; /* Number of header reads */
+ uint f2rxdata; /* Number of frame data reads */
+ uint f2txdata; /* Number of f2 frame writes */
+ uint f1regdata; /* Number of f1 register accesses */
+
+ u8 *ctrl_frame_buf;
+ u32 ctrl_frame_len;
+ bool ctrl_frame_stat;
+} dhd_bus_t;
+
+/* clkstate */
+#define CLK_NONE 0
+#define CLK_SDONLY 1
+#define CLK_PENDING 2 /* Not used yet */
+#define CLK_AVAIL 3
+
+#define DHD_NOPMU(dhd) (false)
+
+#ifdef DHD_DEBUG
+static int qcount[NUMPRIO];
+static int tx_packets[NUMPRIO];
+#endif /* DHD_DEBUG */
+
+/* Deferred transmit */
+const uint dhd_deferred_tx = 1;
+
+extern uint dhd_watchdog_ms;
+extern void dhd_os_wd_timer(void *bus, uint wdtick);
+
+/* Tx/Rx bounds */
+uint dhd_txbound;
+uint dhd_rxbound;
+uint dhd_txminmax;
+
+/* override the RAM size if possible */
+#define DONGLE_MIN_MEMSIZE (128 * 1024)
+int dhd_dongle_memsize;
+
+static bool dhd_doflow;
+static bool dhd_alignctl;
+
+static bool sd1idle;
+
+static bool retrydata;
+#define RETRYCHAN(chan) (((chan) == SDPCM_EVENT_CHANNEL) || retrydata)
+
+static const uint watermark = 8;
+static const uint firstread = DHD_FIRSTREAD;
+
+#define HDATLEN (firstread - (SDPCM_HDRLEN))
+
+/* Retry count for register access failures */
+static const uint retry_limit = 2;
+
+/* Force even SD lengths (some host controllers mess up on odd bytes) */
+static bool forcealign;
+
+#define ALIGNMENT 4
+
+#if defined(OOB_INTR_ONLY) && defined(HW_OOB)
+extern void bcmsdh_enable_hw_oob_intr(void *sdh, bool enable);
+#endif
+
+#if defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD)
+#error OOB_INTR_ONLY is NOT working with SDIO_ISR_THREAD
+#endif /* defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD) */
+#define PKTALIGN(osh, p, len, align) \
+ do { \
+ uint datalign; \
+ datalign = (unsigned long)PKTDATA((p)); \
+ datalign = roundup(datalign, (align)) - datalign; \
+ ASSERT(datalign < (align)); \
+ ASSERT(PKTLEN((p)) >= ((len) + datalign)); \
+ if (datalign) \
+ PKTPULL((p), datalign); \
+ PKTSETLEN((p), (len)); \
+ } while (0)
+
+/* Limit on rounding up frames */
+static const uint max_roundup = 512;
+
+/* Try doing readahead */
+static bool dhd_readahead;
+
+/* To check if there's window offered */
+#define DATAOK(bus) \
+ (((u8)(bus->tx_max - bus->tx_seq) != 0) && \
+ (((u8)(bus->tx_max - bus->tx_seq) & 0x80) == 0))
+
+/* Macros to get register read/write status */
+/* NOTE: these assume a local dhdsdio_bus_t *bus! */
+#define R_SDREG(regvar, regaddr, retryvar) \
+do { \
+ retryvar = 0; \
+ do { \
+ regvar = R_REG(bus->dhd->osh, regaddr); \
+ } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit)); \
+ if (retryvar) { \
+ bus->regfails += (retryvar-1); \
+ if (retryvar > retry_limit) { \
+ DHD_ERROR(("%s: FAILED" #regvar "READ, LINE %d\n", \
+ __func__, __LINE__)); \
+ regvar = 0; \
+ } \
+ } \
+} while (0)
+
+#define W_SDREG(regval, regaddr, retryvar) \
+do { \
+ retryvar = 0; \
+ do { \
+ W_REG(bus->dhd->osh, regaddr, regval); \
+ } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit)); \
+ if (retryvar) { \
+ bus->regfails += (retryvar-1); \
+ if (retryvar > retry_limit) \
+ DHD_ERROR(("%s: FAILED REGISTER WRITE, LINE %d\n", \
+ __func__, __LINE__)); \
+ } \
+} while (0)
+
+#define DHD_BUS SDIO_BUS
+
+#define PKT_AVAILABLE() (intstatus & I_HMB_FRAME_IND)
+
+#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE)
+
+#define GSPI_PR55150_BAILOUT
+
+#ifdef SDTEST
+static void dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq);
+static void dhdsdio_sdtest_set(dhd_bus_t *bus, bool start);
+#endif
+
+#ifdef DHD_DEBUG
+static int dhdsdio_checkdied(dhd_bus_t *bus, u8 *data, uint size);
+static int dhdsdio_mem_dump(dhd_bus_t *bus);
+#endif /* DHD_DEBUG */
+static int dhdsdio_download_state(dhd_bus_t *bus, bool enter);
+
+static void dhdsdio_release(dhd_bus_t *bus, osl_t *osh);
+static void dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh);
+static void dhdsdio_disconnect(void *ptr);
+static bool dhdsdio_chipmatch(u16 chipid);
+static bool dhdsdio_probe_attach(dhd_bus_t *bus, osl_t *osh, void *sdh,
+ void *regsva, u16 devid);
+static bool dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh);
+static bool dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh);
+static void dhdsdio_release_dongle(dhd_bus_t *bus, osl_t * osh);
+
+static uint process_nvram_vars(char *varbuf, uint len);
+
+static void dhd_dongle_setmemsize(struct dhd_bus *bus, int mem_size);
+static int dhd_bcmsdh_recv_buf(dhd_bus_t *bus, u32 addr, uint fn,
+ uint flags, u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle);
+static int dhd_bcmsdh_send_buf(dhd_bus_t *bus, u32 addr, uint fn,
+ uint flags, u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle);
+
+static bool dhdsdio_download_firmware(struct dhd_bus *bus, osl_t *osh,
+ void *sdh);
+static int _dhdsdio_download_firmware(struct dhd_bus *bus);
+
+static int dhdsdio_download_code_file(struct dhd_bus *bus, char *image_path);
+static int dhdsdio_download_nvram(struct dhd_bus *bus);
+#ifdef BCMEMBEDIMAGE
+static int dhdsdio_download_code_array(struct dhd_bus *bus);
+#endif
+
+static void dhd_dongle_setmemsize(struct dhd_bus *bus, int mem_size)
+{
+ s32 min_size = DONGLE_MIN_MEMSIZE;
+ /* Restrict the memsize to user specified limit */
+ DHD_ERROR(("user: Restrict the dongle ram size to %d, min %d\n",
+ dhd_dongle_memsize, min_size));
+ if ((dhd_dongle_memsize > min_size) &&
+ (dhd_dongle_memsize < (s32) bus->orig_ramsize))
+ bus->ramsize = dhd_dongle_memsize;
+}
+
+static int dhdsdio_set_siaddr_window(dhd_bus_t *bus, u32 address)
+{
+ int err = 0;
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW,
+ (address >> 8) & SBSDIO_SBADDRLOW_MASK, &err);
+ if (!err)
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID,
+ (address >> 16) & SBSDIO_SBADDRMID_MASK, &err);
+ if (!err)
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH,
+ (address >> 24) & SBSDIO_SBADDRHIGH_MASK,
+ &err);
+ return err;
+}
+
+/* Turn backplane clock on or off */
+static int dhdsdio_htclk(dhd_bus_t *bus, bool on, bool pendok)
+{
+ int err;
+ u8 clkctl, clkreq, devctl;
+ bcmsdh_info_t *sdh;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+#if defined(OOB_INTR_ONLY)
+ pendok = false;
+#endif
+ clkctl = 0;
+ sdh = bus->sdh;
+
+ if (on) {
+ /* Request HT Avail */
+ clkreq =
+ bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ;
+
+ if ((bus->sih->chip == BCM4329_CHIP_ID)
+ && (bus->sih->chiprev == 0))
+ clkreq |= SBSDIO_FORCE_ALP;
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ clkreq, &err);
+ if (err) {
+ DHD_ERROR(("%s: HT Avail request error: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+
+ if (pendok && ((bus->sih->buscoretype == PCMCIA_CORE_ID)
+ && (bus->sih->buscorerev == 9))) {
+ u32 dummy, retries;
+ R_SDREG(dummy, &bus->regs->clockctlstatus, retries);
+ }
+
+ /* Check current status */
+ clkctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (err) {
+ DHD_ERROR(("%s: HT Avail read error: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+
+ /* Go to pending and await interrupt if appropriate */
+ if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) {
+ /* Allow only clock-available interrupt */
+ devctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ &err);
+ if (err) {
+ DHD_ERROR(("%s: Devctl error setting CA: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+
+ devctl |= SBSDIO_DEVCTL_CA_INT_ONLY;
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ DHD_INFO(("CLKCTL: set PENDING\n"));
+ bus->clkstate = CLK_PENDING;
+
+ return BCME_OK;
+ } else if (bus->clkstate == CLK_PENDING) {
+ /* Cancel CA-only interrupt filter */
+ devctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ &err);
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ }
+
+ /* Otherwise, wait here (polling) for HT Avail */
+ if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
+ SPINWAIT_SLEEP(sdioh_spinwait_sleep,
+ ((clkctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ &err)),
+ !SBSDIO_CLKAV(clkctl, bus->alp_only)),
+ PMU_MAX_TRANSITION_DLY);
+ }
+ if (err) {
+ DHD_ERROR(("%s: HT Avail request error: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
+ DHD_ERROR(("%s: HT Avail timeout (%d): clkctl 0x%02x\n",
+ __func__, PMU_MAX_TRANSITION_DLY, clkctl));
+ return BCME_ERROR;
+ }
+
+ /* Mark clock available */
+ bus->clkstate = CLK_AVAIL;
+ DHD_INFO(("CLKCTL: turned ON\n"));
+
+#if defined(DHD_DEBUG)
+ if (bus->alp_only == true) {
+#if !defined(BCMLXSDMMC)
+ if (!SBSDIO_ALPONLY(clkctl)) {
+ DHD_ERROR(("%s: HT Clock, when ALP Only\n",
+ __func__));
+ }
+#endif /* !defined(BCMLXSDMMC) */
+ } else {
+ if (SBSDIO_ALPONLY(clkctl)) {
+ DHD_ERROR(("%s: HT Clock should be on.\n",
+ __func__));
+ }
+ }
+#endif /* defined (DHD_DEBUG) */
+
+ bus->activity = true;
+ } else {
+ clkreq = 0;
+
+ if (bus->clkstate == CLK_PENDING) {
+ /* Cancel CA-only interrupt filter */
+ devctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ &err);
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ }
+
+ bus->clkstate = CLK_SDONLY;
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ clkreq, &err);
+ DHD_INFO(("CLKCTL: turned OFF\n"));
+ if (err) {
+ DHD_ERROR(("%s: Failed access turning clock off: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ }
+ return BCME_OK;
+}
+
+/* Change idle/active SD state */
+static int dhdsdio_sdclk(dhd_bus_t *bus, bool on)
+{
+ int err;
+ s32 iovalue;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (on) {
+ if (bus->idleclock == DHD_IDLE_STOP) {
+ /* Turn on clock and restore mode */
+ iovalue = 1;
+ err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0,
+ &iovalue, sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error enabling sd_clock: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+
+ iovalue = bus->sd_mode;
+ err = bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0,
+ &iovalue, sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error changing sd_mode: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ } else if (bus->idleclock != DHD_IDLE_ACTIVE) {
+ /* Restore clock speed */
+ iovalue = bus->sd_divisor;
+ err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0,
+ &iovalue, sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error restoring sd_divisor: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ }
+ bus->clkstate = CLK_SDONLY;
+ } else {
+ /* Stop or slow the SD clock itself */
+ if ((bus->sd_divisor == -1) || (bus->sd_mode == -1)) {
+ DHD_TRACE(("%s: can't idle clock, divisor %d mode %d\n",
+ __func__, bus->sd_divisor, bus->sd_mode));
+ return BCME_ERROR;
+ }
+ if (bus->idleclock == DHD_IDLE_STOP) {
+ if (sd1idle) {
+ /* Change to SD1 mode and turn off clock */
+ iovalue = 1;
+ err =
+ bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL,
+ 0, &iovalue,
+ sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error changing sd_clock: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ }
+
+ iovalue = 0;
+ err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0,
+ &iovalue, sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error disabling sd_clock: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ } else if (bus->idleclock != DHD_IDLE_ACTIVE) {
+ /* Set divisor to idle value */
+ iovalue = bus->idleclock;
+ err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0,
+ &iovalue, sizeof(iovalue), true);
+ if (err) {
+ DHD_ERROR(("%s: error changing sd_divisor: %d\n",
+ __func__, err));
+ return BCME_ERROR;
+ }
+ }
+ bus->clkstate = CLK_NONE;
+ }
+
+ return BCME_OK;
+}
+
+/* Transition SD and backplane clock readiness */
+static int dhdsdio_clkctl(dhd_bus_t *bus, uint target, bool pendok)
+{
+#ifdef DHD_DEBUG
+ uint oldstate = bus->clkstate;
+#endif /* DHD_DEBUG */
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Early exit if we're already there */
+ if (bus->clkstate == target) {
+ if (target == CLK_AVAIL) {
+ dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
+ bus->activity = true;
+ }
+ return BCME_OK;
+ }
+
+ switch (target) {
+ case CLK_AVAIL:
+ /* Make sure SD clock is available */
+ if (bus->clkstate == CLK_NONE)
+ dhdsdio_sdclk(bus, true);
+ /* Now request HT Avail on the backplane */
+ dhdsdio_htclk(bus, true, pendok);
+ dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
+ bus->activity = true;
+ break;
+
+ case CLK_SDONLY:
+ /* Remove HT request, or bring up SD clock */
+ if (bus->clkstate == CLK_NONE)
+ dhdsdio_sdclk(bus, true);
+ else if (bus->clkstate == CLK_AVAIL)
+ dhdsdio_htclk(bus, false, false);
+ else
+ DHD_ERROR(("dhdsdio_clkctl: request for %d -> %d\n",
+ bus->clkstate, target));
+ dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
+ break;
+
+ case CLK_NONE:
+ /* Make sure to remove HT request */
+ if (bus->clkstate == CLK_AVAIL)
+ dhdsdio_htclk(bus, false, false);
+ /* Now remove the SD clock */
+ dhdsdio_sdclk(bus, false);
+ dhd_os_wd_timer(bus->dhd, 0);
+ break;
+ }
+#ifdef DHD_DEBUG
+ DHD_INFO(("dhdsdio_clkctl: %d -> %d\n", oldstate, bus->clkstate));
+#endif /* DHD_DEBUG */
+
+ return BCME_OK;
+}
+
+int dhdsdio_bussleep(dhd_bus_t *bus, bool sleep)
+{
+ bcmsdh_info_t *sdh = bus->sdh;
+ sdpcmd_regs_t *regs = bus->regs;
+ uint retries = 0;
+
+ DHD_INFO(("dhdsdio_bussleep: request %s (currently %s)\n",
+ (sleep ? "SLEEP" : "WAKE"),
+ (bus->sleeping ? "SLEEP" : "WAKE")));
+
+ /* Done if we're already in the requested state */
+ if (sleep == bus->sleeping)
+ return BCME_OK;
+
+ /* Going to sleep: set the alarm and turn off the lights... */
+ if (sleep) {
+ /* Don't sleep if something is pending */
+ if (bus->dpc_sched || bus->rxskip || pktq_len(&bus->txq))
+ return BCME_BUSY;
+
+ /* Disable SDIO interrupts (no longer interested) */
+ bcmsdh_intr_disable(bus->sdh);
+
+ /* Make sure the controller has the bus up */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Tell device to start using OOB wakeup */
+ W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
+ if (retries > retry_limit)
+ DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
+
+ /* Turn off our contribution to the HT clock request */
+ dhdsdio_clkctl(bus, CLK_SDONLY, false);
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ SBSDIO_FORCE_HW_CLKREQ_OFF, NULL);
+
+ /* Isolate the bus */
+ if (bus->sih->chip != BCM4329_CHIP_ID
+ && bus->sih->chip != BCM4319_CHIP_ID) {
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ SBSDIO_DEVCTL_PADS_ISO, NULL);
+ }
+
+ /* Change state */
+ bus->sleeping = true;
+
+ } else {
+ /* Waking up: bus power up is ok, set local state */
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ 0, NULL);
+
+ /* Force pad isolation off if possible
+ (in case power never toggled) */
+ if ((bus->sih->buscoretype == PCMCIA_CORE_ID)
+ && (bus->sih->buscorerev >= 10))
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, 0,
+ NULL);
+
+ /* Make sure the controller has the bus up */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Send misc interrupt to indicate OOB not needed */
+ W_SDREG(0, &regs->tosbmailboxdata, retries);
+ if (retries <= retry_limit)
+ W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
+
+ if (retries > retry_limit)
+ DHD_ERROR(("CANNOT SIGNAL CHIP TO CLEAR OOB!!\n"));
+
+ /* Make sure we have SD bus access */
+ dhdsdio_clkctl(bus, CLK_SDONLY, false);
+
+ /* Change state */
+ bus->sleeping = false;
+
+ /* Enable interrupts again */
+ if (bus->intr && (bus->dhd->busstate == DHD_BUS_DATA)) {
+ bus->intdis = false;
+ bcmsdh_intr_enable(bus->sdh);
+ }
+ }
+
+ return BCME_OK;
+}
+
+#if defined(OOB_INTR_ONLY)
+void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable)
+{
+#if defined(HW_OOB)
+ bcmsdh_enable_hw_oob_intr(bus->sdh, enable);
+#else
+ sdpcmd_regs_t *regs = bus->regs;
+ uint retries = 0;
+
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+ if (enable == true) {
+
+ /* Tell device to start using OOB wakeup */
+ W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
+ if (retries > retry_limit)
+ DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
+
+ } else {
+ /* Send misc interrupt to indicate OOB not needed */
+ W_SDREG(0, &regs->tosbmailboxdata, retries);
+ if (retries <= retry_limit)
+ W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
+ }
+
+ /* Turn off our contribution to the HT clock request */
+ dhdsdio_clkctl(bus, CLK_SDONLY, false);
+#endif /* !defined(HW_OOB) */
+}
+#endif /* defined(OOB_INTR_ONLY) */
+
+#define BUS_WAKE(bus) \
+ do { \
+ if ((bus)->sleeping) \
+ dhdsdio_bussleep((bus), false); \
+ } while (0);
+
+/* Writes a HW/SW header into the packet and sends it. */
+/* Assumes: (a) header space already there, (b) caller holds lock */
+static int dhdsdio_txpkt(dhd_bus_t *bus, void *pkt, uint chan, bool free_pkt)
+{
+ int ret;
+ osl_t *osh;
+ u8 *frame;
+ u16 len, pad = 0;
+ u32 swheader;
+ uint retries = 0;
+ bcmsdh_info_t *sdh;
+ void *new;
+ int i;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ sdh = bus->sdh;
+ osh = bus->dhd->osh;
+
+ if (bus->dhd->dongle_reset) {
+ ret = BCME_NOTREADY;
+ goto done;
+ }
+
+ frame = (u8 *) PKTDATA(pkt);
+
+ /* Add alignment padding, allocate new packet if needed */
+ pad = ((unsigned long)frame % DHD_SDALIGN);
+ if (pad) {
+ if (PKTHEADROOM(pkt) < pad) {
+ DHD_INFO(("%s: insufficient headroom %d for %d pad\n",
+ __func__, (int)PKTHEADROOM(pkt), pad));
+ bus->dhd->tx_realloc++;
+ new = PKTGET(osh, (PKTLEN(pkt) + DHD_SDALIGN), true);
+ if (!new) {
+ DHD_ERROR(("%s: couldn't allocate new %d-byte "
+ "packet\n",
+ __func__, PKTLEN(pkt) + DHD_SDALIGN));
+ ret = BCME_NOMEM;
+ goto done;
+ }
+
+ PKTALIGN(osh, new, PKTLEN(pkt), DHD_SDALIGN);
+ bcopy(PKTDATA(pkt), PKTDATA(new), PKTLEN(pkt));
+ if (free_pkt)
+ PKTFREE(osh, pkt, true);
+ /* free the pkt if canned one is not used */
+ free_pkt = true;
+ pkt = new;
+ frame = (u8 *) PKTDATA(pkt);
+ ASSERT(((unsigned long)frame % DHD_SDALIGN) == 0);
+ pad = 0;
+ } else {
+ PKTPUSH(pkt, pad);
+ frame = (u8 *) PKTDATA(pkt);
+
+ ASSERT((pad + SDPCM_HDRLEN) <= (int)PKTLEN(pkt));
+ bzero(frame, pad + SDPCM_HDRLEN);
+ }
+ }
+ ASSERT(pad < DHD_SDALIGN);
+
+ /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */
+ len = (u16) PKTLEN(pkt);
+ *(u16 *) frame = htol16(len);
+ *(((u16 *) frame) + 1) = htol16(~len);
+
+ /* Software tag: channel, sequence number, data offset */
+ swheader =
+ ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) | bus->tx_seq |
+ (((pad +
+ SDPCM_HDRLEN) << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK);
+ htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN);
+ htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader));
+
+#ifdef DHD_DEBUG
+ tx_packets[PKTPRIO(pkt)]++;
+ if (DHD_BYTES_ON() &&
+ (((DHD_CTL_ON() && (chan == SDPCM_CONTROL_CHANNEL)) ||
+ (DHD_DATA_ON() && (chan != SDPCM_CONTROL_CHANNEL))))) {
+ prhex("Tx Frame", frame, len);
+ } else if (DHD_HDRS_ON()) {
+ prhex("TxHdr", frame, min_t(u16, len, 16));
+ }
+#endif
+
+ /* Raise len to next SDIO block to eliminate tail command */
+ if (bus->roundup && bus->blocksize && (len > bus->blocksize)) {
+ u16 pad = bus->blocksize - (len % bus->blocksize);
+ if ((pad <= bus->roundup) && (pad < bus->blocksize))
+#ifdef NOTUSED
+ if (pad <= PKTTAILROOM(pkt))
+#endif /* NOTUSED */
+ len += pad;
+ } else if (len % DHD_SDALIGN) {
+ len += DHD_SDALIGN - (len % DHD_SDALIGN);
+ }
+
+ /* Some controllers have trouble with odd bytes -- round to even */
+ if (forcealign && (len & (ALIGNMENT - 1))) {
+#ifdef NOTUSED
+ if (PKTTAILROOM(pkt))
+#endif
+ len = roundup(len, ALIGNMENT);
+#ifdef NOTUSED
+ else
+ DHD_ERROR(("%s: sending unrounded %d-byte packet\n",
+ __func__, len));
+#endif
+ }
+
+ do {
+ ret =
+ dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
+ F2SYNC, frame, len, pkt, NULL, NULL);
+ bus->f2txdata++;
+ ASSERT(ret != BCME_PENDING);
+
+ if (ret < 0) {
+ /* On failure, abort the command
+ and terminate the frame */
+ DHD_INFO(("%s: sdio error %d, abort command and "
+ "terminate frame.\n", __func__, ret));
+ bus->tx_sderrs++;
+
+ bcmsdh_abort(sdh, SDIO_FUNC_2);
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM,
+ NULL);
+ bus->f1regdata++;
+
+ for (i = 0; i < 3; i++) {
+ u8 hi, lo;
+ hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCHI,
+ NULL);
+ lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCLO,
+ NULL);
+ bus->f1regdata += 2;
+ if ((hi == 0) && (lo == 0))
+ break;
+ }
+
+ }
+ if (ret == 0)
+ bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
+
+ } while ((ret < 0) && retrydata && retries++ < TXRETRIES);
+
+done:
+ /* restore pkt buffer pointer before calling tx complete routine */
+ PKTPULL(pkt, SDPCM_HDRLEN + pad);
+ dhd_os_sdunlock(bus->dhd);
+ dhd_txcomplete(bus->dhd, pkt, ret != 0);
+ dhd_os_sdlock(bus->dhd);
+
+ if (free_pkt)
+ PKTFREE(osh, pkt, true);
+
+ return ret;
+}
+
+int dhd_bus_txdata(struct dhd_bus *bus, void *pkt)
+{
+ int ret = BCME_ERROR;
+ osl_t *osh;
+ uint datalen, prec;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ osh = bus->dhd->osh;
+ datalen = PKTLEN(pkt);
+
+#ifdef SDTEST
+ /* Push the test header if doing loopback */
+ if (bus->ext_loop) {
+ u8 *data;
+ PKTPUSH(pkt, SDPCM_TEST_HDRLEN);
+ data = PKTDATA(pkt);
+ *data++ = SDPCM_TEST_ECHOREQ;
+ *data++ = (u8) bus->loopid++;
+ *data++ = (datalen >> 0);
+ *data++ = (datalen >> 8);
+ datalen += SDPCM_TEST_HDRLEN;
+ }
+#endif /* SDTEST */
+
+ /* Add space for the header */
+ PKTPUSH(pkt, SDPCM_HDRLEN);
+ ASSERT(IS_ALIGNED((unsigned long)PKTDATA(pkt), 2));
+
+ prec = PRIO2PREC((PKTPRIO(pkt) & PRIOMASK));
+
+ /* Check for existing queue, current flow-control,
+ pending event, or pending clock */
+ if (dhd_deferred_tx || bus->fcstate || pktq_len(&bus->txq)
+ || bus->dpc_sched || (!DATAOK(bus))
+ || (bus->flowcontrol & NBITVAL(prec))
+ || (bus->clkstate != CLK_AVAIL)) {
+ DHD_TRACE(("%s: deferring pktq len %d\n", __func__,
+ pktq_len(&bus->txq)));
+ bus->fcqueued++;
+
+ /* Priority based enq */
+ dhd_os_sdlock_txq(bus->dhd);
+ if (dhd_prec_enq(bus->dhd, &bus->txq, pkt, prec) == false) {
+ PKTPULL(pkt, SDPCM_HDRLEN);
+ dhd_txcomplete(bus->dhd, pkt, false);
+ PKTFREE(osh, pkt, true);
+ DHD_ERROR(("%s: out of bus->txq !!!\n", __func__));
+ ret = BCME_NORESOURCE;
+ } else {
+ ret = BCME_OK;
+ }
+ dhd_os_sdunlock_txq(bus->dhd);
+
+ if ((pktq_len(&bus->txq) >= FCHI) && dhd_doflow)
+ dhd_txflowcontrol(bus->dhd, 0, ON);
+
+#ifdef DHD_DEBUG
+ if (pktq_plen(&bus->txq, prec) > qcount[prec])
+ qcount[prec] = pktq_plen(&bus->txq, prec);
+#endif
+ /* Schedule DPC if needed to send queued packet(s) */
+ if (dhd_deferred_tx && !bus->dpc_sched) {
+ bus->dpc_sched = true;
+ dhd_sched_dpc(bus->dhd);
+ }
+ } else {
+ /* Lock: we're about to use shared data/code (and SDIO) */
+ dhd_os_sdlock(bus->dhd);
+
+ /* Otherwise, send it now */
+ BUS_WAKE(bus);
+ /* Make sure back plane ht clk is on, no pending allowed */
+ dhdsdio_clkctl(bus, CLK_AVAIL, true);
+
+#ifndef SDTEST
+ DHD_TRACE(("%s: calling txpkt\n", __func__));
+ ret = dhdsdio_txpkt(bus, pkt, SDPCM_DATA_CHANNEL, true);
+#else
+ ret = dhdsdio_txpkt(bus, pkt,
+ (bus->ext_loop ? SDPCM_TEST_CHANNEL :
+ SDPCM_DATA_CHANNEL), true);
+#endif
+ if (ret)
+ bus->dhd->tx_errors++;
+ else
+ bus->dhd->dstats.tx_bytes += datalen;
+
+ if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, true);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+ }
+
+ return ret;
+}
+
+static uint dhdsdio_sendfromq(dhd_bus_t *bus, uint maxframes)
+{
+ void *pkt;
+ u32 intstatus = 0;
+ uint retries = 0;
+ int ret = 0, prec_out;
+ uint cnt = 0;
+ uint datalen;
+ u8 tx_prec_map;
+
+ dhd_pub_t *dhd = bus->dhd;
+ sdpcmd_regs_t *regs = bus->regs;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ tx_prec_map = ~bus->flowcontrol;
+
+ /* Send frames until the limit or some other event */
+ for (cnt = 0; (cnt < maxframes) && DATAOK(bus); cnt++) {
+ dhd_os_sdlock_txq(bus->dhd);
+ pkt = pktq_mdeq(&bus->txq, tx_prec_map, &prec_out);
+ if (pkt == NULL) {
+ dhd_os_sdunlock_txq(bus->dhd);
+ break;
+ }
+ dhd_os_sdunlock_txq(bus->dhd);
+ datalen = PKTLEN(pkt) - SDPCM_HDRLEN;
+
+#ifndef SDTEST
+ ret = dhdsdio_txpkt(bus, pkt, SDPCM_DATA_CHANNEL, true);
+#else
+ ret = dhdsdio_txpkt(bus, pkt,
+ (bus->ext_loop ? SDPCM_TEST_CHANNEL :
+ SDPCM_DATA_CHANNEL), true);
+#endif
+ if (ret)
+ bus->dhd->tx_errors++;
+ else
+ bus->dhd->dstats.tx_bytes += datalen;
+
+ /* In poll mode, need to check for other events */
+ if (!bus->intr && cnt) {
+ /* Check device status, signal pending interrupt */
+ R_SDREG(intstatus, &regs->intstatus, retries);
+ bus->f2txdata++;
+ if (bcmsdh_regfail(bus->sdh))
+ break;
+ if (intstatus & bus->hostintmask)
+ bus->ipend = true;
+ }
+ }
+
+ /* Deflow-control stack if needed */
+ if (dhd_doflow && dhd->up && (dhd->busstate == DHD_BUS_DATA) &&
+ dhd->txoff && (pktq_len(&bus->txq) < FCLOW))
+ dhd_txflowcontrol(dhd, 0, OFF);
+
+ return cnt;
+}
+
+int dhd_bus_txctl(struct dhd_bus *bus, unsigned char *msg, uint msglen)
+{
+ u8 *frame;
+ u16 len;
+ u32 swheader;
+ uint retries = 0;
+ bcmsdh_info_t *sdh = bus->sdh;
+ u8 doff = 0;
+ int ret = -1;
+ int i;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus->dhd->dongle_reset)
+ return -EIO;
+
+ /* Back the pointer to make a room for bus header */
+ frame = msg - SDPCM_HDRLEN;
+ len = (msglen += SDPCM_HDRLEN);
+
+ /* Add alignment padding (optional for ctl frames) */
+ if (dhd_alignctl) {
+ doff = ((unsigned long)frame % DHD_SDALIGN);
+ if (doff) {
+ frame -= doff;
+ len += doff;
+ msglen += doff;
+ bzero(frame, doff + SDPCM_HDRLEN);
+ }
+ ASSERT(doff < DHD_SDALIGN);
+ }
+ doff += SDPCM_HDRLEN;
+
+ /* Round send length to next SDIO block */
+ if (bus->roundup && bus->blocksize && (len > bus->blocksize)) {
+ u16 pad = bus->blocksize - (len % bus->blocksize);
+ if ((pad <= bus->roundup) && (pad < bus->blocksize))
+ len += pad;
+ } else if (len % DHD_SDALIGN) {
+ len += DHD_SDALIGN - (len % DHD_SDALIGN);
+ }
+
+ /* Satisfy length-alignment requirements */
+ if (forcealign && (len & (ALIGNMENT - 1)))
+ len = roundup(len, ALIGNMENT);
+
+ ASSERT(IS_ALIGNED((unsigned long)frame, 2));
+
+ /* Need to lock here to protect txseq and SDIO tx calls */
+ dhd_os_sdlock(bus->dhd);
+
+ BUS_WAKE(bus);
+
+ /* Make sure backplane clock is on */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */
+ *(u16 *) frame = htol16((u16) msglen);
+ *(((u16 *) frame) + 1) = htol16(~msglen);
+
+ /* Software tag: channel, sequence number, data offset */
+ swheader =
+ ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) &
+ SDPCM_CHANNEL_MASK)
+ | bus->tx_seq | ((doff << SDPCM_DOFFSET_SHIFT) &
+ SDPCM_DOFFSET_MASK);
+ htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN);
+ htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader));
+
+ if (!DATAOK(bus)) {
+ DHD_INFO(("%s: No bus credit bus->tx_max %d, bus->tx_seq %d\n",
+ __func__, bus->tx_max, bus->tx_seq));
+ bus->ctrl_frame_stat = true;
+ /* Send from dpc */
+ bus->ctrl_frame_buf = frame;
+ bus->ctrl_frame_len = len;
+
+ dhd_wait_for_event(bus->dhd, &bus->ctrl_frame_stat);
+
+ if (bus->ctrl_frame_stat == false) {
+ DHD_INFO(("%s: ctrl_frame_stat == false\n", __func__));
+ ret = 0;
+ } else {
+ DHD_INFO(("%s: ctrl_frame_stat == true\n", __func__));
+ ret = -1;
+ }
+ }
+
+ if (ret == -1) {
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_CTL_ON())
+ prhex("Tx Frame", frame, len);
+ else if (DHD_HDRS_ON())
+ prhex("TxHdr", frame, min_t(u16, len, 16));
+#endif
+
+ do {
+ bus->ctrl_frame_stat = false;
+ ret =
+ dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh),
+ SDIO_FUNC_2, F2SYNC, frame, len,
+ NULL, NULL, NULL);
+
+ ASSERT(ret != BCME_PENDING);
+
+ if (ret < 0) {
+ /* On failure, abort the command and
+ terminate the frame */
+ DHD_INFO(("%s: sdio error %d, abort command and terminate frame.\n",
+ __func__, ret));
+ bus->tx_sderrs++;
+
+ bcmsdh_abort(sdh, SDIO_FUNC_2);
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_FRAMECTRL,
+ SFC_WF_TERM, NULL);
+ bus->f1regdata++;
+
+ for (i = 0; i < 3; i++) {
+ u8 hi, lo;
+ hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCHI,
+ NULL);
+ lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCLO,
+ NULL);
+ bus->f1regdata += 2;
+ if ((hi == 0) && (lo == 0))
+ break;
+ }
+
+ }
+ if (ret == 0) {
+ bus->tx_seq =
+ (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
+ }
+ } while ((ret < 0) && retries++ < TXRETRIES);
+ }
+
+ if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, true);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+
+ if (ret)
+ bus->dhd->tx_ctlerrs++;
+ else
+ bus->dhd->tx_ctlpkts++;
+
+ return ret ? -EIO : 0;
+}
+
+int dhd_bus_rxctl(struct dhd_bus *bus, unsigned char *msg, uint msglen)
+{
+ int timeleft;
+ uint rxlen = 0;
+ bool pending;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus->dhd->dongle_reset)
+ return -EIO;
+
+ /* Wait until control frame is available */
+ timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->rxlen, &pending);
+
+ dhd_os_sdlock(bus->dhd);
+ rxlen = bus->rxlen;
+ bcopy(bus->rxctl, msg, min(msglen, rxlen));
+ bus->rxlen = 0;
+ dhd_os_sdunlock(bus->dhd);
+
+ if (rxlen) {
+ DHD_CTL(("%s: resumed on rxctl frame, got %d expected %d\n",
+ __func__, rxlen, msglen));
+ } else if (timeleft == 0) {
+ DHD_ERROR(("%s: resumed on timeout\n", __func__));
+#ifdef DHD_DEBUG
+ dhd_os_sdlock(bus->dhd);
+ dhdsdio_checkdied(bus, NULL, 0);
+ dhd_os_sdunlock(bus->dhd);
+#endif /* DHD_DEBUG */
+ } else if (pending == true) {
+ DHD_CTL(("%s: cancelled\n", __func__));
+ return -ERESTARTSYS;
+ } else {
+ DHD_CTL(("%s: resumed for unknown reason?\n", __func__));
+#ifdef DHD_DEBUG
+ dhd_os_sdlock(bus->dhd);
+ dhdsdio_checkdied(bus, NULL, 0);
+ dhd_os_sdunlock(bus->dhd);
+#endif /* DHD_DEBUG */
+ }
+
+ if (rxlen)
+ bus->dhd->rx_ctlpkts++;
+ else
+ bus->dhd->rx_ctlerrs++;
+
+ return rxlen ? (int)rxlen : -ETIMEDOUT;
+}
+
+/* IOVar table */
+enum {
+ IOV_INTR = 1,
+ IOV_POLLRATE,
+ IOV_SDREG,
+ IOV_SBREG,
+ IOV_SDCIS,
+ IOV_MEMBYTES,
+ IOV_MEMSIZE,
+#ifdef DHD_DEBUG
+ IOV_CHECKDIED,
+#endif
+ IOV_DOWNLOAD,
+ IOV_FORCEEVEN,
+ IOV_SDIOD_DRIVE,
+ IOV_READAHEAD,
+ IOV_SDRXCHAIN,
+ IOV_ALIGNCTL,
+ IOV_SDALIGN,
+ IOV_DEVRESET,
+ IOV_CPU,
+#ifdef SDTEST
+ IOV_PKTGEN,
+ IOV_EXTLOOP,
+#endif /* SDTEST */
+ IOV_SPROM,
+ IOV_TXBOUND,
+ IOV_RXBOUND,
+ IOV_TXMINMAX,
+ IOV_IDLETIME,
+ IOV_IDLECLOCK,
+ IOV_SD1IDLE,
+ IOV_SLEEP,
+ IOV_VARS
+};
+
+const bcm_iovar_t dhdsdio_iovars[] = {
+ {"intr", IOV_INTR, 0, IOVT_BOOL, 0},
+ {"sleep", IOV_SLEEP, 0, IOVT_BOOL, 0},
+ {"pollrate", IOV_POLLRATE, 0, IOVT_UINT32, 0},
+ {"idletime", IOV_IDLETIME, 0, IOVT_INT32, 0},
+ {"idleclock", IOV_IDLECLOCK, 0, IOVT_INT32, 0},
+ {"sd1idle", IOV_SD1IDLE, 0, IOVT_BOOL, 0},
+ {"membytes", IOV_MEMBYTES, 0, IOVT_BUFFER, 2 * sizeof(int)},
+ {"memsize", IOV_MEMSIZE, 0, IOVT_UINT32, 0},
+ {"download", IOV_DOWNLOAD, 0, IOVT_BOOL, 0},
+ {"vars", IOV_VARS, 0, IOVT_BUFFER, 0},
+ {"sdiod_drive", IOV_SDIOD_DRIVE, 0, IOVT_UINT32, 0},
+ {"readahead", IOV_READAHEAD, 0, IOVT_BOOL, 0},
+ {"sdrxchain", IOV_SDRXCHAIN, 0, IOVT_BOOL, 0},
+ {"alignctl", IOV_ALIGNCTL, 0, IOVT_BOOL, 0},
+ {"sdalign", IOV_SDALIGN, 0, IOVT_BOOL, 0},
+ {"devreset", IOV_DEVRESET, 0, IOVT_BOOL, 0},
+#ifdef DHD_DEBUG
+ {"sdreg", IOV_SDREG, 0, IOVT_BUFFER, sizeof(sdreg_t)}
+ ,
+ {"sbreg", IOV_SBREG, 0, IOVT_BUFFER, sizeof(sdreg_t)}
+ ,
+ {"sd_cis", IOV_SDCIS, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN}
+ ,
+ {"forcealign", IOV_FORCEEVEN, 0, IOVT_BOOL, 0}
+ ,
+ {"txbound", IOV_TXBOUND, 0, IOVT_UINT32, 0}
+ ,
+ {"rxbound", IOV_RXBOUND, 0, IOVT_UINT32, 0}
+ ,
+ {"txminmax", IOV_TXMINMAX, 0, IOVT_UINT32, 0}
+ ,
+ {"cpu", IOV_CPU, 0, IOVT_BOOL, 0}
+ ,
+#ifdef DHD_DEBUG
+ {"checkdied", IOV_CHECKDIED, 0, IOVT_BUFFER, 0}
+ ,
+#endif /* DHD_DEBUG */
+#endif /* DHD_DEBUG */
+#ifdef SDTEST
+ {"extloop", IOV_EXTLOOP, 0, IOVT_BOOL, 0}
+ ,
+ {"pktgen", IOV_PKTGEN, 0, IOVT_BUFFER, sizeof(dhd_pktgen_t)}
+ ,
+#endif /* SDTEST */
+
+ {NULL, 0, 0, 0, 0}
+};
+
+static void
+dhd_dump_pct(struct bcmstrbuf *strbuf, char *desc, uint num, uint div)
+{
+ uint q1, q2;
+
+ if (!div) {
+ bcm_bprintf(strbuf, "%s N/A", desc);
+ } else {
+ q1 = num / div;
+ q2 = (100 * (num - (q1 * div))) / div;
+ bcm_bprintf(strbuf, "%s %d.%02d", desc, q1, q2);
+ }
+}
+
+void dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf)
+{
+ dhd_bus_t *bus = dhdp->bus;
+
+ bcm_bprintf(strbuf, "Bus SDIO structure:\n");
+ bcm_bprintf(strbuf,
+ "hostintmask 0x%08x intstatus 0x%08x sdpcm_ver %d\n",
+ bus->hostintmask, bus->intstatus, bus->sdpcm_ver);
+ bcm_bprintf(strbuf,
+ "fcstate %d qlen %d tx_seq %d, max %d, rxskip %d rxlen %d rx_seq %d\n",
+ bus->fcstate, pktq_len(&bus->txq), bus->tx_seq, bus->tx_max,
+ bus->rxskip, bus->rxlen, bus->rx_seq);
+ bcm_bprintf(strbuf, "intr %d intrcount %d lastintrs %d spurious %d\n",
+ bus->intr, bus->intrcount, bus->lastintrs, bus->spurious);
+ bcm_bprintf(strbuf, "pollrate %d pollcnt %d regfails %d\n",
+ bus->pollrate, bus->pollcnt, bus->regfails);
+
+ bcm_bprintf(strbuf, "\nAdditional counters:\n");
+ bcm_bprintf(strbuf,
+ "tx_sderrs %d fcqueued %d rxrtx %d rx_toolong %d rxc_errors %d\n",
+ bus->tx_sderrs, bus->fcqueued, bus->rxrtx, bus->rx_toolong,
+ bus->rxc_errors);
+ bcm_bprintf(strbuf, "rx_hdrfail %d badhdr %d badseq %d\n",
+ bus->rx_hdrfail, bus->rx_badhdr, bus->rx_badseq);
+ bcm_bprintf(strbuf, "fc_rcvd %d, fc_xoff %d, fc_xon %d\n", bus->fc_rcvd,
+ bus->fc_xoff, bus->fc_xon);
+ bcm_bprintf(strbuf, "rxglomfail %d, rxglomframes %d, rxglompkts %d\n",
+ bus->rxglomfail, bus->rxglomframes, bus->rxglompkts);
+ bcm_bprintf(strbuf, "f2rx (hdrs/data) %d (%d/%d), f2tx %d f1regs %d\n",
+ (bus->f2rxhdrs + bus->f2rxdata), bus->f2rxhdrs,
+ bus->f2rxdata, bus->f2txdata, bus->f1regdata);
+ {
+ dhd_dump_pct(strbuf, "\nRx: pkts/f2rd", bus->dhd->rx_packets,
+ (bus->f2rxhdrs + bus->f2rxdata));
+ dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->rx_packets,
+ bus->f1regdata);
+ dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->rx_packets,
+ (bus->f2rxhdrs + bus->f2rxdata + bus->f1regdata));
+ dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->rx_packets,
+ bus->intrcount);
+ bcm_bprintf(strbuf, "\n");
+
+ dhd_dump_pct(strbuf, "Rx: glom pct", (100 * bus->rxglompkts),
+ bus->dhd->rx_packets);
+ dhd_dump_pct(strbuf, ", pkts/glom", bus->rxglompkts,
+ bus->rxglomframes);
+ bcm_bprintf(strbuf, "\n");
+
+ dhd_dump_pct(strbuf, "Tx: pkts/f2wr", bus->dhd->tx_packets,
+ bus->f2txdata);
+ dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->tx_packets,
+ bus->f1regdata);
+ dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->tx_packets,
+ (bus->f2txdata + bus->f1regdata));
+ dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->tx_packets,
+ bus->intrcount);
+ bcm_bprintf(strbuf, "\n");
+
+ dhd_dump_pct(strbuf, "Total: pkts/f2rw",
+ (bus->dhd->tx_packets + bus->dhd->rx_packets),
+ (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata));
+ dhd_dump_pct(strbuf, ", pkts/f1sd",
+ (bus->dhd->tx_packets + bus->dhd->rx_packets),
+ bus->f1regdata);
+ dhd_dump_pct(strbuf, ", pkts/sd",
+ (bus->dhd->tx_packets + bus->dhd->rx_packets),
+ (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata +
+ bus->f1regdata));
+ dhd_dump_pct(strbuf, ", pkts/int",
+ (bus->dhd->tx_packets + bus->dhd->rx_packets),
+ bus->intrcount);
+ bcm_bprintf(strbuf, "\n\n");
+ }
+
+#ifdef SDTEST
+ if (bus->pktgen_count) {
+ bcm_bprintf(strbuf, "pktgen config and count:\n");
+ bcm_bprintf(strbuf,
+ "freq %d count %d print %d total %d min %d len %d\n",
+ bus->pktgen_freq, bus->pktgen_count,
+ bus->pktgen_print, bus->pktgen_total,
+ bus->pktgen_minlen, bus->pktgen_maxlen);
+ bcm_bprintf(strbuf, "send attempts %d rcvd %d fail %d\n",
+ bus->pktgen_sent, bus->pktgen_rcvd,
+ bus->pktgen_fail);
+ }
+#endif /* SDTEST */
+#ifdef DHD_DEBUG
+ bcm_bprintf(strbuf, "dpc_sched %d host interrupt%spending\n",
+ bus->dpc_sched,
+ (bcmsdh_intr_pending(bus->sdh) ? " " : " not "));
+ bcm_bprintf(strbuf, "blocksize %d roundup %d\n", bus->blocksize,
+ bus->roundup);
+#endif /* DHD_DEBUG */
+ bcm_bprintf(strbuf,
+ "clkstate %d activity %d idletime %d idlecount %d sleeping %d\n",
+ bus->clkstate, bus->activity, bus->idletime, bus->idlecount,
+ bus->sleeping);
+}
+
+void dhd_bus_clearcounts(dhd_pub_t *dhdp)
+{
+ dhd_bus_t *bus = (dhd_bus_t *) dhdp->bus;
+
+ bus->intrcount = bus->lastintrs = bus->spurious = bus->regfails = 0;
+ bus->rxrtx = bus->rx_toolong = bus->rxc_errors = 0;
+ bus->rx_hdrfail = bus->rx_badhdr = bus->rx_badseq = 0;
+ bus->tx_sderrs = bus->fc_rcvd = bus->fc_xoff = bus->fc_xon = 0;
+ bus->rxglomfail = bus->rxglomframes = bus->rxglompkts = 0;
+ bus->f2rxhdrs = bus->f2rxdata = bus->f2txdata = bus->f1regdata = 0;
+}
+
+#ifdef SDTEST
+static int dhdsdio_pktgen_get(dhd_bus_t *bus, u8 *arg)
+{
+ dhd_pktgen_t pktgen;
+
+ pktgen.version = DHD_PKTGEN_VERSION;
+ pktgen.freq = bus->pktgen_freq;
+ pktgen.count = bus->pktgen_count;
+ pktgen.print = bus->pktgen_print;
+ pktgen.total = bus->pktgen_total;
+ pktgen.minlen = bus->pktgen_minlen;
+ pktgen.maxlen = bus->pktgen_maxlen;
+ pktgen.numsent = bus->pktgen_sent;
+ pktgen.numrcvd = bus->pktgen_rcvd;
+ pktgen.numfail = bus->pktgen_fail;
+ pktgen.mode = bus->pktgen_mode;
+ pktgen.stop = bus->pktgen_stop;
+
+ bcopy(&pktgen, arg, sizeof(pktgen));
+
+ return 0;
+}
+
+static int dhdsdio_pktgen_set(dhd_bus_t *bus, u8 *arg)
+{
+ dhd_pktgen_t pktgen;
+ uint oldcnt, oldmode;
+
+ bcopy(arg, &pktgen, sizeof(pktgen));
+ if (pktgen.version != DHD_PKTGEN_VERSION)
+ return BCME_BADARG;
+
+ oldcnt = bus->pktgen_count;
+ oldmode = bus->pktgen_mode;
+
+ bus->pktgen_freq = pktgen.freq;
+ bus->pktgen_count = pktgen.count;
+ bus->pktgen_print = pktgen.print;
+ bus->pktgen_total = pktgen.total;
+ bus->pktgen_minlen = pktgen.minlen;
+ bus->pktgen_maxlen = pktgen.maxlen;
+ bus->pktgen_mode = pktgen.mode;
+ bus->pktgen_stop = pktgen.stop;
+
+ bus->pktgen_tick = bus->pktgen_ptick = 0;
+ bus->pktgen_len = max(bus->pktgen_len, bus->pktgen_minlen);
+ bus->pktgen_len = min(bus->pktgen_len, bus->pktgen_maxlen);
+
+ /* Clear counts for a new pktgen (mode change, or was stopped) */
+ if (bus->pktgen_count && (!oldcnt || oldmode != bus->pktgen_mode))
+ bus->pktgen_sent = bus->pktgen_rcvd = bus->pktgen_fail = 0;
+
+ return 0;
+}
+#endif /* SDTEST */
+
+static int
+dhdsdio_membytes(dhd_bus_t *bus, bool write, u32 address, u8 *data,
+ uint size)
+{
+ int bcmerror = 0;
+ u32 sdaddr;
+ uint dsize;
+
+ /* Determine initial transfer parameters */
+ sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK;
+ if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK)
+ dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr);
+ else
+ dsize = size;
+
+ /* Set the backplane window to include the start address */
+ bcmerror = dhdsdio_set_siaddr_window(bus, address);
+ if (bcmerror) {
+ DHD_ERROR(("%s: window change failed\n", __func__));
+ goto xfer_done;
+ }
+
+ /* Do the transfer(s) */
+ while (size) {
+ DHD_INFO(("%s: %s %d bytes at offset 0x%08x in window 0x%08x\n",
+ __func__, (write ? "write" : "read"), dsize,
+ sdaddr, (address & SBSDIO_SBWINDOW_MASK)));
+ bcmerror =
+ bcmsdh_rwdata(bus->sdh, write, sdaddr, data, dsize);
+ if (bcmerror) {
+ DHD_ERROR(("%s: membytes transfer failed\n", __func__));
+ break;
+ }
+
+ /* Adjust for next transfer (if any) */
+ size -= dsize;
+ if (size) {
+ data += dsize;
+ address += dsize;
+ bcmerror = dhdsdio_set_siaddr_window(bus, address);
+ if (bcmerror) {
+ DHD_ERROR(("%s: window change failed\n",
+ __func__));
+ break;
+ }
+ sdaddr = 0;
+ dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size);
+ }
+ }
+
+xfer_done:
+ /* Return the window to backplane enumeration space for core access */
+ if (dhdsdio_set_siaddr_window(bus, bcmsdh_cur_sbwad(bus->sdh))) {
+ DHD_ERROR(("%s: FAILED to set window back to 0x%x\n",
+ __func__, bcmsdh_cur_sbwad(bus->sdh)));
+ }
+
+ return bcmerror;
+}
+
+#ifdef DHD_DEBUG
+static int dhdsdio_readshared(dhd_bus_t *bus, sdpcm_shared_t *sh)
+{
+ u32 addr;
+ int rv;
+
+ /* Read last word in memory to determine address of
+ sdpcm_shared structure */
+ rv = dhdsdio_membytes(bus, false, bus->ramsize - 4, (u8 *)&addr, 4);
+ if (rv < 0)
+ return rv;
+
+ addr = ltoh32(addr);
+
+ DHD_INFO(("sdpcm_shared address 0x%08X\n", addr));
+
+ /*
+ * Check if addr is valid.
+ * NVRAM length at the end of memory should have been overwritten.
+ */
+ if (addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff)) {
+ DHD_ERROR(("%s: address (0x%08x) of sdpcm_shared invalid\n",
+ __func__, addr));
+ return BCME_ERROR;
+ }
+
+ /* Read hndrte_shared structure */
+ rv = dhdsdio_membytes(bus, false, addr, (u8 *) sh,
+ sizeof(sdpcm_shared_t));
+ if (rv < 0)
+ return rv;
+
+ /* Endianness */
+ sh->flags = ltoh32(sh->flags);
+ sh->trap_addr = ltoh32(sh->trap_addr);
+ sh->assert_exp_addr = ltoh32(sh->assert_exp_addr);
+ sh->assert_file_addr = ltoh32(sh->assert_file_addr);
+ sh->assert_line = ltoh32(sh->assert_line);
+ sh->console_addr = ltoh32(sh->console_addr);
+ sh->msgtrace_addr = ltoh32(sh->msgtrace_addr);
+
+ if ((sh->flags & SDPCM_SHARED_VERSION_MASK) != SDPCM_SHARED_VERSION) {
+ DHD_ERROR(("%s: sdpcm_shared version %d in dhd "
+ "is different than sdpcm_shared version %d in dongle\n",
+ __func__, SDPCM_SHARED_VERSION,
+ sh->flags & SDPCM_SHARED_VERSION_MASK));
+ return BCME_ERROR;
+ }
+
+ return BCME_OK;
+}
+
+static int dhdsdio_checkdied(dhd_bus_t *bus, u8 *data, uint size)
+{
+ int bcmerror = 0;
+ uint msize = 512;
+ char *mbuffer = NULL;
+ uint maxstrlen = 256;
+ char *str = NULL;
+ trap_t tr;
+ sdpcm_shared_t sdpcm_shared;
+ struct bcmstrbuf strbuf;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (data == NULL) {
+ /*
+ * Called after a rx ctrl timeout. "data" is NULL.
+ * allocate memory to trace the trap or assert.
+ */
+ size = msize;
+ mbuffer = data = kmalloc(msize, GFP_ATOMIC);
+ if (mbuffer == NULL) {
+ DHD_ERROR(("%s: kmalloc(%d) failed\n", __func__,
+ msize));
+ bcmerror = BCME_NOMEM;
+ goto done;
+ }
+ }
+
+ str = kmalloc(maxstrlen, GFP_ATOMIC);
+ if (str == NULL) {
+ DHD_ERROR(("%s: kmalloc(%d) failed\n", __func__, maxstrlen));
+ bcmerror = BCME_NOMEM;
+ goto done;
+ }
+
+ bcmerror = dhdsdio_readshared(bus, &sdpcm_shared);
+ if (bcmerror < 0)
+ goto done;
+
+ bcm_binit(&strbuf, data, size);
+
+ bcm_bprintf(&strbuf,
+ "msgtrace address : 0x%08X\nconsole address : 0x%08X\n",
+ sdpcm_shared.msgtrace_addr, sdpcm_shared.console_addr);
+
+ if ((sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT) == 0) {
+ /* NOTE: Misspelled assert is intentional - DO NOT FIX.
+ * (Avoids conflict with real asserts for programmatic
+ * parsing of output.)
+ */
+ bcm_bprintf(&strbuf, "Assrt not built in dongle\n");
+ }
+
+ if ((sdpcm_shared.flags & (SDPCM_SHARED_ASSERT | SDPCM_SHARED_TRAP)) ==
+ 0) {
+ /* NOTE: Misspelled assert is intentional - DO NOT FIX.
+ * (Avoids conflict with real asserts for programmatic
+ * parsing of output.)
+ */
+ bcm_bprintf(&strbuf, "No trap%s in dongle",
+ (sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT)
+ ? "/assrt" : "");
+ } else {
+ if (sdpcm_shared.flags & SDPCM_SHARED_ASSERT) {
+ /* Download assert */
+ bcm_bprintf(&strbuf, "Dongle assert");
+ if (sdpcm_shared.assert_exp_addr != 0) {
+ str[0] = '\0';
+ bcmerror = dhdsdio_membytes(bus, false,
+ sdpcm_shared.assert_exp_addr,
+ (u8 *) str, maxstrlen);
+ if (bcmerror < 0)
+ goto done;
+
+ str[maxstrlen - 1] = '\0';
+ bcm_bprintf(&strbuf, " expr \"%s\"", str);
+ }
+
+ if (sdpcm_shared.assert_file_addr != 0) {
+ str[0] = '\0';
+ bcmerror = dhdsdio_membytes(bus, false,
+ sdpcm_shared.assert_file_addr,
+ (u8 *) str, maxstrlen);
+ if (bcmerror < 0)
+ goto done;
+
+ str[maxstrlen - 1] = '\0';
+ bcm_bprintf(&strbuf, " file \"%s\"", str);
+ }
+
+ bcm_bprintf(&strbuf, " line %d ",
+ sdpcm_shared.assert_line);
+ }
+
+ if (sdpcm_shared.flags & SDPCM_SHARED_TRAP) {
+ bcmerror = dhdsdio_membytes(bus, false,
+ sdpcm_shared.trap_addr, (u8 *)&tr,
+ sizeof(trap_t));
+ if (bcmerror < 0)
+ goto done;
+
+ bcm_bprintf(&strbuf,
+ "Dongle trap type 0x%x @ epc 0x%x, cpsr 0x%x, spsr 0x%x, sp 0x%x,"
+ "lp 0x%x, rpc 0x%x Trap offset 0x%x, "
+ "r0 0x%x, r1 0x%x, r2 0x%x, r3 0x%x, r4 0x%x, r5 0x%x, r6 0x%x, r7 0x%x\n",
+ tr.type, tr.epc, tr.cpsr, tr.spsr, tr.r13,
+ tr.r14, tr.pc, sdpcm_shared.trap_addr,
+ tr.r0, tr.r1, tr.r2, tr.r3, tr.r4, tr.r5,
+ tr.r6, tr.r7);
+ }
+ }
+
+ if (sdpcm_shared.flags & (SDPCM_SHARED_ASSERT | SDPCM_SHARED_TRAP))
+ DHD_ERROR(("%s: %s\n", __func__, strbuf.origbuf));
+
+#ifdef DHD_DEBUG
+ if (sdpcm_shared.flags & SDPCM_SHARED_TRAP) {
+ /* Mem dump to a file on device */
+ dhdsdio_mem_dump(bus);
+ }
+#endif /* DHD_DEBUG */
+
+done:
+ if (mbuffer)
+ kfree(mbuffer);
+ if (str)
+ kfree(str);
+
+ return bcmerror;
+}
+
+static int dhdsdio_mem_dump(dhd_bus_t *bus)
+{
+ int ret = 0;
+ int size; /* Full mem size */
+ int start = 0; /* Start address */
+ int read_size = 0; /* Read size of each iteration */
+ u8 *buf = NULL, *databuf = NULL;
+
+ /* Get full mem size */
+ size = bus->ramsize;
+ buf = kmalloc(size, GFP_ATOMIC);
+ if (!buf) {
+ printf("%s: Out of memory (%d bytes)\n", __func__, size);
+ return -1;
+ }
+
+ /* Read mem content */
+ printf("Dump dongle memory");
+ databuf = buf;
+ while (size) {
+ read_size = min(MEMBLOCK, size);
+ ret = dhdsdio_membytes(bus, false, start, databuf, read_size);
+ if (ret) {
+ printf("%s: Error membytes %d\n", __func__, ret);
+ if (buf)
+ kfree(buf);
+ return -1;
+ }
+ printf(".");
+
+ /* Decrement size and increment start address */
+ size -= read_size;
+ start += read_size;
+ databuf += read_size;
+ }
+ printf("Done\n");
+
+ /* free buf before return !!! */
+ if (write_to_file(bus->dhd, buf, bus->ramsize)) {
+ printf("%s: Error writing to files\n", __func__);
+ return -1;
+ }
+
+ /* buf free handled in write_to_file, not here */
+ return 0;
+}
+
+#define CONSOLE_LINE_MAX 192
+
+static int dhdsdio_readconsole(dhd_bus_t *bus)
+{
+ dhd_console_t *c = &bus->console;
+ u8 line[CONSOLE_LINE_MAX], ch;
+ u32 n, idx, addr;
+ int rv;
+
+ /* Don't do anything until FWREADY updates console address */
+ if (bus->console_addr == 0)
+ return 0;
+
+ /* Read console log struct */
+ addr = bus->console_addr + offsetof(hndrte_cons_t, log);
+ rv = dhdsdio_membytes(bus, false, addr, (u8 *)&c->log,
+ sizeof(c->log));
+ if (rv < 0)
+ return rv;
+
+ /* Allocate console buffer (one time only) */
+ if (c->buf == NULL) {
+ c->bufsize = ltoh32(c->log.buf_size);
+ c->buf = kmalloc(c->bufsize, GFP_ATOMIC);
+ if (c->buf == NULL)
+ return BCME_NOMEM;
+ }
+
+ idx = ltoh32(c->log.idx);
+
+ /* Protect against corrupt value */
+ if (idx > c->bufsize)
+ return BCME_ERROR;
+
+ /* Skip reading the console buffer if the index pointer
+ has not moved */
+ if (idx == c->last)
+ return BCME_OK;
+
+ /* Read the console buffer */
+ addr = ltoh32(c->log.buf);
+ rv = dhdsdio_membytes(bus, false, addr, c->buf, c->bufsize);
+ if (rv < 0)
+ return rv;
+
+ while (c->last != idx) {
+ for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) {
+ if (c->last == idx) {
+ /* This would output a partial line.
+ * Instead, back up
+ * the buffer pointer and output this
+ * line next time around.
+ */
+ if (c->last >= n)
+ c->last -= n;
+ else
+ c->last = c->bufsize - n;
+ goto break2;
+ }
+ ch = c->buf[c->last];
+ c->last = (c->last + 1) % c->bufsize;
+ if (ch == '\n')
+ break;
+ line[n] = ch;
+ }
+
+ if (n > 0) {
+ if (line[n - 1] == '\r')
+ n--;
+ line[n] = 0;
+ printf("CONSOLE: %s\n", line);
+ }
+ }
+break2:
+
+ return BCME_OK;
+}
+#endif /* DHD_DEBUG */
+
+int dhdsdio_downloadvars(dhd_bus_t *bus, void *arg, int len)
+{
+ int bcmerror = BCME_OK;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Basic sanity checks */
+ if (bus->dhd->up) {
+ bcmerror = BCME_NOTDOWN;
+ goto err;
+ }
+ if (!len) {
+ bcmerror = BCME_BUFTOOSHORT;
+ goto err;
+ }
+
+ /* Free the old ones and replace with passed variables */
+ if (bus->vars)
+ kfree(bus->vars);
+
+ bus->vars = kmalloc(len, GFP_ATOMIC);
+ bus->varsz = bus->vars ? len : 0;
+ if (bus->vars == NULL) {
+ bcmerror = BCME_NOMEM;
+ goto err;
+ }
+
+ /* Copy the passed variables, which should include the
+ terminating double-null */
+ bcopy(arg, bus->vars, bus->varsz);
+err:
+ return bcmerror;
+}
+
+static int
+dhdsdio_doiovar(dhd_bus_t *bus, const bcm_iovar_t *vi, u32 actionid,
+ const char *name, void *params, int plen, void *arg, int len,
+ int val_size)
+{
+ int bcmerror = 0;
+ s32 int_val = 0;
+ bool bool_val = 0;
+
+ DHD_TRACE(("%s: Enter, action %d name %s params %p plen %d arg %p "
+ "len %d val_size %d\n",
+ __func__, actionid, name, params, plen, arg, len, val_size));
+
+ bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid));
+ if (bcmerror != 0)
+ goto exit;
+
+ if (plen >= (int)sizeof(int_val))
+ bcopy(params, &int_val, sizeof(int_val));
+
+ bool_val = (int_val != 0) ? true : false;
+
+ /* Some ioctls use the bus */
+ dhd_os_sdlock(bus->dhd);
+
+ /* Check if dongle is in reset. If so, only allow DEVRESET iovars */
+ if (bus->dhd->dongle_reset && !(actionid == IOV_SVAL(IOV_DEVRESET) ||
+ actionid == IOV_GVAL(IOV_DEVRESET))) {
+ bcmerror = BCME_NOTREADY;
+ goto exit;
+ }
+
+ /* Handle sleep stuff before any clock mucking */
+ if (vi->varid == IOV_SLEEP) {
+ if (IOV_ISSET(actionid)) {
+ bcmerror = dhdsdio_bussleep(bus, bool_val);
+ } else {
+ int_val = (s32) bus->sleeping;
+ bcopy(&int_val, arg, val_size);
+ }
+ goto exit;
+ }
+
+ /* Request clock to allow SDIO accesses */
+ if (!bus->dhd->dongle_reset) {
+ BUS_WAKE(bus);
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+ }
+
+ switch (actionid) {
+ case IOV_GVAL(IOV_INTR):
+ int_val = (s32) bus->intr;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_INTR):
+ bus->intr = bool_val;
+ bus->intdis = false;
+ if (bus->dhd->up) {
+ if (bus->intr) {
+ DHD_INTR(("%s: enable SDIO device interrupts\n",
+ __func__));
+ bcmsdh_intr_enable(bus->sdh);
+ } else {
+ DHD_INTR(("%s: disable SDIO interrupts\n",
+ __func__));
+ bcmsdh_intr_disable(bus->sdh);
+ }
+ }
+ break;
+
+ case IOV_GVAL(IOV_POLLRATE):
+ int_val = (s32) bus->pollrate;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_POLLRATE):
+ bus->pollrate = (uint) int_val;
+ bus->poll = (bus->pollrate != 0);
+ break;
+
+ case IOV_GVAL(IOV_IDLETIME):
+ int_val = bus->idletime;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_IDLETIME):
+ if ((int_val < 0) && (int_val != DHD_IDLE_IMMEDIATE))
+ bcmerror = BCME_BADARG;
+ else
+ bus->idletime = int_val;
+ break;
+
+ case IOV_GVAL(IOV_IDLECLOCK):
+ int_val = (s32) bus->idleclock;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_IDLECLOCK):
+ bus->idleclock = int_val;
+ break;
+
+ case IOV_GVAL(IOV_SD1IDLE):
+ int_val = (s32) sd1idle;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_SD1IDLE):
+ sd1idle = bool_val;
+ break;
+
+ case IOV_SVAL(IOV_MEMBYTES):
+ case IOV_GVAL(IOV_MEMBYTES):
+ {
+ u32 address;
+ uint size, dsize;
+ u8 *data;
+
+ bool set = (actionid == IOV_SVAL(IOV_MEMBYTES));
+
+ ASSERT(plen >= 2 * sizeof(int));
+
+ address = (u32) int_val;
+ bcopy((char *)params + sizeof(int_val), &int_val,
+ sizeof(int_val));
+ size = (uint) int_val;
+
+ /* Do some validation */
+ dsize = set ? plen - (2 * sizeof(int)) : len;
+ if (dsize < size) {
+ DHD_ERROR(("%s: error on %s membytes, addr "
+ "0x%08x size %d dsize %d\n",
+ __func__, (set ? "set" : "get"),
+ address, size, dsize));
+ bcmerror = BCME_BADARG;
+ break;
+ }
+
+ DHD_INFO(("%s: Request to %s %d bytes at address "
+ "0x%08x\n",
+ __func__, (set ? "write" : "read"), size, address));
+
+ /* If we know about SOCRAM, check for a fit */
+ if ((bus->orig_ramsize) &&
+ ((address > bus->orig_ramsize)
+ || (address + size > bus->orig_ramsize))) {
+ DHD_ERROR(("%s: ramsize 0x%08x doesn't have %d "
+ "bytes at 0x%08x\n",
+ __func__, bus->orig_ramsize, size, address));
+ bcmerror = BCME_BADARG;
+ break;
+ }
+
+ /* Generate the actual data pointer */
+ data =
+ set ? (u8 *) params +
+ 2 * sizeof(int) : (u8 *) arg;
+
+ /* Call to do the transfer */
+ bcmerror =
+ dhdsdio_membytes(bus, set, address, data, size);
+
+ break;
+ }
+
+ case IOV_GVAL(IOV_MEMSIZE):
+ int_val = (s32) bus->ramsize;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_GVAL(IOV_SDIOD_DRIVE):
+ int_val = (s32) dhd_sdiod_drive_strength;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_SDIOD_DRIVE):
+ dhd_sdiod_drive_strength = int_val;
+ si_sdiod_drive_strength_init(bus->sih, bus->dhd->osh,
+ dhd_sdiod_drive_strength);
+ break;
+
+ case IOV_SVAL(IOV_DOWNLOAD):
+ bcmerror = dhdsdio_download_state(bus, bool_val);
+ break;
+
+ case IOV_SVAL(IOV_VARS):
+ bcmerror = dhdsdio_downloadvars(bus, arg, len);
+ break;
+
+ case IOV_GVAL(IOV_READAHEAD):
+ int_val = (s32) dhd_readahead;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_READAHEAD):
+ if (bool_val && !dhd_readahead)
+ bus->nextlen = 0;
+ dhd_readahead = bool_val;
+ break;
+
+ case IOV_GVAL(IOV_SDRXCHAIN):
+ int_val = (s32) bus->use_rxchain;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_SDRXCHAIN):
+ if (bool_val && !bus->sd_rxchain)
+ bcmerror = BCME_UNSUPPORTED;
+ else
+ bus->use_rxchain = bool_val;
+ break;
+ case IOV_GVAL(IOV_ALIGNCTL):
+ int_val = (s32) dhd_alignctl;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_ALIGNCTL):
+ dhd_alignctl = bool_val;
+ break;
+
+ case IOV_GVAL(IOV_SDALIGN):
+ int_val = DHD_SDALIGN;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+#ifdef DHD_DEBUG
+ case IOV_GVAL(IOV_VARS):
+ if (bus->varsz < (uint) len)
+ bcopy(bus->vars, arg, bus->varsz);
+ else
+ bcmerror = BCME_BUFTOOSHORT;
+ break;
+#endif /* DHD_DEBUG */
+
+#ifdef DHD_DEBUG
+ case IOV_GVAL(IOV_SDREG):
+ {
+ sdreg_t *sd_ptr;
+ u32 addr, size;
+
+ sd_ptr = (sdreg_t *) params;
+
+ addr = (unsigned long)bus->regs + sd_ptr->offset;
+ size = sd_ptr->func;
+ int_val = (s32) bcmsdh_reg_read(bus->sdh, addr, size);
+ if (bcmsdh_regfail(bus->sdh))
+ bcmerror = BCME_SDIO_ERROR;
+ bcopy(&int_val, arg, sizeof(s32));
+ break;
+ }
+
+ case IOV_SVAL(IOV_SDREG):
+ {
+ sdreg_t *sd_ptr;
+ u32 addr, size;
+
+ sd_ptr = (sdreg_t *) params;
+
+ addr = (unsigned long)bus->regs + sd_ptr->offset;
+ size = sd_ptr->func;
+ bcmsdh_reg_write(bus->sdh, addr, size, sd_ptr->value);
+ if (bcmsdh_regfail(bus->sdh))
+ bcmerror = BCME_SDIO_ERROR;
+ break;
+ }
+
+ /* Same as above, but offset is not backplane
+ (not SDIO core) */
+ case IOV_GVAL(IOV_SBREG):
+ {
+ sdreg_t sdreg;
+ u32 addr, size;
+
+ bcopy(params, &sdreg, sizeof(sdreg));
+
+ addr = SI_ENUM_BASE + sdreg.offset;
+ size = sdreg.func;
+ int_val = (s32) bcmsdh_reg_read(bus->sdh, addr, size);
+ if (bcmsdh_regfail(bus->sdh))
+ bcmerror = BCME_SDIO_ERROR;
+ bcopy(&int_val, arg, sizeof(s32));
+ break;
+ }
+
+ case IOV_SVAL(IOV_SBREG):
+ {
+ sdreg_t sdreg;
+ u32 addr, size;
+
+ bcopy(params, &sdreg, sizeof(sdreg));
+
+ addr = SI_ENUM_BASE + sdreg.offset;
+ size = sdreg.func;
+ bcmsdh_reg_write(bus->sdh, addr, size, sdreg.value);
+ if (bcmsdh_regfail(bus->sdh))
+ bcmerror = BCME_SDIO_ERROR;
+ break;
+ }
+
+ case IOV_GVAL(IOV_SDCIS):
+ {
+ *(char *)arg = 0;
+
+ strcat(arg, "\nFunc 0\n");
+ bcmsdh_cis_read(bus->sdh, 0x10,
+ (u8 *) arg + strlen(arg),
+ SBSDIO_CIS_SIZE_LIMIT);
+ strcat(arg, "\nFunc 1\n");
+ bcmsdh_cis_read(bus->sdh, 0x11,
+ (u8 *) arg + strlen(arg),
+ SBSDIO_CIS_SIZE_LIMIT);
+ strcat(arg, "\nFunc 2\n");
+ bcmsdh_cis_read(bus->sdh, 0x12,
+ (u8 *) arg + strlen(arg),
+ SBSDIO_CIS_SIZE_LIMIT);
+ break;
+ }
+
+ case IOV_GVAL(IOV_FORCEEVEN):
+ int_val = (s32) forcealign;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_FORCEEVEN):
+ forcealign = bool_val;
+ break;
+
+ case IOV_GVAL(IOV_TXBOUND):
+ int_val = (s32) dhd_txbound;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_TXBOUND):
+ dhd_txbound = (uint) int_val;
+ break;
+
+ case IOV_GVAL(IOV_RXBOUND):
+ int_val = (s32) dhd_rxbound;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_RXBOUND):
+ dhd_rxbound = (uint) int_val;
+ break;
+
+ case IOV_GVAL(IOV_TXMINMAX):
+ int_val = (s32) dhd_txminmax;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_TXMINMAX):
+ dhd_txminmax = (uint) int_val;
+ break;
+#endif /* DHD_DEBUG */
+
+#ifdef SDTEST
+ case IOV_GVAL(IOV_EXTLOOP):
+ int_val = (s32) bus->ext_loop;
+ bcopy(&int_val, arg, val_size);
+ break;
+
+ case IOV_SVAL(IOV_EXTLOOP):
+ bus->ext_loop = bool_val;
+ break;
+
+ case IOV_GVAL(IOV_PKTGEN):
+ bcmerror = dhdsdio_pktgen_get(bus, arg);
+ break;
+
+ case IOV_SVAL(IOV_PKTGEN):
+ bcmerror = dhdsdio_pktgen_set(bus, arg);
+ break;
+#endif /* SDTEST */
+
+ case IOV_SVAL(IOV_DEVRESET):
+ DHD_TRACE(("%s: Called set IOV_DEVRESET=%d dongle_reset=%d "
+ "busstate=%d\n",
+ __func__, bool_val, bus->dhd->dongle_reset,
+ bus->dhd->busstate));
+
+ ASSERT(bus->dhd->osh);
+ /* ASSERT(bus->cl_devid); */
+
+ dhd_bus_devreset(bus->dhd, (u8) bool_val);
+
+ break;
+
+ case IOV_GVAL(IOV_DEVRESET):
+ DHD_TRACE(("%s: Called get IOV_DEVRESET\n", __func__));
+
+ /* Get its status */
+ int_val = (bool) bus->dhd->dongle_reset;
+ bcopy(&int_val, arg, val_size);
+
+ break;
+
+ default:
+ bcmerror = BCME_UNSUPPORTED;
+ break;
+ }
+
+exit:
+ if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, true);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+
+ if (actionid == IOV_SVAL(IOV_DEVRESET) && bool_val == false)
+ dhd_preinit_ioctls((dhd_pub_t *) bus->dhd);
+
+ return bcmerror;
+}
+
+static int dhdsdio_write_vars(dhd_bus_t *bus)
+{
+ int bcmerror = 0;
+ u32 varsize;
+ u32 varaddr;
+ u8 *vbuffer;
+ u32 varsizew;
+#ifdef DHD_DEBUG
+ char *nvram_ularray;
+#endif /* DHD_DEBUG */
+
+ /* Even if there are no vars are to be written, we still
+ need to set the ramsize. */
+ varsize = bus->varsz ? roundup(bus->varsz, 4) : 0;
+ varaddr = (bus->ramsize - 4) - varsize;
+
+ if (bus->vars) {
+ vbuffer = kmalloc(varsize, GFP_ATOMIC);
+ if (!vbuffer)
+ return BCME_NOMEM;
+
+ bzero(vbuffer, varsize);
+ bcopy(bus->vars, vbuffer, bus->varsz);
+
+ /* Write the vars list */
+ bcmerror =
+ dhdsdio_membytes(bus, true, varaddr, vbuffer, varsize);
+#ifdef DHD_DEBUG
+ /* Verify NVRAM bytes */
+ DHD_INFO(("Compare NVRAM dl & ul; varsize=%d\n", varsize));
+ nvram_ularray = kmalloc(varsize, GFP_ATOMIC);
+ if (!nvram_ularray)
+ return BCME_NOMEM;
+
+ /* Upload image to verify downloaded contents. */
+ memset(nvram_ularray, 0xaa, varsize);
+
+ /* Read the vars list to temp buffer for comparison */
+ bcmerror =
+ dhdsdio_membytes(bus, false, varaddr, nvram_ularray,
+ varsize);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on reading %d nvram bytes at "
+ "0x%08x\n", __func__, bcmerror, varsize, varaddr));
+ }
+ /* Compare the org NVRAM with the one read from RAM */
+ if (memcmp(vbuffer, nvram_ularray, varsize)) {
+ DHD_ERROR(("%s: Downloaded NVRAM image is corrupted.\n",
+ __func__));
+ } else
+ DHD_ERROR(("%s: Download/Upload/Compare of NVRAM ok.\n",
+ __func__));
+
+ kfree(nvram_ularray);
+#endif /* DHD_DEBUG */
+
+ kfree(vbuffer);
+ }
+
+ /* adjust to the user specified RAM */
+ DHD_INFO(("Physical memory size: %d, usable memory size: %d\n",
+ bus->orig_ramsize, bus->ramsize));
+ DHD_INFO(("Vars are at %d, orig varsize is %d\n", varaddr, varsize));
+ varsize = ((bus->orig_ramsize - 4) - varaddr);
+
+ /*
+ * Determine the length token:
+ * Varsize, converted to words, in lower 16-bits, checksum
+ * in upper 16-bits.
+ */
+ if (bcmerror) {
+ varsizew = 0;
+ } else {
+ varsizew = varsize / 4;
+ varsizew = (~varsizew << 16) | (varsizew & 0x0000FFFF);
+ varsizew = htol32(varsizew);
+ }
+
+ DHD_INFO(("New varsize is %d, length token=0x%08x\n", varsize,
+ varsizew));
+
+ /* Write the length token to the last word */
+ bcmerror = dhdsdio_membytes(bus, true, (bus->orig_ramsize - 4),
+ (u8 *)&varsizew, 4);
+
+ return bcmerror;
+}
+
+static int dhdsdio_download_state(dhd_bus_t *bus, bool enter)
+{
+ uint retries;
+ int bcmerror = 0;
+
+ /* To enter download state, disable ARM and reset SOCRAM.
+ * To exit download state, simply reset ARM (default is RAM boot).
+ */
+ if (enter) {
+
+ bus->alp_only = true;
+
+ if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) &&
+ !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) {
+ DHD_ERROR(("%s: Failed to find ARM core!\n", __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+
+ si_core_disable(bus->sih, 0);
+ if (bcmsdh_regfail(bus->sdh)) {
+ bcmerror = BCME_SDIO_ERROR;
+ goto fail;
+ }
+
+ if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) {
+ DHD_ERROR(("%s: Failed to find SOCRAM core!\n",
+ __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+
+ si_core_reset(bus->sih, 0, 0);
+ if (bcmsdh_regfail(bus->sdh)) {
+ DHD_ERROR(("%s: Failure trying reset SOCRAM core?\n",
+ __func__));
+ bcmerror = BCME_SDIO_ERROR;
+ goto fail;
+ }
+
+ /* Clear the top bit of memory */
+ if (bus->ramsize) {
+ u32 zeros = 0;
+ dhdsdio_membytes(bus, true, bus->ramsize - 4,
+ (u8 *)&zeros, 4);
+ }
+ } else {
+ if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) {
+ DHD_ERROR(("%s: Failed to find SOCRAM core!\n",
+ __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+
+ if (!si_iscoreup(bus->sih)) {
+ DHD_ERROR(("%s: SOCRAM core is down after reset?\n",
+ __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+
+ bcmerror = dhdsdio_write_vars(bus);
+ if (bcmerror) {
+ DHD_ERROR(("%s: no vars written to RAM\n", __func__));
+ bcmerror = 0;
+ }
+
+ if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0) &&
+ !si_setcore(bus->sih, SDIOD_CORE_ID, 0)) {
+ DHD_ERROR(("%s: Can't change back to SDIO core?\n",
+ __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+ W_SDREG(0xFFFFFFFF, &bus->regs->intstatus, retries);
+
+ if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) &&
+ !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) {
+ DHD_ERROR(("%s: Failed to find ARM core!\n", __func__));
+ bcmerror = BCME_ERROR;
+ goto fail;
+ }
+
+ si_core_reset(bus->sih, 0, 0);
+ if (bcmsdh_regfail(bus->sdh)) {
+ DHD_ERROR(("%s: Failure trying to reset ARM core?\n",
+ __func__));
+ bcmerror = BCME_SDIO_ERROR;
+ goto fail;
+ }
+
+ /* Allow HT Clock now that the ARM is running. */
+ bus->alp_only = false;
+
+ bus->dhd->busstate = DHD_BUS_LOAD;
+ }
+
+fail:
+ /* Always return to SDIOD core */
+ if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0))
+ si_setcore(bus->sih, SDIOD_CORE_ID, 0);
+
+ return bcmerror;
+}
+
+int
+dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name,
+ void *params, int plen, void *arg, int len, bool set)
+{
+ dhd_bus_t *bus = dhdp->bus;
+ const bcm_iovar_t *vi = NULL;
+ int bcmerror = 0;
+ int val_size;
+ u32 actionid;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ASSERT(name);
+ ASSERT(len >= 0);
+
+ /* Get MUST have return space */
+ ASSERT(set || (arg && len));
+
+ /* Set does NOT take qualifiers */
+ ASSERT(!set || (!params && !plen));
+
+ /* Look up var locally; if not found pass to host driver */
+ vi = bcm_iovar_lookup(dhdsdio_iovars, name);
+ if (vi == NULL) {
+ dhd_os_sdlock(bus->dhd);
+
+ BUS_WAKE(bus);
+
+ /* Turn on clock in case SD command needs backplane */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ bcmerror =
+ bcmsdh_iovar_op(bus->sdh, name, params, plen, arg, len,
+ set);
+
+ /* Check for bus configuration changes of interest */
+
+ /* If it was divisor change, read the new one */
+ if (set && strcmp(name, "sd_divisor") == 0) {
+ if (bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0,
+ &bus->sd_divisor, sizeof(s32),
+ false) != BCME_OK) {
+ bus->sd_divisor = -1;
+ DHD_ERROR(("%s: fail on %s get\n", __func__,
+ name));
+ } else {
+ DHD_INFO(("%s: noted %s update, value now %d\n",
+ __func__, name, bus->sd_divisor));
+ }
+ }
+ /* If it was a mode change, read the new one */
+ if (set && strcmp(name, "sd_mode") == 0) {
+ if (bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0,
+ &bus->sd_mode, sizeof(s32),
+ false) != BCME_OK) {
+ bus->sd_mode = -1;
+ DHD_ERROR(("%s: fail on %s get\n", __func__,
+ name));
+ } else {
+ DHD_INFO(("%s: noted %s update, value now %d\n",
+ __func__, name, bus->sd_mode));
+ }
+ }
+ /* Similar check for blocksize change */
+ if (set && strcmp(name, "sd_blocksize") == 0) {
+ s32 fnum = 2;
+ if (bcmsdh_iovar_op
+ (bus->sdh, "sd_blocksize", &fnum, sizeof(s32),
+ &bus->blocksize, sizeof(s32),
+ false) != BCME_OK) {
+ bus->blocksize = 0;
+ DHD_ERROR(("%s: fail on %s get\n", __func__,
+ "sd_blocksize"));
+ } else {
+ DHD_INFO(("%s: noted %s update, value now %d\n",
+ __func__, "sd_blocksize",
+ bus->blocksize));
+ }
+ }
+ bus->roundup = min(max_roundup, bus->blocksize);
+
+ if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, true);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+ goto exit;
+ }
+
+ DHD_CTL(("%s: %s %s, len %d plen %d\n", __func__,
+ name, (set ? "set" : "get"), len, plen));
+
+ /* set up 'params' pointer in case this is a set command so that
+ * the convenience int and bool code can be common to set and get
+ */
+ if (params == NULL) {
+ params = arg;
+ plen = len;
+ }
+
+ if (vi->type == IOVT_VOID)
+ val_size = 0;
+ else if (vi->type == IOVT_BUFFER)
+ val_size = len;
+ else
+ /* all other types are integer sized */
+ val_size = sizeof(int);
+
+ actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid);
+ bcmerror =
+ dhdsdio_doiovar(bus, vi, actionid, name, params, plen, arg, len,
+ val_size);
+
+exit:
+ return bcmerror;
+}
+
+void dhd_bus_stop(struct dhd_bus *bus, bool enforce_mutex)
+{
+ osl_t *osh = bus->dhd->osh;
+ u32 local_hostintmask;
+ u8 saveclk;
+ uint retries;
+ int err;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (enforce_mutex)
+ dhd_os_sdlock(bus->dhd);
+
+ BUS_WAKE(bus);
+
+ /* Enable clock for device interrupts */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Disable and clear interrupts at the chip level also */
+ W_SDREG(0, &bus->regs->hostintmask, retries);
+ local_hostintmask = bus->hostintmask;
+ bus->hostintmask = 0;
+
+ /* Change our idea of bus state */
+ bus->dhd->busstate = DHD_BUS_DOWN;
+
+ /* Force clocks on backplane to be sure F2 interrupt propagates */
+ saveclk =
+ bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (!err) {
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ (saveclk | SBSDIO_FORCE_HT), &err);
+ }
+ if (err) {
+ DHD_ERROR(("%s: Failed to force clock for F2: err %d\n",
+ __func__, err));
+ }
+
+ /* Turn off the bus (F2), free any pending packets */
+ DHD_INTR(("%s: disable SDIO interrupts\n", __func__));
+ bcmsdh_intr_disable(bus->sdh);
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN,
+ SDIO_FUNC_ENABLE_1, NULL);
+
+ /* Clear any pending interrupts now that F2 is disabled */
+ W_SDREG(local_hostintmask, &bus->regs->intstatus, retries);
+
+ /* Turn off the backplane clock (only) */
+ dhdsdio_clkctl(bus, CLK_SDONLY, false);
+
+ /* Clear the data packet queues */
+ pktq_flush(osh, &bus->txq, true);
+
+ /* Clear any held glomming stuff */
+ if (bus->glomd)
+ PKTFREE(osh, bus->glomd, false);
+
+ if (bus->glom)
+ PKTFREE(osh, bus->glom, false);
+
+ bus->glom = bus->glomd = NULL;
+
+ /* Clear rx control and wake any waiters */
+ bus->rxlen = 0;
+ dhd_os_ioctl_resp_wake(bus->dhd);
+
+ /* Reset some F2 state stuff */
+ bus->rxskip = false;
+ bus->tx_seq = bus->rx_seq = 0;
+
+ if (enforce_mutex)
+ dhd_os_sdunlock(bus->dhd);
+}
+
+int dhd_bus_init(dhd_pub_t *dhdp, bool enforce_mutex)
+{
+ dhd_bus_t *bus = dhdp->bus;
+ dhd_timeout_t tmo;
+ uint retries = 0;
+ u8 ready, enable;
+ int err, ret = 0;
+ u8 saveclk;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ASSERT(bus->dhd);
+ if (!bus->dhd)
+ return 0;
+
+ if (enforce_mutex)
+ dhd_os_sdlock(bus->dhd);
+
+ /* Make sure backplane clock is on, needed to generate F2 interrupt */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+ if (bus->clkstate != CLK_AVAIL)
+ goto exit;
+
+ /* Force clocks on backplane to be sure F2 interrupt propagates */
+ saveclk =
+ bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (!err) {
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ (saveclk | SBSDIO_FORCE_HT), &err);
+ }
+ if (err) {
+ DHD_ERROR(("%s: Failed to force clock for F2: err %d\n",
+ __func__, err));
+ goto exit;
+ }
+
+ /* Enable function 2 (frame transfers) */
+ W_SDREG((SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT),
+ &bus->regs->tosbmailboxdata, retries);
+ enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2);
+
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable, NULL);
+
+ /* Give the dongle some time to do its thing and set IOR2 */
+ dhd_timeout_start(&tmo, DHD_WAIT_F2RDY * 1000);
+
+ ready = 0;
+ while (ready != enable && !dhd_timeout_expired(&tmo))
+ ready =
+ bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IORDY,
+ NULL);
+
+ DHD_INFO(("%s: enable 0x%02x, ready 0x%02x (waited %uus)\n",
+ __func__, enable, ready, tmo.elapsed));
+
+ /* If F2 successfully enabled, set core and enable interrupts */
+ if (ready == enable) {
+ /* Make sure we're talking to the core. */
+ bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0);
+ if (!(bus->regs))
+ bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0);
+
+ /* Set up the interrupt mask and enable interrupts */
+ bus->hostintmask = HOSTINTMASK;
+ W_SDREG(bus->hostintmask, &bus->regs->hostintmask, retries);
+
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK,
+ (u8) watermark, &err);
+
+ /* Set bus state according to enable result */
+ dhdp->busstate = DHD_BUS_DATA;
+
+ /* bcmsdh_intr_unmask(bus->sdh); */
+
+ bus->intdis = false;
+ if (bus->intr) {
+ DHD_INTR(("%s: enable SDIO device interrupts\n",
+ __func__));
+ bcmsdh_intr_enable(bus->sdh);
+ } else {
+ DHD_INTR(("%s: disable SDIO interrupts\n", __func__));
+ bcmsdh_intr_disable(bus->sdh);
+ }
+
+ }
+
+ else {
+ /* Disable F2 again */
+ enable = SDIO_FUNC_ENABLE_1;
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable,
+ NULL);
+ }
+
+ /* Restore previous clock setting */
+ bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ saveclk, &err);
+
+ /* If we didn't come up, turn off backplane clock */
+ if (dhdp->busstate != DHD_BUS_DATA)
+ dhdsdio_clkctl(bus, CLK_NONE, false);
+
+exit:
+ if (enforce_mutex)
+ dhd_os_sdunlock(bus->dhd);
+
+ return ret;
+}
+
+static void dhdsdio_rxfail(dhd_bus_t *bus, bool abort, bool rtx)
+{
+ bcmsdh_info_t *sdh = bus->sdh;
+ sdpcmd_regs_t *regs = bus->regs;
+ uint retries = 0;
+ u16 lastrbc;
+ u8 hi, lo;
+ int err;
+
+ DHD_ERROR(("%s: %sterminate frame%s\n", __func__,
+ (abort ? "abort command, " : ""),
+ (rtx ? ", send NAK" : "")));
+
+ if (abort)
+ bcmsdh_abort(sdh, SDIO_FUNC_2);
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, SFC_RF_TERM,
+ &err);
+ bus->f1regdata++;
+
+ /* Wait until the packet has been flushed (device/FIFO stable) */
+ for (lastrbc = retries = 0xffff; retries > 0; retries--) {
+ hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCHI,
+ NULL);
+ lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCLO,
+ NULL);
+ bus->f1regdata += 2;
+
+ if ((hi == 0) && (lo == 0))
+ break;
+
+ if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) {
+ DHD_ERROR(("%s: count growing: last 0x%04x now "
+ "0x%04x\n",
+ __func__, lastrbc, ((hi << 8) + lo)));
+ }
+ lastrbc = (hi << 8) + lo;
+ }
+
+ if (!retries) {
+ DHD_ERROR(("%s: count never zeroed: last 0x%04x\n",
+ __func__, lastrbc));
+ } else {
+ DHD_INFO(("%s: flush took %d iterations\n", __func__,
+ (0xffff - retries)));
+ }
+
+ if (rtx) {
+ bus->rxrtx++;
+ W_SDREG(SMB_NAK, &regs->tosbmailbox, retries);
+ bus->f1regdata++;
+ if (retries <= retry_limit)
+ bus->rxskip = true;
+ }
+
+ /* Clear partial in any case */
+ bus->nextlen = 0;
+
+ /* If we can't reach the device, signal failure */
+ if (err || bcmsdh_regfail(sdh))
+ bus->dhd->busstate = DHD_BUS_DOWN;
+}
+
+static void
+dhdsdio_read_control(dhd_bus_t *bus, u8 *hdr, uint len, uint doff)
+{
+ bcmsdh_info_t *sdh = bus->sdh;
+ uint rdlen, pad;
+
+ int sdret;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Control data already received in aligned rxctl */
+ if ((bus->bus == SPI_BUS) && (!bus->usebufpool))
+ goto gotpkt;
+
+ ASSERT(bus->rxbuf);
+ /* Set rxctl for frame (w/optional alignment) */
+ bus->rxctl = bus->rxbuf;
+ if (dhd_alignctl) {
+ bus->rxctl += firstread;
+ pad = ((unsigned long)bus->rxctl % DHD_SDALIGN);
+ if (pad)
+ bus->rxctl += (DHD_SDALIGN - pad);
+ bus->rxctl -= firstread;
+ }
+ ASSERT(bus->rxctl >= bus->rxbuf);
+
+ /* Copy the already-read portion over */
+ bcopy(hdr, bus->rxctl, firstread);
+ if (len <= firstread)
+ goto gotpkt;
+
+ /* Copy the full data pkt in gSPI case and process ioctl. */
+ if (bus->bus == SPI_BUS) {
+ bcopy(hdr, bus->rxctl, len);
+ goto gotpkt;
+ }
+
+ /* Raise rdlen to next SDIO block to avoid tail command */
+ rdlen = len - firstread;
+ if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) {
+ pad = bus->blocksize - (rdlen % bus->blocksize);
+ if ((pad <= bus->roundup) && (pad < bus->blocksize) &&
+ ((len + pad) < bus->dhd->maxctl))
+ rdlen += pad;
+ } else if (rdlen % DHD_SDALIGN) {
+ rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN);
+ }
+
+ /* Satisfy length-alignment requirements */
+ if (forcealign && (rdlen & (ALIGNMENT - 1)))
+ rdlen = roundup(rdlen, ALIGNMENT);
+
+ /* Drop if the read is too big or it exceeds our maximum */
+ if ((rdlen + firstread) > bus->dhd->maxctl) {
+ DHD_ERROR(("%s: %d-byte control read exceeds %d-byte buffer\n",
+ __func__, rdlen, bus->dhd->maxctl));
+ bus->dhd->rx_errors++;
+ dhdsdio_rxfail(bus, false, false);
+ goto done;
+ }
+
+ if ((len - doff) > bus->dhd->maxctl) {
+ DHD_ERROR(("%s: %d-byte ctl frame (%d-byte ctl data) exceeds "
+ "%d-byte limit\n",
+ __func__, len, (len - doff), bus->dhd->maxctl));
+ bus->dhd->rx_errors++;
+ bus->rx_toolong++;
+ dhdsdio_rxfail(bus, false, false);
+ goto done;
+ }
+
+ /* Read remainder of frame body into the rxctl buffer */
+ sdret =
+ dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC,
+ (bus->rxctl + firstread), rdlen, NULL, NULL,
+ NULL);
+ bus->f2rxdata++;
+ ASSERT(sdret != BCME_PENDING);
+
+ /* Control frame failures need retransmission */
+ if (sdret < 0) {
+ DHD_ERROR(("%s: read %d control bytes failed: %d\n",
+ __func__, rdlen, sdret));
+ bus->rxc_errors++; /* dhd.rx_ctlerrs is higher level */
+ dhdsdio_rxfail(bus, true, true);
+ goto done;
+ }
+
+gotpkt:
+
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_CTL_ON())
+ prhex("RxCtrl", bus->rxctl, len);
+#endif
+
+ /* Point to valid data and indicate its length */
+ bus->rxctl += doff;
+ bus->rxlen = len - doff;
+
+done:
+ /* Awake any waiters */
+ dhd_os_ioctl_resp_wake(bus->dhd);
+}
+
+static u8 dhdsdio_rxglom(dhd_bus_t *bus, u8 rxseq)
+{
+ u16 dlen, totlen;
+ u8 *dptr, num = 0;
+
+ u16 sublen, check;
+ void *pfirst, *plast, *pnext, *save_pfirst;
+ osl_t *osh = bus->dhd->osh;
+
+ int errcode;
+ u8 chan, seq, doff, sfdoff;
+ u8 txmax;
+
+ int ifidx = 0;
+ bool usechain = bus->use_rxchain;
+
+ /* If packets, issue read(s) and send up packet chain */
+ /* Return sequence numbers consumed? */
+
+ DHD_TRACE(("dhdsdio_rxglom: start: glomd %p glom %p\n", bus->glomd,
+ bus->glom));
+
+ /* If there's a descriptor, generate the packet chain */
+ if (bus->glomd) {
+ dhd_os_sdlock_rxq(bus->dhd);
+
+ pfirst = plast = pnext = NULL;
+ dlen = (u16) PKTLEN(bus->glomd);
+ dptr = PKTDATA(bus->glomd);
+ if (!dlen || (dlen & 1)) {
+ DHD_ERROR(("%s: bad glomd len(%d), ignore descriptor\n",
+ __func__, dlen));
+ dlen = 0;
+ }
+
+ for (totlen = num = 0; dlen; num++) {
+ /* Get (and move past) next length */
+ sublen = ltoh16_ua(dptr);
+ dlen -= sizeof(u16);
+ dptr += sizeof(u16);
+ if ((sublen < SDPCM_HDRLEN) ||
+ ((num == 0) && (sublen < (2 * SDPCM_HDRLEN)))) {
+ DHD_ERROR(("%s: descriptor len %d bad: %d\n",
+ __func__, num, sublen));
+ pnext = NULL;
+ break;
+ }
+ if (sublen % DHD_SDALIGN) {
+ DHD_ERROR(("%s: sublen %d not multiple of %d\n",
+ __func__, sublen, DHD_SDALIGN));
+ usechain = false;
+ }
+ totlen += sublen;
+
+ /* For last frame, adjust read len so total
+ is a block multiple */
+ if (!dlen) {
+ sublen +=
+ (roundup(totlen, bus->blocksize) - totlen);
+ totlen = roundup(totlen, bus->blocksize);
+ }
+
+ /* Allocate/chain packet for next subframe */
+ pnext = PKTGET(osh, sublen + DHD_SDALIGN, false);
+ if (pnext == NULL) {
+ DHD_ERROR(("%s: PKTGET failed, num %d len %d\n",
+ __func__, num, sublen));
+ break;
+ }
+ ASSERT(!PKTLINK(pnext));
+ if (!pfirst) {
+ ASSERT(!plast);
+ pfirst = plast = pnext;
+ } else {
+ ASSERT(plast);
+ PKTSETNEXT(plast, pnext);
+ plast = pnext;
+ }
+
+ /* Adhere to start alignment requirements */
+ PKTALIGN(osh, pnext, sublen, DHD_SDALIGN);
+ }
+
+ /* If all allocations succeeded, save packet chain
+ in bus structure */
+ if (pnext) {
+ DHD_GLOM(("%s: allocated %d-byte packet chain for %d "
+ "subframes\n", __func__, totlen, num));
+ if (DHD_GLOM_ON() && bus->nextlen) {
+ if (totlen != bus->nextlen) {
+ DHD_GLOM(("%s: glomdesc mismatch: nextlen %d glomdesc %d " "rxseq %d\n",
+ __func__, bus->nextlen,
+ totlen, rxseq));
+ }
+ }
+ bus->glom = pfirst;
+ pfirst = pnext = NULL;
+ } else {
+ if (pfirst)
+ PKTFREE(osh, pfirst, false);
+ bus->glom = NULL;
+ num = 0;
+ }
+
+ /* Done with descriptor packet */
+ PKTFREE(osh, bus->glomd, false);
+ bus->glomd = NULL;
+ bus->nextlen = 0;
+
+ dhd_os_sdunlock_rxq(bus->dhd);
+ }
+
+ /* Ok -- either we just generated a packet chain,
+ or had one from before */
+ if (bus->glom) {
+ if (DHD_GLOM_ON()) {
+ DHD_GLOM(("%s: try superframe read, packet chain:\n",
+ __func__));
+ for (pnext = bus->glom; pnext; pnext = PKTNEXT(pnext)) {
+ DHD_GLOM((" %p: %p len 0x%04x (%d)\n",
+ pnext, (u8 *) PKTDATA(pnext),
+ PKTLEN(pnext), PKTLEN(pnext)));
+ }
+ }
+
+ pfirst = bus->glom;
+ dlen = (u16) pkttotlen(osh, pfirst);
+
+ /* Do an SDIO read for the superframe. Configurable iovar to
+ * read directly into the chained packet, or allocate a large
+ * packet and and copy into the chain.
+ */
+ if (usechain) {
+ errcode = dhd_bcmsdh_recv_buf(bus,
+ bcmsdh_cur_sbwad
+ (bus->sdh), SDIO_FUNC_2,
+ F2SYNC,
+ (u8 *) PKTDATA(pfirst),
+ dlen, pfirst, NULL, NULL);
+ } else if (bus->dataptr) {
+ errcode = dhd_bcmsdh_recv_buf(bus,
+ bcmsdh_cur_sbwad
+ (bus->sdh), SDIO_FUNC_2,
+ F2SYNC, bus->dataptr,
+ dlen, NULL, NULL, NULL);
+ sublen =
+ (u16) pktfrombuf(osh, pfirst, 0, dlen,
+ bus->dataptr);
+ if (sublen != dlen) {
+ DHD_ERROR(("%s: FAILED TO COPY, dlen %d sublen %d\n",
+ __func__, dlen, sublen));
+ errcode = -1;
+ }
+ pnext = NULL;
+ } else {
+ DHD_ERROR(("COULDN'T ALLOC %d-BYTE GLOM, FORCE FAILURE\n",
+ dlen));
+ errcode = -1;
+ }
+ bus->f2rxdata++;
+ ASSERT(errcode != BCME_PENDING);
+
+ /* On failure, kill the superframe, allow a couple retries */
+ if (errcode < 0) {
+ DHD_ERROR(("%s: glom read of %d bytes failed: %d\n",
+ __func__, dlen, errcode));
+ bus->dhd->rx_errors++;
+
+ if (bus->glomerr++ < 3) {
+ dhdsdio_rxfail(bus, true, true);
+ } else {
+ bus->glomerr = 0;
+ dhdsdio_rxfail(bus, true, false);
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(osh, bus->glom, false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ bus->rxglomfail++;
+ bus->glom = NULL;
+ }
+ return 0;
+ }
+#ifdef DHD_DEBUG
+ if (DHD_GLOM_ON()) {
+ prhex("SUPERFRAME", PKTDATA(pfirst),
+ min_t(int, PKTLEN(pfirst), 48));
+ }
+#endif
+
+ /* Validate the superframe header */
+ dptr = (u8 *) PKTDATA(pfirst);
+ sublen = ltoh16_ua(dptr);
+ check = ltoh16_ua(dptr + sizeof(u16));
+
+ chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
+ seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]);
+ bus->nextlen = dptr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET];
+ if ((bus->nextlen << 4) > MAX_RX_DATASZ) {
+ DHD_INFO(("%s: nextlen too large (%d) seq %d\n",
+ __func__, bus->nextlen, seq));
+ bus->nextlen = 0;
+ }
+ doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
+ txmax = SDPCM_WINDOW_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
+
+ errcode = 0;
+ if ((u16)~(sublen ^ check)) {
+ DHD_ERROR(("%s (superframe): HW hdr error: len/check "
+ "0x%04x/0x%04x\n", __func__, sublen, check));
+ errcode = -1;
+ } else if (roundup(sublen, bus->blocksize) != dlen) {
+ DHD_ERROR(("%s (superframe): len 0x%04x, rounded "
+ "0x%04x, expect 0x%04x\n",
+ __func__, sublen,
+ roundup(sublen, bus->blocksize), dlen));
+ errcode = -1;
+ } else if (SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]) !=
+ SDPCM_GLOM_CHANNEL) {
+ DHD_ERROR(("%s (superframe): bad channel %d\n",
+ __func__,
+ SDPCM_PACKET_CHANNEL(&dptr
+ [SDPCM_FRAMETAG_LEN])));
+ errcode = -1;
+ } else if (SDPCM_GLOMDESC(&dptr[SDPCM_FRAMETAG_LEN])) {
+ DHD_ERROR(("%s (superframe): got second descriptor?\n",
+ __func__));
+ errcode = -1;
+ } else if ((doff < SDPCM_HDRLEN) ||
+ (doff > (PKTLEN(pfirst) - SDPCM_HDRLEN))) {
+ DHD_ERROR(("%s (superframe): Bad data offset %d: HW %d "
+ "pkt %d min %d\n",
+ __func__, doff, sublen,
+ PKTLEN(pfirst), SDPCM_HDRLEN));
+ errcode = -1;
+ }
+
+ /* Check sequence number of superframe SW header */
+ if (rxseq != seq) {
+ DHD_INFO(("%s: (superframe) rx_seq %d, expected %d\n",
+ __func__, seq, rxseq));
+ bus->rx_badseq++;
+ rxseq = seq;
+ }
+
+ /* Check window for sanity */
+ if ((u8) (txmax - bus->tx_seq) > 0x40) {
+ DHD_ERROR(("%s: unlikely tx max %d with tx_seq %d\n",
+ __func__, txmax, bus->tx_seq));
+ txmax = bus->tx_seq + 2;
+ }
+ bus->tx_max = txmax;
+
+ /* Remove superframe header, remember offset */
+ PKTPULL(pfirst, doff);
+ sfdoff = doff;
+
+ /* Validate all the subframe headers */
+ for (num = 0, pnext = pfirst; pnext && !errcode;
+ num++, pnext = PKTNEXT(pnext)) {
+ dptr = (u8 *) PKTDATA(pnext);
+ dlen = (u16) PKTLEN(pnext);
+ sublen = ltoh16_ua(dptr);
+ check = ltoh16_ua(dptr + sizeof(u16));
+ chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
+ doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
+#ifdef DHD_DEBUG
+ if (DHD_GLOM_ON())
+ prhex("subframe", dptr, 32);
+#endif
+
+ if ((u16)~(sublen ^ check)) {
+ DHD_ERROR(("%s (subframe %d): HW hdr error: "
+ "len/check 0x%04x/0x%04x\n",
+ __func__, num, sublen, check));
+ errcode = -1;
+ } else if ((sublen > dlen) || (sublen < SDPCM_HDRLEN)) {
+ DHD_ERROR(("%s (subframe %d): length mismatch: "
+ "len 0x%04x, expect 0x%04x\n",
+ __func__, num, sublen, dlen));
+ errcode = -1;
+ } else if ((chan != SDPCM_DATA_CHANNEL) &&
+ (chan != SDPCM_EVENT_CHANNEL)) {
+ DHD_ERROR(("%s (subframe %d): bad channel %d\n",
+ __func__, num, chan));
+ errcode = -1;
+ } else if ((doff < SDPCM_HDRLEN) || (doff > sublen)) {
+ DHD_ERROR(("%s (subframe %d): Bad data offset %d: HW %d min %d\n",
+ __func__, num, doff, sublen,
+ SDPCM_HDRLEN));
+ errcode = -1;
+ }
+ }
+
+ if (errcode) {
+ /* Terminate frame on error, request
+ a couple retries */
+ if (bus->glomerr++ < 3) {
+ /* Restore superframe header space */
+ PKTPUSH(pfirst, sfdoff);
+ dhdsdio_rxfail(bus, true, true);
+ } else {
+ bus->glomerr = 0;
+ dhdsdio_rxfail(bus, true, false);
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(osh, bus->glom, false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ bus->rxglomfail++;
+ bus->glom = NULL;
+ }
+ bus->nextlen = 0;
+ return 0;
+ }
+
+ /* Basic SD framing looks ok - process each packet (header) */
+ save_pfirst = pfirst;
+ bus->glom = NULL;
+ plast = NULL;
+
+ dhd_os_sdlock_rxq(bus->dhd);
+ for (num = 0; pfirst; rxseq++, pfirst = pnext) {
+ pnext = PKTNEXT(pfirst);
+ PKTSETNEXT(pfirst, NULL);
+
+ dptr = (u8 *) PKTDATA(pfirst);
+ sublen = ltoh16_ua(dptr);
+ chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
+ seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]);
+ doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
+
+ DHD_GLOM(("%s: Get subframe %d, %p(%p/%d), sublen %d "
+ "chan %d seq %d\n",
+ __func__, num, pfirst, PKTDATA(pfirst),
+ PKTLEN(pfirst), sublen, chan, seq));
+
+ ASSERT((chan == SDPCM_DATA_CHANNEL)
+ || (chan == SDPCM_EVENT_CHANNEL));
+
+ if (rxseq != seq) {
+ DHD_GLOM(("%s: rx_seq %d, expected %d\n",
+ __func__, seq, rxseq));
+ bus->rx_badseq++;
+ rxseq = seq;
+ }
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_DATA_ON())
+ prhex("Rx Subframe Data", dptr, dlen);
+#endif
+
+ PKTSETLEN(pfirst, sublen);
+ PKTPULL(pfirst, doff);
+
+ if (PKTLEN(pfirst) == 0) {
+ PKTFREE(bus->dhd->osh, pfirst, false);
+ if (plast) {
+ PKTSETNEXT(plast, pnext);
+ } else {
+ ASSERT(save_pfirst == pfirst);
+ save_pfirst = pnext;
+ }
+ continue;
+ } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pfirst) !=
+ 0) {
+ DHD_ERROR(("%s: rx protocol error\n",
+ __func__));
+ bus->dhd->rx_errors++;
+ PKTFREE(osh, pfirst, false);
+ if (plast) {
+ PKTSETNEXT(plast, pnext);
+ } else {
+ ASSERT(save_pfirst == pfirst);
+ save_pfirst = pnext;
+ }
+ continue;
+ }
+
+ /* this packet will go up, link back into
+ chain and count it */
+ PKTSETNEXT(pfirst, pnext);
+ plast = pfirst;
+ num++;
+
+#ifdef DHD_DEBUG
+ if (DHD_GLOM_ON()) {
+ DHD_GLOM(("%s subframe %d to stack, %p(%p/%d) "
+ "nxt/lnk %p/%p\n",
+ __func__, num, pfirst, PKTDATA(pfirst),
+ PKTLEN(pfirst), PKTNEXT(pfirst),
+ PKTLINK(pfirst)));
+ prhex("", (u8 *) PKTDATA(pfirst),
+ min_t(int, PKTLEN(pfirst), 32));
+ }
+#endif /* DHD_DEBUG */
+ }
+ dhd_os_sdunlock_rxq(bus->dhd);
+ if (num) {
+ dhd_os_sdunlock(bus->dhd);
+ dhd_rx_frame(bus->dhd, ifidx, save_pfirst, num);
+ dhd_os_sdlock(bus->dhd);
+ }
+
+ bus->rxglomframes++;
+ bus->rxglompkts += num;
+ }
+ return num;
+}
+
+/* Return true if there may be more frames to read */
+static uint dhdsdio_readframes(dhd_bus_t *bus, uint maxframes, bool *finished)
+{
+ osl_t *osh = bus->dhd->osh;
+ bcmsdh_info_t *sdh = bus->sdh;
+
+ u16 len, check; /* Extracted hardware header fields */
+ u8 chan, seq, doff; /* Extracted software header fields */
+ u8 fcbits; /* Extracted fcbits from software header */
+ u8 delta;
+
+ void *pkt; /* Packet for event or data frames */
+ u16 pad; /* Number of pad bytes to read */
+ u16 rdlen; /* Total number of bytes to read */
+ u8 rxseq; /* Next sequence number to expect */
+ uint rxleft = 0; /* Remaining number of frames allowed */
+ int sdret; /* Return code from bcmsdh calls */
+ u8 txmax; /* Maximum tx sequence offered */
+ bool len_consistent; /* Result of comparing readahead len and
+ len from hw-hdr */
+ u8 *rxbuf;
+ int ifidx = 0;
+ uint rxcount = 0; /* Total frames read */
+
+#if defined(DHD_DEBUG) || defined(SDTEST)
+ bool sdtest = false; /* To limit message spew from test mode */
+#endif
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ ASSERT(maxframes);
+
+#ifdef SDTEST
+ /* Allow pktgen to override maxframes */
+ if (bus->pktgen_count && (bus->pktgen_mode == DHD_PKTGEN_RECV)) {
+ maxframes = bus->pktgen_count;
+ sdtest = true;
+ }
+#endif
+
+ /* Not finished unless we encounter no more frames indication */
+ *finished = false;
+
+ for (rxseq = bus->rx_seq, rxleft = maxframes;
+ !bus->rxskip && rxleft && bus->dhd->busstate != DHD_BUS_DOWN;
+ rxseq++, rxleft--) {
+
+ /* Handle glomming separately */
+ if (bus->glom || bus->glomd) {
+ u8 cnt;
+ DHD_GLOM(("%s: calling rxglom: glomd %p, glom %p\n",
+ __func__, bus->glomd, bus->glom));
+ cnt = dhdsdio_rxglom(bus, rxseq);
+ DHD_GLOM(("%s: rxglom returned %d\n", __func__, cnt));
+ rxseq += cnt - 1;
+ rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1;
+ continue;
+ }
+
+ /* Try doing single read if we can */
+ if (dhd_readahead && bus->nextlen) {
+ u16 nextlen = bus->nextlen;
+ bus->nextlen = 0;
+
+ if (bus->bus == SPI_BUS) {
+ rdlen = len = nextlen;
+ } else {
+ rdlen = len = nextlen << 4;
+
+ /* Pad read to blocksize for efficiency */
+ if (bus->roundup && bus->blocksize
+ && (rdlen > bus->blocksize)) {
+ pad =
+ bus->blocksize -
+ (rdlen % bus->blocksize);
+ if ((pad <= bus->roundup)
+ && (pad < bus->blocksize)
+ && ((rdlen + pad + firstread) <
+ MAX_RX_DATASZ))
+ rdlen += pad;
+ } else if (rdlen % DHD_SDALIGN) {
+ rdlen +=
+ DHD_SDALIGN - (rdlen % DHD_SDALIGN);
+ }
+ }
+
+ /* We use bus->rxctl buffer in WinXP for initial
+ * control pkt receives.
+ * Later we use buffer-poll for data as well
+ * as control packets.
+ * This is required becuase dhd receives full
+ * frame in gSPI unlike SDIO.
+ * After the frame is received we have to
+ * distinguish whether it is data
+ * or non-data frame.
+ */
+ /* Allocate a packet buffer */
+ dhd_os_sdlock_rxq(bus->dhd);
+ pkt = PKTGET(osh, rdlen + DHD_SDALIGN, false);
+ if (!pkt) {
+ if (bus->bus == SPI_BUS) {
+ bus->usebufpool = false;
+ bus->rxctl = bus->rxbuf;
+ if (dhd_alignctl) {
+ bus->rxctl += firstread;
+ pad = ((unsigned long)bus->rxctl %
+ DHD_SDALIGN);
+ if (pad)
+ bus->rxctl +=
+ (DHD_SDALIGN - pad);
+ bus->rxctl -= firstread;
+ }
+ ASSERT(bus->rxctl >= bus->rxbuf);
+ rxbuf = bus->rxctl;
+ /* Read the entire frame */
+ sdret = dhd_bcmsdh_recv_buf(bus,
+ bcmsdh_cur_sbwad
+ (sdh),
+ SDIO_FUNC_2,
+ F2SYNC,
+ rxbuf,
+ rdlen, NULL,
+ NULL, NULL);
+ bus->f2rxdata++;
+ ASSERT(sdret != BCME_PENDING);
+
+ /* Control frame failures need
+ retransmission */
+ if (sdret < 0) {
+ DHD_ERROR(("%s: read %d control bytes failed: %d\n",
+ __func__,
+ rdlen, sdret));
+ /* dhd.rx_ctlerrs is higher */
+ bus->rxc_errors++;
+ dhd_os_sdunlock_rxq(bus->dhd);
+ dhdsdio_rxfail(bus, true,
+ (bus->bus ==
+ SPI_BUS) ? false
+ : true);
+ continue;
+ }
+ } else {
+ /* Give up on data,
+ request rtx of events */
+ DHD_ERROR(("%s (nextlen): PKTGET failed: len %d rdlen %d " "expected rxseq %d\n",
+ __func__, len, rdlen, rxseq));
+ /* Just go try again w/normal
+ header read */
+ dhd_os_sdunlock_rxq(bus->dhd);
+ continue;
+ }
+ } else {
+ if (bus->bus == SPI_BUS)
+ bus->usebufpool = true;
+
+ ASSERT(!PKTLINK(pkt));
+ PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN);
+ rxbuf = (u8 *) PKTDATA(pkt);
+ /* Read the entire frame */
+ sdret =
+ dhd_bcmsdh_recv_buf(bus,
+ bcmsdh_cur_sbwad(sdh),
+ SDIO_FUNC_2, F2SYNC,
+ rxbuf, rdlen, pkt, NULL,
+ NULL);
+ bus->f2rxdata++;
+ ASSERT(sdret != BCME_PENDING);
+
+ if (sdret < 0) {
+ DHD_ERROR(("%s (nextlen): read %d bytes failed: %d\n",
+ __func__, rdlen, sdret));
+ PKTFREE(bus->dhd->osh, pkt, false);
+ bus->dhd->rx_errors++;
+ dhd_os_sdunlock_rxq(bus->dhd);
+ /* Force retry w/normal header read.
+ * Don't attemp NAK for
+ * gSPI
+ */
+ dhdsdio_rxfail(bus, true,
+ (bus->bus ==
+ SPI_BUS) ? false :
+ true);
+ continue;
+ }
+ }
+ dhd_os_sdunlock_rxq(bus->dhd);
+
+ /* Now check the header */
+ bcopy(rxbuf, bus->rxhdr, SDPCM_HDRLEN);
+
+ /* Extract hardware header fields */
+ len = ltoh16_ua(bus->rxhdr);
+ check = ltoh16_ua(bus->rxhdr + sizeof(u16));
+
+ /* All zeros means readahead info was bad */
+ if (!(len | check)) {
+ DHD_INFO(("%s (nextlen): read zeros in HW "
+ "header???\n", __func__));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ GSPI_PR55150_BAILOUT;
+ continue;
+ }
+
+ /* Validate check bytes */
+ if ((u16)~(len ^ check)) {
+ DHD_ERROR(("%s (nextlen): HW hdr error: nextlen/len/check" " 0x%04x/0x%04x/0x%04x\n",
+ __func__, nextlen, len, check));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ bus->rx_badhdr++;
+ dhdsdio_rxfail(bus, false, false);
+ GSPI_PR55150_BAILOUT;
+ continue;
+ }
+
+ /* Validate frame length */
+ if (len < SDPCM_HDRLEN) {
+ DHD_ERROR(("%s (nextlen): HW hdr length "
+ "invalid: %d\n", __func__, len));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ GSPI_PR55150_BAILOUT;
+ continue;
+ }
+
+ /* Check for consistency withreadahead info */
+ len_consistent = (nextlen != (roundup(len, 16) >> 4));
+ if (len_consistent) {
+ /* Mismatch, force retry w/normal
+ header (may be >4K) */
+ DHD_ERROR(("%s (nextlen): mismatch, nextlen %d len %d rnd %d; " "expected rxseq %d\n",
+ __func__, nextlen,
+ len, roundup(len, 16), rxseq));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ dhdsdio_rxfail(bus, true,
+ (bus->bus ==
+ SPI_BUS) ? false : true);
+ GSPI_PR55150_BAILOUT;
+ continue;
+ }
+
+ /* Extract software header fields */
+ chan =
+ SDPCM_PACKET_CHANNEL(&bus->rxhdr
+ [SDPCM_FRAMETAG_LEN]);
+ seq =
+ SDPCM_PACKET_SEQUENCE(&bus->rxhdr
+ [SDPCM_FRAMETAG_LEN]);
+ doff =
+ SDPCM_DOFFSET_VALUE(&bus->rxhdr
+ [SDPCM_FRAMETAG_LEN]);
+ txmax =
+ SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+
+ bus->nextlen =
+ bus->rxhdr[SDPCM_FRAMETAG_LEN +
+ SDPCM_NEXTLEN_OFFSET];
+ if ((bus->nextlen << 4) > MAX_RX_DATASZ) {
+ DHD_INFO(("%s (nextlen): got frame w/nextlen too large" " (%d), seq %d\n",
+ __func__, bus->nextlen, seq));
+ bus->nextlen = 0;
+ }
+
+ bus->dhd->rx_readahead_cnt++;
+ /* Handle Flow Control */
+ fcbits =
+ SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+
+ delta = 0;
+ if (~bus->flowcontrol & fcbits) {
+ bus->fc_xoff++;
+ delta = 1;
+ }
+ if (bus->flowcontrol & ~fcbits) {
+ bus->fc_xon++;
+ delta = 1;
+ }
+
+ if (delta) {
+ bus->fc_rcvd++;
+ bus->flowcontrol = fcbits;
+ }
+
+ /* Check and update sequence number */
+ if (rxseq != seq) {
+ DHD_INFO(("%s (nextlen): rx_seq %d, expected "
+ "%d\n", __func__, seq, rxseq));
+ bus->rx_badseq++;
+ rxseq = seq;
+ }
+
+ /* Check window for sanity */
+ if ((u8) (txmax - bus->tx_seq) > 0x40) {
+ DHD_ERROR(("%s: got unlikely tx max %d with "
+ "tx_seq %d\n",
+ __func__, txmax, bus->tx_seq));
+ txmax = bus->tx_seq + 2;
+ }
+ bus->tx_max = txmax;
+
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_DATA_ON())
+ prhex("Rx Data", rxbuf, len);
+ else if (DHD_HDRS_ON())
+ prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN);
+#endif
+
+ if (chan == SDPCM_CONTROL_CHANNEL) {
+ if (bus->bus == SPI_BUS) {
+ dhdsdio_read_control(bus, rxbuf, len,
+ doff);
+ if (bus->usebufpool) {
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(bus->dhd->osh, pkt,
+ false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ }
+ continue;
+ } else {
+ DHD_ERROR(("%s (nextlen): readahead on control" " packet %d?\n",
+ __func__, seq));
+ /* Force retry w/normal header read */
+ bus->nextlen = 0;
+ dhdsdio_rxfail(bus, false, true);
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ continue;
+ }
+ }
+
+ if ((bus->bus == SPI_BUS) && !bus->usebufpool) {
+ DHD_ERROR(("Received %d bytes on %d channel. Running out of " "rx pktbuf's or not yet malloced.\n",
+ len, chan));
+ continue;
+ }
+
+ /* Validate data offset */
+ if ((doff < SDPCM_HDRLEN) || (doff > len)) {
+ DHD_ERROR(("%s (nextlen): bad data offset %d: HW len %d min %d\n",
+ __func__, doff, len, SDPCM_HDRLEN));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE2();
+ dhd_os_sdunlock_rxq(bus->dhd);
+ ASSERT(0);
+ dhdsdio_rxfail(bus, false, false);
+ continue;
+ }
+
+ /* All done with this one -- now deliver the packet */
+ goto deliver;
+ }
+ /* gSPI frames should not be handled in fractions */
+ if (bus->bus == SPI_BUS)
+ break;
+
+ /* Read frame header (hardware and software) */
+ sdret =
+ dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
+ F2SYNC, bus->rxhdr, firstread, NULL,
+ NULL, NULL);
+ bus->f2rxhdrs++;
+ ASSERT(sdret != BCME_PENDING);
+
+ if (sdret < 0) {
+ DHD_ERROR(("%s: RXHEADER FAILED: %d\n", __func__,
+ sdret));
+ bus->rx_hdrfail++;
+ dhdsdio_rxfail(bus, true, true);
+ continue;
+ }
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() || DHD_HDRS_ON())
+ prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN);
+#endif
+
+ /* Extract hardware header fields */
+ len = ltoh16_ua(bus->rxhdr);
+ check = ltoh16_ua(bus->rxhdr + sizeof(u16));
+
+ /* All zeros means no more frames */
+ if (!(len | check)) {
+ *finished = true;
+ break;
+ }
+
+ /* Validate check bytes */
+ if ((u16) ~(len ^ check)) {
+ DHD_ERROR(("%s: HW hdr err: len/check 0x%04x/0x%04x\n",
+ __func__, len, check));
+ bus->rx_badhdr++;
+ dhdsdio_rxfail(bus, false, false);
+ continue;
+ }
+
+ /* Validate frame length */
+ if (len < SDPCM_HDRLEN) {
+ DHD_ERROR(("%s: HW hdr length invalid: %d\n",
+ __func__, len));
+ continue;
+ }
+
+ /* Extract software header fields */
+ chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+ seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+ doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+ txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+
+ /* Validate data offset */
+ if ((doff < SDPCM_HDRLEN) || (doff > len)) {
+ DHD_ERROR(("%s: Bad data offset %d: HW len %d, min %d "
+ "seq %d\n",
+ __func__, doff, len, SDPCM_HDRLEN, seq));
+ bus->rx_badhdr++;
+ ASSERT(0);
+ dhdsdio_rxfail(bus, false, false);
+ continue;
+ }
+
+ /* Save the readahead length if there is one */
+ bus->nextlen =
+ bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET];
+ if ((bus->nextlen << 4) > MAX_RX_DATASZ) {
+ DHD_INFO(("%s (nextlen): got frame w/nextlen too large "
+ "(%d), seq %d\n",
+ __func__, bus->nextlen, seq));
+ bus->nextlen = 0;
+ }
+
+ /* Handle Flow Control */
+ fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
+
+ delta = 0;
+ if (~bus->flowcontrol & fcbits) {
+ bus->fc_xoff++;
+ delta = 1;
+ }
+ if (bus->flowcontrol & ~fcbits) {
+ bus->fc_xon++;
+ delta = 1;
+ }
+
+ if (delta) {
+ bus->fc_rcvd++;
+ bus->flowcontrol = fcbits;
+ }
+
+ /* Check and update sequence number */
+ if (rxseq != seq) {
+ DHD_INFO(("%s: rx_seq %d, expected %d\n", __func__,
+ seq, rxseq));
+ bus->rx_badseq++;
+ rxseq = seq;
+ }
+
+ /* Check window for sanity */
+ if ((u8) (txmax - bus->tx_seq) > 0x40) {
+ DHD_ERROR(("%s: unlikely tx max %d with tx_seq %d\n",
+ __func__, txmax, bus->tx_seq));
+ txmax = bus->tx_seq + 2;
+ }
+ bus->tx_max = txmax;
+
+ /* Call a separate function for control frames */
+ if (chan == SDPCM_CONTROL_CHANNEL) {
+ dhdsdio_read_control(bus, bus->rxhdr, len, doff);
+ continue;
+ }
+
+ ASSERT((chan == SDPCM_DATA_CHANNEL)
+ || (chan == SDPCM_EVENT_CHANNEL)
+ || (chan == SDPCM_TEST_CHANNEL)
+ || (chan == SDPCM_GLOM_CHANNEL));
+
+ /* Length to read */
+ rdlen = (len > firstread) ? (len - firstread) : 0;
+
+ /* May pad read to blocksize for efficiency */
+ if (bus->roundup && bus->blocksize &&
+ (rdlen > bus->blocksize)) {
+ pad = bus->blocksize - (rdlen % bus->blocksize);
+ if ((pad <= bus->roundup) && (pad < bus->blocksize) &&
+ ((rdlen + pad + firstread) < MAX_RX_DATASZ))
+ rdlen += pad;
+ } else if (rdlen % DHD_SDALIGN) {
+ rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN);
+ }
+
+ /* Satisfy length-alignment requirements */
+ if (forcealign && (rdlen & (ALIGNMENT - 1)))
+ rdlen = roundup(rdlen, ALIGNMENT);
+
+ if ((rdlen + firstread) > MAX_RX_DATASZ) {
+ /* Too long -- skip this frame */
+ DHD_ERROR(("%s: too long: len %d rdlen %d\n",
+ __func__, len, rdlen));
+ bus->dhd->rx_errors++;
+ bus->rx_toolong++;
+ dhdsdio_rxfail(bus, false, false);
+ continue;
+ }
+
+ dhd_os_sdlock_rxq(bus->dhd);
+ pkt = PKTGET(osh, (rdlen + firstread + DHD_SDALIGN), false);
+ if (!pkt) {
+ /* Give up on data, request rtx of events */
+ DHD_ERROR(("%s: PKTGET failed: rdlen %d chan %d\n",
+ __func__, rdlen, chan));
+ bus->dhd->rx_dropped++;
+ dhd_os_sdunlock_rxq(bus->dhd);
+ dhdsdio_rxfail(bus, false, RETRYCHAN(chan));
+ continue;
+ }
+ dhd_os_sdunlock_rxq(bus->dhd);
+
+ ASSERT(!PKTLINK(pkt));
+
+ /* Leave room for what we already read, and align remainder */
+ ASSERT(firstread < (PKTLEN(pkt)));
+ PKTPULL(pkt, firstread);
+ PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN);
+
+ /* Read the remaining frame data */
+ sdret =
+ dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
+ F2SYNC, ((u8 *) PKTDATA(pkt)), rdlen,
+ pkt, NULL, NULL);
+ bus->f2rxdata++;
+ ASSERT(sdret != BCME_PENDING);
+
+ if (sdret < 0) {
+ DHD_ERROR(("%s: read %d %s bytes failed: %d\n",
+ __func__, rdlen,
+ ((chan ==
+ SDPCM_EVENT_CHANNEL) ? "event" : ((chan ==
+ SDPCM_DATA_CHANNEL)
+ ? "data" : "test")),
+ sdret));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(bus->dhd->osh, pkt, false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ bus->dhd->rx_errors++;
+ dhdsdio_rxfail(bus, true, RETRYCHAN(chan));
+ continue;
+ }
+
+ /* Copy the already-read portion */
+ PKTPUSH(pkt, firstread);
+ bcopy(bus->rxhdr, PKTDATA(pkt), firstread);
+
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_DATA_ON())
+ prhex("Rx Data", PKTDATA(pkt), len);
+#endif
+
+deliver:
+ /* Save superframe descriptor and allocate packet frame */
+ if (chan == SDPCM_GLOM_CHANNEL) {
+ if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_FRAMETAG_LEN])) {
+ DHD_GLOM(("%s: glom descriptor, %d bytes:\n",
+ __func__, len));
+#ifdef DHD_DEBUG
+ if (DHD_GLOM_ON()) {
+ prhex("Glom Data", PKTDATA(pkt), len);
+ }
+#endif
+ PKTSETLEN(pkt, len);
+ ASSERT(doff == SDPCM_HDRLEN);
+ PKTPULL(pkt, SDPCM_HDRLEN);
+ bus->glomd = pkt;
+ } else {
+ DHD_ERROR(("%s: glom superframe w/o "
+ "descriptor!\n", __func__));
+ dhdsdio_rxfail(bus, false, false);
+ }
+ continue;
+ }
+
+ /* Fill in packet len and prio, deliver upward */
+ PKTSETLEN(pkt, len);
+ PKTPULL(pkt, doff);
+
+#ifdef SDTEST
+ /* Test channel packets are processed separately */
+ if (chan == SDPCM_TEST_CHANNEL) {
+ dhdsdio_testrcv(bus, pkt, seq);
+ continue;
+ }
+#endif /* SDTEST */
+
+ if (PKTLEN(pkt) == 0) {
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(bus->dhd->osh, pkt, false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ continue;
+ } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pkt) != 0) {
+ DHD_ERROR(("%s: rx protocol error\n", __func__));
+ dhd_os_sdlock_rxq(bus->dhd);
+ PKTFREE(bus->dhd->osh, pkt, false);
+ dhd_os_sdunlock_rxq(bus->dhd);
+ bus->dhd->rx_errors++;
+ continue;
+ }
+
+ /* Unlock during rx call */
+ dhd_os_sdunlock(bus->dhd);
+ dhd_rx_frame(bus->dhd, ifidx, pkt, 1);
+ dhd_os_sdlock(bus->dhd);
+ }
+ rxcount = maxframes - rxleft;
+#ifdef DHD_DEBUG
+ /* Message if we hit the limit */
+ if (!rxleft && !sdtest)
+ DHD_DATA(("%s: hit rx limit of %d frames\n", __func__,
+ maxframes));
+ else
+#endif /* DHD_DEBUG */
+ DHD_DATA(("%s: processed %d frames\n", __func__, rxcount));
+ /* Back off rxseq if awaiting rtx, update rx_seq */
+ if (bus->rxskip)
+ rxseq--;
+ bus->rx_seq = rxseq;
+
+ return rxcount;
+}
+
+static u32 dhdsdio_hostmail(dhd_bus_t *bus)
+{
+ sdpcmd_regs_t *regs = bus->regs;
+ u32 intstatus = 0;
+ u32 hmb_data;
+ u8 fcbits;
+ uint retries = 0;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Read mailbox data and ack that we did so */
+ R_SDREG(hmb_data, &regs->tohostmailboxdata, retries);
+ if (retries <= retry_limit)
+ W_SDREG(SMB_INT_ACK, &regs->tosbmailbox, retries);
+ bus->f1regdata += 2;
+
+ /* Dongle recomposed rx frames, accept them again */
+ if (hmb_data & HMB_DATA_NAKHANDLED) {
+ DHD_INFO(("Dongle reports NAK handled, expect rtx of %d\n",
+ bus->rx_seq));
+ if (!bus->rxskip)
+ DHD_ERROR(("%s: unexpected NAKHANDLED!\n", __func__));
+
+ bus->rxskip = false;
+ intstatus |= I_HMB_FRAME_IND;
+ }
+
+ /*
+ * DEVREADY does not occur with gSPI.
+ */
+ if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) {
+ bus->sdpcm_ver =
+ (hmb_data & HMB_DATA_VERSION_MASK) >>
+ HMB_DATA_VERSION_SHIFT;
+ if (bus->sdpcm_ver != SDPCM_PROT_VERSION)
+ DHD_ERROR(("Version mismatch, dongle reports %d, "
+ "expecting %d\n",
+ bus->sdpcm_ver, SDPCM_PROT_VERSION));
+ else
+ DHD_INFO(("Dongle ready, protocol version %d\n",
+ bus->sdpcm_ver));
+ }
+
+ /*
+ * Flow Control has been moved into the RX headers and this out of band
+ * method isn't used any more. Leae this here for possibly
+ * remaining backward
+ * compatible with older dongles
+ */
+ if (hmb_data & HMB_DATA_FC) {
+ fcbits =
+ (hmb_data & HMB_DATA_FCDATA_MASK) >> HMB_DATA_FCDATA_SHIFT;
+
+ if (fcbits & ~bus->flowcontrol)
+ bus->fc_xoff++;
+ if (bus->flowcontrol & ~fcbits)
+ bus->fc_xon++;
+
+ bus->fc_rcvd++;
+ bus->flowcontrol = fcbits;
+ }
+
+ /* Shouldn't be any others */
+ if (hmb_data & ~(HMB_DATA_DEVREADY |
+ HMB_DATA_NAKHANDLED |
+ HMB_DATA_FC |
+ HMB_DATA_FWREADY |
+ HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK)) {
+ DHD_ERROR(("Unknown mailbox data content: 0x%02x\n", hmb_data));
+ }
+
+ return intstatus;
+}
+
+bool dhdsdio_dpc(dhd_bus_t *bus)
+{
+ bcmsdh_info_t *sdh = bus->sdh;
+ sdpcmd_regs_t *regs = bus->regs;
+ u32 intstatus, newstatus = 0;
+ uint retries = 0;
+ uint rxlimit = dhd_rxbound; /* Rx frames to read before resched */
+ uint txlimit = dhd_txbound; /* Tx frames to send before resched */
+ uint framecnt = 0; /* Temporary counter of tx/rx frames */
+ bool rxdone = true; /* Flag for no more read data */
+ bool resched = false; /* Flag indicating resched wanted */
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ /* Start with leftover status bits */
+ intstatus = bus->intstatus;
+
+ dhd_os_sdlock(bus->dhd);
+
+ /* If waiting for HTAVAIL, check status */
+ if (bus->clkstate == CLK_PENDING) {
+ int err;
+ u8 clkctl, devctl = 0;
+
+#ifdef DHD_DEBUG
+ /* Check for inconsistent device control */
+ devctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
+ if (err) {
+ DHD_ERROR(("%s: error reading DEVCTL: %d\n",
+ __func__, err));
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ } else {
+ ASSERT(devctl & SBSDIO_DEVCTL_CA_INT_ONLY);
+ }
+#endif /* DHD_DEBUG */
+
+ /* Read CSR, if clock on switch to AVAIL, else ignore */
+ clkctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (err) {
+ DHD_ERROR(("%s: error reading CSR: %d\n", __func__,
+ err));
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ }
+
+ DHD_INFO(("DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", devctl,
+ clkctl));
+
+ if (SBSDIO_HTAV(clkctl)) {
+ devctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ &err);
+ if (err) {
+ DHD_ERROR(("%s: error reading DEVCTL: %d\n",
+ __func__, err));
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ }
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ if (err) {
+ DHD_ERROR(("%s: error writing DEVCTL: %d\n",
+ __func__, err));
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ }
+ bus->clkstate = CLK_AVAIL;
+ } else {
+ goto clkwait;
+ }
+ }
+
+ BUS_WAKE(bus);
+
+ /* Make sure backplane clock is on */
+ dhdsdio_clkctl(bus, CLK_AVAIL, true);
+ if (bus->clkstate == CLK_PENDING)
+ goto clkwait;
+
+ /* Pending interrupt indicates new device status */
+ if (bus->ipend) {
+ bus->ipend = false;
+ R_SDREG(newstatus, &regs->intstatus, retries);
+ bus->f1regdata++;
+ if (bcmsdh_regfail(bus->sdh))
+ newstatus = 0;
+ newstatus &= bus->hostintmask;
+ bus->fcstate = !!(newstatus & I_HMB_FC_STATE);
+ if (newstatus) {
+ W_SDREG(newstatus, &regs->intstatus, retries);
+ bus->f1regdata++;
+ }
+ }
+
+ /* Merge new bits with previous */
+ intstatus |= newstatus;
+ bus->intstatus = 0;
+
+ /* Handle flow-control change: read new state in case our ack
+ * crossed another change interrupt. If change still set, assume
+ * FC ON for safety, let next loop through do the debounce.
+ */
+ if (intstatus & I_HMB_FC_CHANGE) {
+ intstatus &= ~I_HMB_FC_CHANGE;
+ W_SDREG(I_HMB_FC_CHANGE, &regs->intstatus, retries);
+ R_SDREG(newstatus, &regs->intstatus, retries);
+ bus->f1regdata += 2;
+ bus->fcstate =
+ !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE));
+ intstatus |= (newstatus & bus->hostintmask);
+ }
+
+ /* Handle host mailbox indication */
+ if (intstatus & I_HMB_HOST_INT) {
+ intstatus &= ~I_HMB_HOST_INT;
+ intstatus |= dhdsdio_hostmail(bus);
+ }
+
+ /* Generally don't ask for these, can get CRC errors... */
+ if (intstatus & I_WR_OOSYNC) {
+ DHD_ERROR(("Dongle reports WR_OOSYNC\n"));
+ intstatus &= ~I_WR_OOSYNC;
+ }
+
+ if (intstatus & I_RD_OOSYNC) {
+ DHD_ERROR(("Dongle reports RD_OOSYNC\n"));
+ intstatus &= ~I_RD_OOSYNC;
+ }
+
+ if (intstatus & I_SBINT) {
+ DHD_ERROR(("Dongle reports SBINT\n"));
+ intstatus &= ~I_SBINT;
+ }
+
+ /* Would be active due to wake-wlan in gSPI */
+ if (intstatus & I_CHIPACTIVE) {
+ DHD_INFO(("Dongle reports CHIPACTIVE\n"));
+ intstatus &= ~I_CHIPACTIVE;
+ }
+
+ /* Ignore frame indications if rxskip is set */
+ if (bus->rxskip)
+ intstatus &= ~I_HMB_FRAME_IND;
+
+ /* On frame indication, read available frames */
+ if (PKT_AVAILABLE()) {
+ framecnt = dhdsdio_readframes(bus, rxlimit, &rxdone);
+ if (rxdone || bus->rxskip)
+ intstatus &= ~I_HMB_FRAME_IND;
+ rxlimit -= min(framecnt, rxlimit);
+ }
+
+ /* Keep still-pending events for next scheduling */
+ bus->intstatus = intstatus;
+
+clkwait:
+#if defined(OOB_INTR_ONLY)
+ bcmsdh_oob_intr_set(1);
+#endif /* (OOB_INTR_ONLY) */
+ /* Re-enable interrupts to detect new device events (mailbox, rx frame)
+ * or clock availability. (Allows tx loop to check ipend if desired.)
+ * (Unless register access seems hosed, as we may not be able to ACK...)
+ */
+ if (bus->intr && bus->intdis && !bcmsdh_regfail(sdh)) {
+ DHD_INTR(("%s: enable SDIO interrupts, rxdone %d framecnt %d\n",
+ __func__, rxdone, framecnt));
+ bus->intdis = false;
+ bcmsdh_intr_enable(sdh);
+ }
+
+ if (DATAOK(bus) && bus->ctrl_frame_stat &&
+ (bus->clkstate == CLK_AVAIL)) {
+ int ret, i;
+
+ ret =
+ dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
+ F2SYNC, (u8 *) bus->ctrl_frame_buf,
+ (u32) bus->ctrl_frame_len, NULL,
+ NULL, NULL);
+ ASSERT(ret != BCME_PENDING);
+
+ if (ret < 0) {
+ /* On failure, abort the command and
+ terminate the frame */
+ DHD_INFO(("%s: sdio error %d, abort command and "
+ "terminate frame.\n", __func__, ret));
+ bus->tx_sderrs++;
+
+ bcmsdh_abort(sdh, SDIO_FUNC_2);
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM,
+ NULL);
+ bus->f1regdata++;
+
+ for (i = 0; i < 3; i++) {
+ u8 hi, lo;
+ hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCHI,
+ NULL);
+ lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_WFRAMEBCLO,
+ NULL);
+ bus->f1regdata += 2;
+ if ((hi == 0) && (lo == 0))
+ break;
+ }
+
+ }
+ if (ret == 0)
+ bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
+
+ printf("Return_dpc value is : %d\n", ret);
+ bus->ctrl_frame_stat = false;
+ dhd_wait_event_wakeup(bus->dhd);
+ }
+ /* Send queued frames (limit 1 if rx may still be pending) */
+ else if ((bus->clkstate == CLK_AVAIL) && !bus->fcstate &&
+ pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit
+ && DATAOK(bus)) {
+ framecnt = rxdone ? txlimit : min(txlimit, dhd_txminmax);
+ framecnt = dhdsdio_sendfromq(bus, framecnt);
+ txlimit -= framecnt;
+ }
+
+ /* Resched if events or tx frames are pending,
+ else await next interrupt */
+ /* On failed register access, all bets are off:
+ no resched or interrupts */
+ if ((bus->dhd->busstate == DHD_BUS_DOWN) || bcmsdh_regfail(sdh)) {
+ DHD_ERROR(("%s: failed backplane access over SDIO, halting "
+ "operation %d\n", __func__, bcmsdh_regfail(sdh)));
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ bus->intstatus = 0;
+ } else if (bus->clkstate == CLK_PENDING) {
+ DHD_INFO(("%s: rescheduled due to CLK_PENDING awaiting "
+ "I_CHIPACTIVE interrupt\n", __func__));
+ resched = true;
+ } else if (bus->intstatus || bus->ipend ||
+ (!bus->fcstate && pktq_mlen(&bus->txq, ~bus->flowcontrol) &&
+ DATAOK(bus)) || PKT_AVAILABLE()) {
+ resched = true;
+ }
+
+ bus->dpc_sched = resched;
+
+ /* If we're done for now, turn off clock request. */
+ if ((bus->clkstate != CLK_PENDING)
+ && bus->idletime == DHD_IDLE_IMMEDIATE) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, false);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+
+ return resched;
+}
+
+bool dhd_bus_dpc(struct dhd_bus *bus)
+{
+ bool resched;
+
+ /* Call the DPC directly. */
+ DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __func__));
+ resched = dhdsdio_dpc(bus);
+
+ return resched;
+}
+
+void dhdsdio_isr(void *arg)
+{
+ dhd_bus_t *bus = (dhd_bus_t *) arg;
+ bcmsdh_info_t *sdh;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (!bus) {
+ DHD_ERROR(("%s : bus is null pointer , exit\n", __func__));
+ return;
+ }
+ sdh = bus->sdh;
+
+ if (bus->dhd->busstate == DHD_BUS_DOWN) {
+ DHD_ERROR(("%s : bus is down. we have nothing to do\n",
+ __func__));
+ return;
+ }
+ /* Count the interrupt call */
+ bus->intrcount++;
+ bus->ipend = true;
+
+ /* Shouldn't get this interrupt if we're sleeping? */
+ if (bus->sleeping) {
+ DHD_ERROR(("INTERRUPT WHILE SLEEPING??\n"));
+ return;
+ }
+
+ /* Disable additional interrupts (is this needed now)? */
+ if (bus->intr)
+ DHD_INTR(("%s: disable SDIO interrupts\n", __func__));
+ else
+ DHD_ERROR(("dhdsdio_isr() w/o interrupt configured!\n"));
+
+ bcmsdh_intr_disable(sdh);
+ bus->intdis = true;
+
+#if defined(SDIO_ISR_THREAD)
+ DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __func__));
+ while (dhdsdio_dpc(bus))
+ ;
+#else
+ bus->dpc_sched = true;
+ dhd_sched_dpc(bus->dhd);
+#endif
+
+}
+
+#ifdef SDTEST
+static void dhdsdio_pktgen_init(dhd_bus_t *bus)
+{
+ /* Default to specified length, or full range */
+ if (dhd_pktgen_len) {
+ bus->pktgen_maxlen = min(dhd_pktgen_len, MAX_PKTGEN_LEN);
+ bus->pktgen_minlen = bus->pktgen_maxlen;
+ } else {
+ bus->pktgen_maxlen = MAX_PKTGEN_LEN;
+ bus->pktgen_minlen = 0;
+ }
+ bus->pktgen_len = (u16) bus->pktgen_minlen;
+
+ /* Default to per-watchdog burst with 10s print time */
+ bus->pktgen_freq = 1;
+ bus->pktgen_print = 10000 / dhd_watchdog_ms;
+ bus->pktgen_count = (dhd_pktgen * dhd_watchdog_ms + 999) / 1000;
+
+ /* Default to echo mode */
+ bus->pktgen_mode = DHD_PKTGEN_ECHO;
+ bus->pktgen_stop = 1;
+}
+
+static void dhdsdio_pktgen(dhd_bus_t *bus)
+{
+ void *pkt;
+ u8 *data;
+ uint pktcount;
+ uint fillbyte;
+ osl_t *osh = bus->dhd->osh;
+ u16 len;
+
+ /* Display current count if appropriate */
+ if (bus->pktgen_print && (++bus->pktgen_ptick >= bus->pktgen_print)) {
+ bus->pktgen_ptick = 0;
+ printf("%s: send attempts %d rcvd %d\n",
+ __func__, bus->pktgen_sent, bus->pktgen_rcvd);
+ }
+
+ /* For recv mode, just make sure dongle has started sending */
+ if (bus->pktgen_mode == DHD_PKTGEN_RECV) {
+ if (!bus->pktgen_rcvd)
+ dhdsdio_sdtest_set(bus, true);
+ return;
+ }
+
+ /* Otherwise, generate or request the specified number of packets */
+ for (pktcount = 0; pktcount < bus->pktgen_count; pktcount++) {
+ /* Stop if total has been reached */
+ if (bus->pktgen_total
+ && (bus->pktgen_sent >= bus->pktgen_total)) {
+ bus->pktgen_count = 0;
+ break;
+ }
+
+ /* Allocate an appropriate-sized packet */
+ len = bus->pktgen_len;
+ pkt = PKTGET(osh,
+ (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN),
+ true);
+ if (!pkt) {
+ DHD_ERROR(("%s: PKTGET failed!\n", __func__));
+ break;
+ }
+ PKTALIGN(osh, pkt, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN),
+ DHD_SDALIGN);
+ data = (u8 *) PKTDATA(pkt) + SDPCM_HDRLEN;
+
+ /* Write test header cmd and extra based on mode */
+ switch (bus->pktgen_mode) {
+ case DHD_PKTGEN_ECHO:
+ *data++ = SDPCM_TEST_ECHOREQ;
+ *data++ = (u8) bus->pktgen_sent;
+ break;
+
+ case DHD_PKTGEN_SEND:
+ *data++ = SDPCM_TEST_DISCARD;
+ *data++ = (u8) bus->pktgen_sent;
+ break;
+
+ case DHD_PKTGEN_RXBURST:
+ *data++ = SDPCM_TEST_BURST;
+ *data++ = (u8) bus->pktgen_count;
+ break;
+
+ default:
+ DHD_ERROR(("Unrecognized pktgen mode %d\n",
+ bus->pktgen_mode));
+ PKTFREE(osh, pkt, true);
+ bus->pktgen_count = 0;
+ return;
+ }
+
+ /* Write test header length field */
+ *data++ = (len >> 0);
+ *data++ = (len >> 8);
+
+ /* Then fill in the remainder -- N/A for burst,
+ but who cares... */
+ for (fillbyte = 0; fillbyte < len; fillbyte++)
+ *data++ =
+ SDPCM_TEST_FILL(fillbyte, (u8) bus->pktgen_sent);
+
+#ifdef DHD_DEBUG
+ if (DHD_BYTES_ON() && DHD_DATA_ON()) {
+ data = (u8 *) PKTDATA(pkt) + SDPCM_HDRLEN;
+ prhex("dhdsdio_pktgen: Tx Data", data,
+ PKTLEN(pkt) - SDPCM_HDRLEN);
+ }
+#endif
+
+ /* Send it */
+ if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, true)) {
+ bus->pktgen_fail++;
+ if (bus->pktgen_stop
+ && bus->pktgen_stop == bus->pktgen_fail)
+ bus->pktgen_count = 0;
+ }
+ bus->pktgen_sent++;
+
+ /* Bump length if not fixed, wrap at max */
+ if (++bus->pktgen_len > bus->pktgen_maxlen)
+ bus->pktgen_len = (u16) bus->pktgen_minlen;
+
+ /* Special case for burst mode: just send one request! */
+ if (bus->pktgen_mode == DHD_PKTGEN_RXBURST)
+ break;
+ }
+}
+
+static void dhdsdio_sdtest_set(dhd_bus_t *bus, bool start)
+{
+ void *pkt;
+ u8 *data;
+ osl_t *osh = bus->dhd->osh;
+
+ /* Allocate the packet */
+ pkt = PKTGET(osh, SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN,
+ true);
+ if (!pkt) {
+ DHD_ERROR(("%s: PKTGET failed!\n", __func__));
+ return;
+ }
+ PKTALIGN(osh, pkt, (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN), DHD_SDALIGN);
+ data = (u8 *) PKTDATA(pkt) + SDPCM_HDRLEN;
+
+ /* Fill in the test header */
+ *data++ = SDPCM_TEST_SEND;
+ *data++ = start;
+ *data++ = (bus->pktgen_maxlen >> 0);
+ *data++ = (bus->pktgen_maxlen >> 8);
+
+ /* Send it */
+ if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, true))
+ bus->pktgen_fail++;
+}
+
+static void dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq)
+{
+ osl_t *osh = bus->dhd->osh;
+ u8 *data;
+ uint pktlen;
+
+ u8 cmd;
+ u8 extra;
+ u16 len;
+ u16 offset;
+
+ /* Check for min length */
+ pktlen = PKTLEN(pkt);
+ if (pktlen < SDPCM_TEST_HDRLEN) {
+ DHD_ERROR(("dhdsdio_restrcv: toss runt frame, pktlen %d\n",
+ pktlen));
+ PKTFREE(osh, pkt, false);
+ return;
+ }
+
+ /* Extract header fields */
+ data = PKTDATA(pkt);
+ cmd = *data++;
+ extra = *data++;
+ len = *data++;
+ len += *data++ << 8;
+
+ /* Check length for relevant commands */
+ if (cmd == SDPCM_TEST_DISCARD || cmd == SDPCM_TEST_ECHOREQ
+ || cmd == SDPCM_TEST_ECHORSP) {
+ if (pktlen != len + SDPCM_TEST_HDRLEN) {
+ DHD_ERROR(("dhdsdio_testrcv: frame length mismatch, "
+ "pktlen %d seq %d" " cmd %d extra %d len %d\n",
+ pktlen, seq, cmd, extra, len));
+ PKTFREE(osh, pkt, false);
+ return;
+ }
+ }
+
+ /* Process as per command */
+ switch (cmd) {
+ case SDPCM_TEST_ECHOREQ:
+ /* Rx->Tx turnaround ok (even on NDIS w/current
+ implementation) */
+ *(u8 *) (PKTDATA(pkt)) = SDPCM_TEST_ECHORSP;
+ if (dhdsdio_txpkt(bus, pkt, SDPCM_TEST_CHANNEL, true) == 0) {
+ bus->pktgen_sent++;
+ } else {
+ bus->pktgen_fail++;
+ PKTFREE(osh, pkt, false);
+ }
+ bus->pktgen_rcvd++;
+ break;
+
+ case SDPCM_TEST_ECHORSP:
+ if (bus->ext_loop) {
+ PKTFREE(osh, pkt, false);
+ bus->pktgen_rcvd++;
+ break;
+ }
+
+ for (offset = 0; offset < len; offset++, data++) {
+ if (*data != SDPCM_TEST_FILL(offset, extra)) {
+ DHD_ERROR(("dhdsdio_testrcv: echo data mismatch: " "offset %d (len %d) expect 0x%02x rcvd 0x%02x\n",
+ offset, len,
+ SDPCM_TEST_FILL(offset, extra), *data));
+ break;
+ }
+ }
+ PKTFREE(osh, pkt, false);
+ bus->pktgen_rcvd++;
+ break;
+
+ case SDPCM_TEST_DISCARD:
+ PKTFREE(osh, pkt, false);
+ bus->pktgen_rcvd++;
+ break;
+
+ case SDPCM_TEST_BURST:
+ case SDPCM_TEST_SEND:
+ default:
+ DHD_INFO(("dhdsdio_testrcv: unsupported or unknown command, "
+ "pktlen %d seq %d" " cmd %d extra %d len %d\n",
+ pktlen, seq, cmd, extra, len));
+ PKTFREE(osh, pkt, false);
+ break;
+ }
+
+ /* For recv mode, stop at limie (and tell dongle to stop sending) */
+ if (bus->pktgen_mode == DHD_PKTGEN_RECV) {
+ if (bus->pktgen_total
+ && (bus->pktgen_rcvd >= bus->pktgen_total)) {
+ bus->pktgen_count = 0;
+ dhdsdio_sdtest_set(bus, false);
+ }
+ }
+}
+#endif /* SDTEST */
+
+extern bool dhd_bus_watchdog(dhd_pub_t *dhdp)
+{
+ dhd_bus_t *bus;
+
+ DHD_TIMER(("%s: Enter\n", __func__));
+
+ bus = dhdp->bus;
+
+ if (bus->dhd->dongle_reset)
+ return false;
+
+ /* Ignore the timer if simulating bus down */
+ if (bus->sleeping)
+ return false;
+
+ dhd_os_sdlock(bus->dhd);
+
+ /* Poll period: check device if appropriate. */
+ if (bus->poll && (++bus->polltick >= bus->pollrate)) {
+ u32 intstatus = 0;
+
+ /* Reset poll tick */
+ bus->polltick = 0;
+
+ /* Check device if no interrupts */
+ if (!bus->intr || (bus->intrcount == bus->lastintrs)) {
+
+ if (!bus->dpc_sched) {
+ u8 devpend;
+ devpend = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0,
+ SDIOD_CCCR_INTPEND,
+ NULL);
+ intstatus =
+ devpend & (INTR_STATUS_FUNC1 |
+ INTR_STATUS_FUNC2);
+ }
+
+ /* If there is something, make like the ISR and
+ schedule the DPC */
+ if (intstatus) {
+ bus->pollcnt++;
+ bus->ipend = true;
+ if (bus->intr)
+ bcmsdh_intr_disable(bus->sdh);
+
+ bus->dpc_sched = true;
+ dhd_sched_dpc(bus->dhd);
+
+ }
+ }
+
+ /* Update interrupt tracking */
+ bus->lastintrs = bus->intrcount;
+ }
+#ifdef DHD_DEBUG
+ /* Poll for console output periodically */
+ if (dhdp->busstate == DHD_BUS_DATA && dhd_console_ms != 0) {
+ bus->console.count += dhd_watchdog_ms;
+ if (bus->console.count >= dhd_console_ms) {
+ bus->console.count -= dhd_console_ms;
+ /* Make sure backplane clock is on */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+ if (dhdsdio_readconsole(bus) < 0)
+ dhd_console_ms = 0; /* On error,
+ stop trying */
+ }
+ }
+#endif /* DHD_DEBUG */
+
+#ifdef SDTEST
+ /* Generate packets if configured */
+ if (bus->pktgen_count && (++bus->pktgen_tick >= bus->pktgen_freq)) {
+ /* Make sure backplane clock is on */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+ bus->pktgen_tick = 0;
+ dhdsdio_pktgen(bus);
+ }
+#endif
+
+ /* On idle timeout clear activity flag and/or turn off clock */
+ if ((bus->idletime > 0) && (bus->clkstate == CLK_AVAIL)) {
+ if (++bus->idlecount >= bus->idletime) {
+ bus->idlecount = 0;
+ if (bus->activity) {
+ bus->activity = false;
+ dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
+ } else {
+ dhdsdio_clkctl(bus, CLK_NONE, false);
+ }
+ }
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+
+ return bus->ipend;
+}
+
+#ifdef DHD_DEBUG
+extern int dhd_bus_console_in(dhd_pub_t *dhdp, unsigned char *msg, uint msglen)
+{
+ dhd_bus_t *bus = dhdp->bus;
+ u32 addr, val;
+ int rv;
+ void *pkt;
+
+ /* Address could be zero if CONSOLE := 0 in dongle Makefile */
+ if (bus->console_addr == 0)
+ return BCME_UNSUPPORTED;
+
+ /* Exclusive bus access */
+ dhd_os_sdlock(bus->dhd);
+
+ /* Don't allow input if dongle is in reset */
+ if (bus->dhd->dongle_reset) {
+ dhd_os_sdunlock(bus->dhd);
+ return BCME_NOTREADY;
+ }
+
+ /* Request clock to allow SDIO accesses */
+ BUS_WAKE(bus);
+ /* No pend allowed since txpkt is called later, ht clk has to be on */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Zero cbuf_index */
+ addr = bus->console_addr + offsetof(hndrte_cons_t, cbuf_idx);
+ val = htol32(0);
+ rv = dhdsdio_membytes(bus, true, addr, (u8 *)&val, sizeof(val));
+ if (rv < 0)
+ goto done;
+
+ /* Write message into cbuf */
+ addr = bus->console_addr + offsetof(hndrte_cons_t, cbuf);
+ rv = dhdsdio_membytes(bus, true, addr, (u8 *)msg, msglen);
+ if (rv < 0)
+ goto done;
+
+ /* Write length into vcons_in */
+ addr = bus->console_addr + offsetof(hndrte_cons_t, vcons_in);
+ val = htol32(msglen);
+ rv = dhdsdio_membytes(bus, true, addr, (u8 *)&val, sizeof(val));
+ if (rv < 0)
+ goto done;
+
+ /* Bump dongle by sending an empty event pkt.
+ * sdpcm_sendup (RX) checks for virtual console input.
+ */
+ pkt = PKTGET(bus->dhd->osh, 4 + SDPCM_RESERVE, true);
+ if ((pkt != NULL) && bus->clkstate == CLK_AVAIL)
+ dhdsdio_txpkt(bus, pkt, SDPCM_EVENT_CHANNEL, true);
+
+done:
+ if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched) {
+ bus->activity = false;
+ dhdsdio_clkctl(bus, CLK_NONE, true);
+ }
+
+ dhd_os_sdunlock(bus->dhd);
+
+ return rv;
+}
+#endif /* DHD_DEBUG */
+
+#ifdef DHD_DEBUG
+static void dhd_dump_cis(uint fn, u8 *cis)
+{
+ uint byte, tag, tdata;
+ DHD_INFO(("Function %d CIS:\n", fn));
+
+ for (tdata = byte = 0; byte < SBSDIO_CIS_SIZE_LIMIT; byte++) {
+ if ((byte % 16) == 0)
+ DHD_INFO((" "));
+ DHD_INFO(("%02x ", cis[byte]));
+ if ((byte % 16) == 15)
+ DHD_INFO(("\n"));
+ if (!tdata--) {
+ tag = cis[byte];
+ if (tag == 0xff)
+ break;
+ else if (!tag)
+ tdata = 0;
+ else if ((byte + 1) < SBSDIO_CIS_SIZE_LIMIT)
+ tdata = cis[byte + 1] + 1;
+ else
+ DHD_INFO(("]"));
+ }
+ }
+ if ((byte % 16) != 15)
+ DHD_INFO(("\n"));
+}
+#endif /* DHD_DEBUG */
+
+static bool dhdsdio_chipmatch(u16 chipid)
+{
+ if (chipid == BCM4325_CHIP_ID)
+ return true;
+ if (chipid == BCM4329_CHIP_ID)
+ return true;
+ if (chipid == BCM4319_CHIP_ID)
+ return true;
+ return false;
+}
+
+static void *dhdsdio_probe(u16 venid, u16 devid, u16 bus_no,
+ u16 slot, u16 func, uint bustype, void *regsva,
+ osl_t *osh, void *sdh)
+{
+ int ret;
+ dhd_bus_t *bus;
+
+ /* Init global variables at run-time, not as part of the declaration.
+ * This is required to support init/de-init of the driver.
+ * Initialization
+ * of globals as part of the declaration results in non-deterministic
+ * behavior since the value of the globals may be different on the
+ * first time that the driver is initialized vs subsequent
+ * initializations.
+ */
+ dhd_txbound = DHD_TXBOUND;
+ dhd_rxbound = DHD_RXBOUND;
+ dhd_alignctl = true;
+ sd1idle = true;
+ dhd_readahead = true;
+ retrydata = false;
+ dhd_doflow = false;
+ dhd_dongle_memsize = 0;
+ dhd_txminmax = DHD_TXMINMAX;
+
+ forcealign = true;
+
+ dhd_common_init();
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+ DHD_INFO(("%s: venid 0x%04x devid 0x%04x\n", __func__, venid, devid));
+
+ /* We make assumptions about address window mappings */
+ ASSERT((unsigned long)regsva == SI_ENUM_BASE);
+
+ /* BCMSDH passes venid and devid based on CIS parsing -- but
+ * low-power start
+ * means early parse could fail, so here we should get either an ID
+ * we recognize OR (-1) indicating we must request power first.
+ */
+ /* Check the Vendor ID */
+ switch (venid) {
+ case 0x0000:
+ case VENDOR_BROADCOM:
+ break;
+ default:
+ DHD_ERROR(("%s: unknown vendor: 0x%04x\n", __func__, venid));
+ return NULL;
+ }
+
+ /* Check the Device ID and make sure it's one that we support */
+ switch (devid) {
+ case BCM4325_D11DUAL_ID: /* 4325 802.11a/g id */
+ case BCM4325_D11G_ID: /* 4325 802.11g 2.4Ghz band id */
+ case BCM4325_D11A_ID: /* 4325 802.11a 5Ghz band id */
+ DHD_INFO(("%s: found 4325 Dongle\n", __func__));
+ break;
+ case BCM4329_D11NDUAL_ID: /* 4329 802.11n dualband device */
+ case BCM4329_D11N2G_ID: /* 4329 802.11n 2.4G device */
+ case BCM4329_D11N5G_ID: /* 4329 802.11n 5G device */
+ case 0x4329:
+ DHD_INFO(("%s: found 4329 Dongle\n", __func__));
+ break;
+ case BCM4319_D11N_ID: /* 4319 802.11n id */
+ case BCM4319_D11N2G_ID: /* 4319 802.11n2g id */
+ case BCM4319_D11N5G_ID: /* 4319 802.11n5g id */
+ DHD_INFO(("%s: found 4319 Dongle\n", __func__));
+ break;
+ case 0:
+ DHD_INFO(("%s: allow device id 0, will check chip internals\n",
+ __func__));
+ break;
+
+ default:
+ DHD_ERROR(("%s: skipping 0x%04x/0x%04x, not a dongle\n",
+ __func__, venid, devid));
+ return NULL;
+ }
+
+ if (osh == NULL) {
+ /* Ask the OS interface part for an OSL handle */
+ osh = dhd_osl_attach(sdh, DHD_BUS);
+ if (!osh) {
+ DHD_ERROR(("%s: osl_attach failed!\n", __func__));
+ return NULL;
+ }
+ }
+
+ /* Allocate private bus interface state */
+ bus = kzalloc(sizeof(dhd_bus_t), GFP_ATOMIC);
+ if (!bus) {
+ DHD_ERROR(("%s: kmalloc of dhd_bus_t failed\n", __func__));
+ goto fail;
+ }
+ bus->sdh = sdh;
+ bus->cl_devid = (u16) devid;
+ bus->bus = DHD_BUS;
+ bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1;
+ bus->usebufpool = false; /* Use bufpool if allocated,
+ else use locally malloced rxbuf */
+
+ /* attempt to attach to the dongle */
+ if (!(dhdsdio_probe_attach(bus, osh, sdh, regsva, devid))) {
+ DHD_ERROR(("%s: dhdsdio_probe_attach failed\n", __func__));
+ goto fail;
+ }
+
+ /* Attach to the dhd/OS/network interface */
+ bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE);
+ if (!bus->dhd) {
+ DHD_ERROR(("%s: dhd_attach failed\n", __func__));
+ goto fail;
+ }
+
+ /* Allocate buffers */
+ if (!(dhdsdio_probe_malloc(bus, osh, sdh))) {
+ DHD_ERROR(("%s: dhdsdio_probe_malloc failed\n", __func__));
+ goto fail;
+ }
+
+ if (!(dhdsdio_probe_init(bus, osh, sdh))) {
+ DHD_ERROR(("%s: dhdsdio_probe_init failed\n", __func__));
+ goto fail;
+ }
+
+ /* Register interrupt callback, but mask it (not operational yet). */
+ DHD_INTR(("%s: disable SDIO interrupts (not interested yet)\n",
+ __func__));
+ bcmsdh_intr_disable(sdh);
+ ret = bcmsdh_intr_reg(sdh, dhdsdio_isr, bus);
+ if (ret != 0) {
+ DHD_ERROR(("%s: FAILED: bcmsdh_intr_reg returned %d\n",
+ __func__, ret));
+ goto fail;
+ }
+ DHD_INTR(("%s: registered SDIO interrupt function ok\n", __func__));
+
+ DHD_INFO(("%s: completed!!\n", __func__));
+
+ /* if firmware path present try to download and bring up bus */
+ ret = dhd_bus_start(bus->dhd);
+ if (ret != 0) {
+ if (ret == BCME_NOTUP) {
+ DHD_ERROR(("%s: dongle is not responding\n", __func__));
+ goto fail;
+ }
+ }
+ /* Ok, have the per-port tell the stack we're open for business */
+ if (dhd_net_attach(bus->dhd, 0) != 0) {
+ DHD_ERROR(("%s: Net attach failed!!\n", __func__));
+ goto fail;
+ }
+
+ return bus;
+
+fail:
+ dhdsdio_release(bus, osh);
+ return NULL;
+}
+
+static bool
+dhdsdio_probe_attach(struct dhd_bus *bus, osl_t *osh, void *sdh, void *regsva,
+ u16 devid)
+{
+ u8 clkctl = 0;
+ int err = 0;
+
+ bus->alp_only = true;
+
+ /* Return the window to backplane enumeration space for core access */
+ if (dhdsdio_set_siaddr_window(bus, SI_ENUM_BASE))
+ DHD_ERROR(("%s: FAILED to return to SI_ENUM_BASE\n", __func__));
+
+#ifdef DHD_DEBUG
+ printf("F1 signature read @0x18000000=0x%4x\n",
+ bcmsdh_reg_read(bus->sdh, SI_ENUM_BASE, 4));
+
+#endif /* DHD_DEBUG */
+
+ /* Force PLL off until si_attach() programs PLL control regs */
+
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ DHD_INIT_CLKCTL1, &err);
+ if (!err)
+ clkctl =
+ bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+
+ if (err || ((clkctl & ~SBSDIO_AVBITS) != DHD_INIT_CLKCTL1)) {
+ DHD_ERROR(("dhdsdio_probe: ChipClkCSR access: err %d wrote "
+ "0x%02x read 0x%02x\n",
+ err, DHD_INIT_CLKCTL1, clkctl));
+ goto fail;
+ }
+#ifdef DHD_DEBUG
+ if (DHD_INFO_ON()) {
+ uint fn, numfn;
+ u8 *cis[SDIOD_MAX_IOFUNCS];
+ int err = 0;
+
+ numfn = bcmsdh_query_iofnum(sdh);
+ ASSERT(numfn <= SDIOD_MAX_IOFUNCS);
+
+ /* Make sure ALP is available before trying to read CIS */
+ SPINWAIT(((clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ NULL)),
+ !SBSDIO_ALPAV(clkctl)), PMU_MAX_TRANSITION_DLY);
+
+ /* Now request ALP be put on the bus */
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
+ DHD_INIT_CLKCTL2, &err);
+ udelay(65);
+
+ for (fn = 0; fn <= numfn; fn++) {
+ cis[fn] = kmalloc(SBSDIO_CIS_SIZE_LIMIT, GFP_ATOMIC);
+ if (!cis[fn]) {
+ DHD_INFO(("dhdsdio_probe: fn %d cis malloc "
+ "failed\n", fn));
+ break;
+ }
+ bzero(cis[fn], SBSDIO_CIS_SIZE_LIMIT);
+
+ err = bcmsdh_cis_read(sdh, fn, cis[fn],
+ SBSDIO_CIS_SIZE_LIMIT);
+ if (err) {
+ DHD_INFO(("dhdsdio_probe: fn %d cis read "
+ "err %d\n", fn, err));
+ kfree(cis[fn]);
+ break;
+ }
+ dhd_dump_cis(fn, cis[fn]);
+ }
+
+ while (fn-- > 0) {
+ ASSERT(cis[fn]);
+ kfree(cis[fn]);
+ }
+
+ if (err) {
+ DHD_ERROR(("dhdsdio_probe: error read/parsing CIS\n"));
+ goto fail;
+ }
+ }
+#endif /* DHD_DEBUG */
+
+ /* si_attach() will provide an SI handle and scan the backplane */
+ bus->sih = si_attach((uint) devid, osh, regsva, DHD_BUS, sdh,
+ &bus->vars, &bus->varsz);
+ if (!(bus->sih)) {
+ DHD_ERROR(("%s: si_attach failed!\n", __func__));
+ goto fail;
+ }
+
+ bcmsdh_chipinfo(sdh, bus->sih->chip, bus->sih->chiprev);
+
+ if (!dhdsdio_chipmatch((u16) bus->sih->chip)) {
+ DHD_ERROR(("%s: unsupported chip: 0x%04x\n",
+ __func__, bus->sih->chip));
+ goto fail;
+ }
+
+ si_sdiod_drive_strength_init(bus->sih, osh, dhd_sdiod_drive_strength);
+
+ /* Get info on the ARM and SOCRAM cores... */
+ if (!DHD_NOPMU(bus)) {
+ if ((si_setcore(bus->sih, ARM7S_CORE_ID, 0)) ||
+ (si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) {
+ bus->armrev = si_corerev(bus->sih);
+ } else {
+ DHD_ERROR(("%s: failed to find ARM core!\n", __func__));
+ goto fail;
+ }
+ bus->orig_ramsize = si_socram_size(bus->sih);
+ if (!(bus->orig_ramsize)) {
+ DHD_ERROR(("%s: failed to find SOCRAM memory!\n",
+ __func__));
+ goto fail;
+ }
+ bus->ramsize = bus->orig_ramsize;
+ if (dhd_dongle_memsize)
+ dhd_dongle_setmemsize(bus, dhd_dongle_memsize);
+
+ DHD_ERROR(("DHD: dongle ram size is set to %d(orig %d)\n",
+ bus->ramsize, bus->orig_ramsize));
+ }
+
+ /* ...but normally deal with the SDPCMDEV core */
+ bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0);
+ if (!bus->regs) {
+ bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0);
+ if (!bus->regs) {
+ DHD_ERROR(("%s: failed to find SDIODEV core!\n",
+ __func__));
+ goto fail;
+ }
+ }
+ bus->sdpcmrev = si_corerev(bus->sih);
+
+ /* Set core control so an SDIO reset does a backplane reset */
+ OR_REG(osh, &bus->regs->corecontrol, CC_BPRESEN);
+
+ pktq_init(&bus->txq, (PRIOMASK + 1), QLEN);
+
+ /* Locate an appropriately-aligned portion of hdrbuf */
+ bus->rxhdr = (u8 *) roundup((unsigned long)&bus->hdrbuf[0], DHD_SDALIGN);
+
+ /* Set the poll and/or interrupt flags */
+ bus->intr = (bool) dhd_intr;
+ bus->poll = (bool) dhd_poll;
+ if (bus->poll)
+ bus->pollrate = 1;
+
+ return true;
+
+fail:
+ return false;
+}
+
+static bool dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus->dhd->maxctl) {
+ bus->rxblen =
+ roundup((bus->dhd->maxctl + SDPCM_HDRLEN),
+ ALIGNMENT) + DHD_SDALIGN;
+ bus->rxbuf = kmalloc(bus->rxblen, GFP_ATOMIC);
+ if (!(bus->rxbuf)) {
+ DHD_ERROR(("%s: kmalloc of %d-byte rxbuf failed\n",
+ __func__, bus->rxblen));
+ goto fail;
+ }
+ }
+
+ /* Allocate buffer to receive glomed packet */
+ bus->databuf = kmalloc(MAX_DATA_BUF, GFP_ATOMIC);
+ if (!(bus->databuf)) {
+ DHD_ERROR(("%s: kmalloc of %d-byte databuf failed\n",
+ __func__, MAX_DATA_BUF));
+ /* release rxbuf which was already located as above */
+ if (!bus->rxblen)
+ kfree(bus->rxbuf);
+ goto fail;
+ }
+
+ /* Align the buffer */
+ if ((unsigned long)bus->databuf % DHD_SDALIGN)
+ bus->dataptr =
+ bus->databuf + (DHD_SDALIGN -
+ ((unsigned long)bus->databuf % DHD_SDALIGN));
+ else
+ bus->dataptr = bus->databuf;
+
+ return true;
+
+fail:
+ return false;
+}
+
+static bool dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh)
+{
+ s32 fnum;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+#ifdef SDTEST
+ dhdsdio_pktgen_init(bus);
+#endif /* SDTEST */
+
+ /* Disable F2 to clear any intermediate frame state on the dongle */
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, SDIO_FUNC_ENABLE_1,
+ NULL);
+
+ bus->dhd->busstate = DHD_BUS_DOWN;
+ bus->sleeping = false;
+ bus->rxflow = false;
+ bus->prev_rxlim_hit = 0;
+
+ /* Done with backplane-dependent accesses, can drop clock... */
+ bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL);
+
+ /* ...and initialize clock/power states */
+ bus->clkstate = CLK_SDONLY;
+ bus->idletime = (s32) dhd_idletime;
+ bus->idleclock = DHD_IDLE_ACTIVE;
+
+ /* Query the SD clock speed */
+ if (bcmsdh_iovar_op(sdh, "sd_divisor", NULL, 0,
+ &bus->sd_divisor, sizeof(s32),
+ false) != BCME_OK) {
+ DHD_ERROR(("%s: fail on %s get\n", __func__, "sd_divisor"));
+ bus->sd_divisor = -1;
+ } else {
+ DHD_INFO(("%s: Initial value for %s is %d\n",
+ __func__, "sd_divisor", bus->sd_divisor));
+ }
+
+ /* Query the SD bus mode */
+ if (bcmsdh_iovar_op(sdh, "sd_mode", NULL, 0,
+ &bus->sd_mode, sizeof(s32), false) != BCME_OK) {
+ DHD_ERROR(("%s: fail on %s get\n", __func__, "sd_mode"));
+ bus->sd_mode = -1;
+ } else {
+ DHD_INFO(("%s: Initial value for %s is %d\n",
+ __func__, "sd_mode", bus->sd_mode));
+ }
+
+ /* Query the F2 block size, set roundup accordingly */
+ fnum = 2;
+ if (bcmsdh_iovar_op(sdh, "sd_blocksize", &fnum, sizeof(s32),
+ &bus->blocksize, sizeof(s32), false) != BCME_OK) {
+ bus->blocksize = 0;
+ DHD_ERROR(("%s: fail on %s get\n", __func__, "sd_blocksize"));
+ } else {
+ DHD_INFO(("%s: Initial value for %s is %d\n",
+ __func__, "sd_blocksize", bus->blocksize));
+ }
+ bus->roundup = min(max_roundup, bus->blocksize);
+
+ /* Query if bus module supports packet chaining,
+ default to use if supported */
+ if (bcmsdh_iovar_op(sdh, "sd_rxchain", NULL, 0,
+ &bus->sd_rxchain, sizeof(s32),
+ false) != BCME_OK) {
+ bus->sd_rxchain = false;
+ } else {
+ DHD_INFO(("%s: bus module (through bcmsdh API) %s chaining\n",
+ __func__,
+ (bus->sd_rxchain ? "supports" : "does not support")));
+ }
+ bus->use_rxchain = (bool) bus->sd_rxchain;
+
+ return true;
+}
+
+bool
+dhd_bus_download_firmware(struct dhd_bus *bus, osl_t *osh,
+ char *fw_path, char *nv_path)
+{
+ bool ret;
+ bus->fw_path = fw_path;
+ bus->nv_path = nv_path;
+
+ ret = dhdsdio_download_firmware(bus, osh, bus->sdh);
+
+ return ret;
+}
+
+static bool
+dhdsdio_download_firmware(struct dhd_bus *bus, osl_t *osh, void *sdh)
+{
+ bool ret;
+
+ /* Download the firmware */
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+
+ ret = _dhdsdio_download_firmware(bus) == 0;
+
+ dhdsdio_clkctl(bus, CLK_SDONLY, false);
+
+ return ret;
+}
+
+/* Detach and free everything */
+static void dhdsdio_release(dhd_bus_t *bus, osl_t *osh)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus) {
+ ASSERT(osh);
+
+ /* De-register interrupt handler */
+ bcmsdh_intr_disable(bus->sdh);
+ bcmsdh_intr_dereg(bus->sdh);
+
+ if (bus->dhd) {
+
+ dhdsdio_release_dongle(bus, osh);
+
+ dhd_detach(bus->dhd);
+ bus->dhd = NULL;
+ }
+
+ dhdsdio_release_malloc(bus, osh);
+
+ kfree(bus);
+ }
+
+ if (osh)
+ dhd_osl_detach(osh);
+
+ DHD_TRACE(("%s: Disconnected\n", __func__));
+}
+
+static void dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus->dhd && bus->dhd->dongle_reset)
+ return;
+
+ if (bus->rxbuf) {
+ kfree(bus->rxbuf);
+ bus->rxctl = bus->rxbuf = NULL;
+ bus->rxlen = 0;
+ }
+
+ if (bus->databuf) {
+ kfree(bus->databuf);
+ bus->databuf = NULL;
+ }
+}
+
+static void dhdsdio_release_dongle(dhd_bus_t *bus, osl_t *osh)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus->dhd && bus->dhd->dongle_reset)
+ return;
+
+ if (bus->sih) {
+ dhdsdio_clkctl(bus, CLK_AVAIL, false);
+#if !defined(BCMLXSDMMC)
+ si_watchdog(bus->sih, 4);
+#endif /* !defined(BCMLXSDMMC) */
+ dhdsdio_clkctl(bus, CLK_NONE, false);
+ si_detach(bus->sih);
+ if (bus->vars && bus->varsz)
+ kfree(bus->vars);
+ bus->vars = NULL;
+ }
+
+ DHD_TRACE(("%s: Disconnected\n", __func__));
+}
+
+static void dhdsdio_disconnect(void *ptr)
+{
+ dhd_bus_t *bus = (dhd_bus_t *)ptr;
+
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ if (bus) {
+ ASSERT(bus->dhd);
+ dhdsdio_release(bus, bus->dhd->osh);
+ }
+
+ DHD_TRACE(("%s: Disconnected\n", __func__));
+}
+
+/* Register/Unregister functions are called by the main DHD entry
+ * point (e.g. module insertion) to link with the bus driver, in
+ * order to look for or await the device.
+ */
+
+static bcmsdh_driver_t dhd_sdio = {
+ dhdsdio_probe,
+ dhdsdio_disconnect
+};
+
+int dhd_bus_register(void)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ return bcmsdh_register(&dhd_sdio);
+}
+
+void dhd_bus_unregister(void)
+{
+ DHD_TRACE(("%s: Enter\n", __func__));
+
+ bcmsdh_unregister();
+}
+
+#ifdef BCMEMBEDIMAGE
+static int dhdsdio_download_code_array(struct dhd_bus *bus)
+{
+ int bcmerror = -1;
+ int offset = 0;
+
+ DHD_INFO(("%s: download embedded firmware...\n", __func__));
+
+ /* Download image */
+ while ((offset + MEMBLOCK) < sizeof(dlarray)) {
+ bcmerror =
+ dhdsdio_membytes(bus, true, offset, dlarray + offset,
+ MEMBLOCK);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on writing %d membytes at "
+ "0x%08x\n",
+ __func__, bcmerror, MEMBLOCK, offset));
+ goto err;
+ }
+
+ offset += MEMBLOCK;
+ }
+
+ if (offset < sizeof(dlarray)) {
+ bcmerror = dhdsdio_membytes(bus, true, offset,
+ dlarray + offset,
+ sizeof(dlarray) - offset);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on writing %d membytes at "
+ "0x%08x\n", __func__, bcmerror,
+ sizeof(dlarray) - offset, offset));
+ goto err;
+ }
+ }
+#ifdef DHD_DEBUG
+ /* Upload and compare the downloaded code */
+ {
+ unsigned char *ularray;
+
+ ularray = kmalloc(bus->ramsize, GFP_ATOMIC);
+ /* Upload image to verify downloaded contents. */
+ offset = 0;
+ memset(ularray, 0xaa, bus->ramsize);
+ while ((offset + MEMBLOCK) < sizeof(dlarray)) {
+ bcmerror =
+ dhdsdio_membytes(bus, false, offset,
+ ularray + offset, MEMBLOCK);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on reading %d membytes"
+ " at 0x%08x\n",
+ __func__, bcmerror, MEMBLOCK, offset));
+ goto err;
+ }
+
+ offset += MEMBLOCK;
+ }
+
+ if (offset < sizeof(dlarray)) {
+ bcmerror = dhdsdio_membytes(bus, false, offset,
+ ularray + offset,
+ sizeof(dlarray) - offset);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on reading %d membytes at 0x%08x\n",
+ __func__, bcmerror,
+ sizeof(dlarray) - offset, offset));
+ goto err;
+ }
+ }
+
+ if (memcmp(dlarray, ularray, sizeof(dlarray))) {
+ DHD_ERROR(("%s: Downloaded image is corrupted.\n",
+ __func__));
+ ASSERT(0);
+ goto err;
+ } else
+ DHD_ERROR(("%s: Download/Upload/Compare succeeded.\n",
+ __func__));
+
+ kfree(ularray);
+ }
+#endif /* DHD_DEBUG */
+
+err:
+ return bcmerror;
+}
+#endif /* BCMEMBEDIMAGE */
+
+static int dhdsdio_download_code_file(struct dhd_bus *bus, char *fw_path)
+{
+ int bcmerror = -1;
+ int offset = 0;
+ uint len;
+ void *image = NULL;
+ u8 *memblock = NULL, *memptr;
+
+ DHD_INFO(("%s: download firmware %s\n", __func__, fw_path));
+
+ image = dhd_os_open_image(fw_path);
+ if (image == NULL)
+ goto err;
+
+ memptr = memblock = kmalloc(MEMBLOCK + DHD_SDALIGN, GFP_ATOMIC);
+ if (memblock == NULL) {
+ DHD_ERROR(("%s: Failed to allocate memory %d bytes\n",
+ __func__, MEMBLOCK));
+ goto err;
+ }
+ if ((u32)(unsigned long)memblock % DHD_SDALIGN)
+ memptr +=
+ (DHD_SDALIGN - ((u32)(unsigned long)memblock % DHD_SDALIGN));
+
+ /* Download image */
+ while ((len =
+ dhd_os_get_image_block((char *)memptr, MEMBLOCK, image))) {
+ bcmerror = dhdsdio_membytes(bus, true, offset, memptr, len);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error %d on writing %d membytes at "
+ "0x%08x\n", __func__, bcmerror, MEMBLOCK, offset));
+ goto err;
+ }
+
+ offset += MEMBLOCK;
+ }
+
+err:
+ if (memblock)
+ kfree(memblock);
+
+ if (image)
+ dhd_os_close_image(image);
+
+ return bcmerror;
+}
+
+/*
+ * ProcessVars:Takes a buffer of "<var>=<value>\n" lines read from a file
+ * and ending in a NUL.
+ * Removes carriage returns, empty lines, comment lines, and converts
+ * newlines to NULs.
+ * Shortens buffer as needed and pads with NULs. End of buffer is marked
+ * by two NULs.
+*/
+
+static uint process_nvram_vars(char *varbuf, uint len)
+{
+ char *dp;
+ bool findNewline;
+ int column;
+ uint buf_len, n;
+
+ dp = varbuf;
+
+ findNewline = false;
+ column = 0;
+
+ for (n = 0; n < len; n++) {
+ if (varbuf[n] == 0)
+ break;
+ if (varbuf[n] == '\r')
+ continue;
+ if (findNewline && varbuf[n] != '\n')
+ continue;
+ findNewline = false;
+ if (varbuf[n] == '#') {
+ findNewline = true;
+ continue;
+ }
+ if (varbuf[n] == '\n') {
+ if (column == 0)
+ continue;
+ *dp++ = 0;
+ column = 0;
+ continue;
+ }
+ *dp++ = varbuf[n];
+ column++;
+ }
+ buf_len = dp - varbuf;
+
+ while (dp < varbuf + n)
+ *dp++ = 0;
+
+ return buf_len;
+}
+
+/*
+ EXAMPLE: nvram_array
+ nvram_arry format:
+ name=value
+ Use carriage return at the end of each assignment,
+ and an empty string with
+ carriage return at the end of array.
+
+ For example:
+ unsigned char nvram_array[] = {"name1=value1\n",
+ "name2=value2\n", "\n"};
+ Hex values start with 0x, and mac addr format: xx:xx:xx:xx:xx:xx.
+
+ Search "EXAMPLE: nvram_array" to see how the array is activated.
+*/
+
+void dhd_bus_set_nvram_params(struct dhd_bus *bus, const char *nvram_params)
+{
+ bus->nvram_params = nvram_params;
+}
+
+static int dhdsdio_download_nvram(struct dhd_bus *bus)
+{
+ int bcmerror = -1;
+ uint len;
+ void *image = NULL;
+ char *memblock = NULL;
+ char *bufp;
+ char *nv_path;
+ bool nvram_file_exists;
+
+ nv_path = bus->nv_path;
+
+ nvram_file_exists = ((nv_path != NULL) && (nv_path[0] != '\0'));
+ if (!nvram_file_exists && (bus->nvram_params == NULL))
+ return 0;
+
+ if (nvram_file_exists) {
+ image = dhd_os_open_image(nv_path);
+ if (image == NULL)
+ goto err;
+ }
+
+ memblock = kmalloc(MEMBLOCK, GFP_ATOMIC);
+ if (memblock == NULL) {
+ DHD_ERROR(("%s: Failed to allocate memory %d bytes\n",
+ __func__, MEMBLOCK));
+ goto err;
+ }
+
+ /* Download variables */
+ if (nvram_file_exists) {
+ len = dhd_os_get_image_block(memblock, MEMBLOCK, image);
+ } else {
+ len = strlen(bus->nvram_params);
+ ASSERT(len <= MEMBLOCK);
+ if (len > MEMBLOCK)
+ len = MEMBLOCK;
+ memcpy(memblock, bus->nvram_params, len);
+ }
+
+ if (len > 0 && len < MEMBLOCK) {
+ bufp = (char *)memblock;
+ bufp[len] = 0;
+ len = process_nvram_vars(bufp, len);
+ bufp += len;
+ *bufp++ = 0;
+ if (len)
+ bcmerror = dhdsdio_downloadvars(bus, memblock, len + 1);
+ if (bcmerror) {
+ DHD_ERROR(("%s: error downloading vars: %d\n",
+ __func__, bcmerror));
+ }
+ } else {
+ DHD_ERROR(("%s: error reading nvram file: %d\n",
+ __func__, len));
+ bcmerror = BCME_SDIO_ERROR;
+ }
+
+err:
+ if (memblock)
+ kfree(memblock);
+
+ if (image)
+ dhd_os_close_image(image);
+
+ return bcmerror;
+}
+
+static int _dhdsdio_download_firmware(struct dhd_bus *bus)
+{
+ int bcmerror = -1;
+
+ bool embed = false; /* download embedded firmware */
+ bool dlok = false; /* download firmware succeeded */
+
+ /* Out immediately if no image to download */
+ if ((bus->fw_path == NULL) || (bus->fw_path[0] == '\0')) {
+#ifdef BCMEMBEDIMAGE
+ embed = true;
+#else
+ return bcmerror;
+#endif
+ }
+
+ /* Keep arm in reset */
+ if (dhdsdio_download_state(bus, true)) {
+ DHD_ERROR(("%s: error placing ARM core in reset\n", __func__));
+ goto err;
+ }
+
+ /* External image takes precedence if specified */
+ if ((bus->fw_path != NULL) && (bus->fw_path[0] != '\0')) {
+ if (dhdsdio_download_code_file(bus, bus->fw_path)) {
+ DHD_ERROR(("%s: dongle image file download failed\n",
+ __func__));
+#ifdef BCMEMBEDIMAGE
+ embed = true;
+#else
+ goto err;
+#endif
+ } else {
+ embed = false;
+ dlok = true;
+ }
+ }
+#ifdef BCMEMBEDIMAGE
+ if (embed) {
+ if (dhdsdio_download_code_array(bus)) {
+ DHD_ERROR(("%s: dongle image array download failed\n",
+ __func__));
+ goto err;
+ } else {
+ dlok = true;
+ }
+ }
+#endif
+ if (!dlok) {
+ DHD_ERROR(("%s: dongle image download failed\n", __func__));
+ goto err;
+ }
+
+ /* EXAMPLE: nvram_array */
+ /* If a valid nvram_arry is specified as above, it can be passed
+ down to dongle */
+ /* dhd_bus_set_nvram_params(bus, (char *)&nvram_array); */
+
+ /* External nvram takes precedence if specified */
+ if (dhdsdio_download_nvram(bus)) {
+ DHD_ERROR(("%s: dongle nvram file download failed\n",
+ __func__));
+ }
+
+ /* Take arm out of reset */
+ if (dhdsdio_download_state(bus, false)) {
+ DHD_ERROR(("%s: error getting out of ARM core reset\n",
+ __func__));
+ goto err;
+ }
+
+ bcmerror = 0;
+
+err:
+ return bcmerror;
+}
+
+static int
+dhd_bcmsdh_recv_buf(dhd_bus_t *bus, u32 addr, uint fn, uint flags,
+ u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle)
+{
+ int status;
+
+ /* 4329: GSPI check */
+ status =
+ bcmsdh_recv_buf(bus->sdh, addr, fn, flags, buf, nbytes, pkt,
+ complete, handle);
+ return status;
+}
+
+static int
+dhd_bcmsdh_send_buf(dhd_bus_t *bus, u32 addr, uint fn, uint flags,
+ u8 *buf, uint nbytes, void *pkt,
+ bcmsdh_cmplt_fn_t complete, void *handle)
+{
+ return bcmsdh_send_buf
+ (bus->sdh, addr, fn, flags, buf, nbytes, pkt, complete,
+ handle);
+}
+
+uint dhd_bus_chip(struct dhd_bus *bus)
+{
+ ASSERT(bus->sih != NULL);
+ return bus->sih->chip;
+}
+
+void *dhd_bus_pub(struct dhd_bus *bus)
+{
+ return bus->dhd;
+}
+
+void *dhd_bus_txq(struct dhd_bus *bus)
+{
+ return &bus->txq;
+}
+
+uint dhd_bus_hdrlen(struct dhd_bus *bus)
+{
+ return SDPCM_HDRLEN;
+}
+
+int dhd_bus_devreset(dhd_pub_t *dhdp, u8 flag)
+{
+ int bcmerror = 0;
+ dhd_bus_t *bus;
+
+ bus = dhdp->bus;
+
+ if (flag == true) {
+ if (!bus->dhd->dongle_reset) {
+ /* Expect app to have torn down any
+ connection before calling */
+ /* Stop the bus, disable F2 */
+ dhd_bus_stop(bus, false);
+
+ /* Clean tx/rx buffer pointers,
+ detach from the dongle */
+ dhdsdio_release_dongle(bus, bus->dhd->osh);
+
+ bus->dhd->dongle_reset = true;
+ bus->dhd->up = false;
+
+ DHD_TRACE(("%s: WLAN OFF DONE\n", __func__));
+ /* App can now remove power from device */
+ } else
+ bcmerror = BCME_SDIO_ERROR;
+ } else {
+ /* App must have restored power to device before calling */
+
+ DHD_TRACE(("\n\n%s: == WLAN ON ==\n", __func__));
+
+ if (bus->dhd->dongle_reset) {
+ /* Turn on WLAN */
+ /* Reset SD client */
+ bcmsdh_reset(bus->sdh);
+
+ /* Attempt to re-attach & download */
+ if (dhdsdio_probe_attach(bus, bus->dhd->osh, bus->sdh,
+ (u32 *) SI_ENUM_BASE,
+ bus->cl_devid)) {
+ /* Attempt to download binary to the dongle */
+ if (dhdsdio_probe_init
+ (bus, bus->dhd->osh, bus->sdh)
+ && dhdsdio_download_firmware(bus,
+ bus->dhd->osh,
+ bus->sdh)) {
+
+ /* Re-init bus, enable F2 transfer */
+ dhd_bus_init((dhd_pub_t *) bus->dhd,
+ false);
+
+#if defined(OOB_INTR_ONLY)
+ dhd_enable_oob_intr(bus, true);
+#endif /* defined(OOB_INTR_ONLY) */
+
+ bus->dhd->dongle_reset = false;
+ bus->dhd->up = true;
+
+ DHD_TRACE(("%s: WLAN ON DONE\n",
+ __func__));
+ } else
+ bcmerror = BCME_SDIO_ERROR;
+ } else
+ bcmerror = BCME_SDIO_ERROR;
+ } else {
+ bcmerror = BCME_NOTDOWN;
+ DHD_ERROR(("%s: Set DEVRESET=false invoked when device "
+ "is on\n", __func__));
+ bcmerror = BCME_SDIO_ERROR;
+ }
+ }
+ return bcmerror;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/dngl_stats.h b/drivers/staging/brcm80211/brcmfmac/dngl_stats.h
new file mode 100644
index 000000000000..699cbffa9c4b
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/dngl_stats.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _dngl_stats_h_
+#define _dngl_stats_h_
+
+typedef struct {
+ unsigned long rx_packets; /* total packets received */
+ unsigned long tx_packets; /* total packets transmitted */
+ unsigned long rx_bytes; /* total bytes received */
+ unsigned long tx_bytes; /* total bytes transmitted */
+ unsigned long rx_errors; /* bad packets received */
+ unsigned long tx_errors; /* packet transmit problems */
+ unsigned long rx_dropped; /* packets dropped by dongle */
+ unsigned long tx_dropped; /* packets dropped by dongle */
+ unsigned long multicast; /* multicast packets received */
+} dngl_stats_t;
+
+#endif /* _dngl_stats_h_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c
new file mode 100644
index 000000000000..3f29488d9c72
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c
@@ -0,0 +1,4229 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_arp.h>
+#include <linuxver.h>
+#include <osl.h>
+
+#include <bcmutils.h>
+#include <bcmendian.h>
+#include <proto/ethernet.h>
+
+#include <asm/uaccess.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhdioctl.h>
+#include <wlioctl.h>
+
+#include <proto/ethernet.h>
+#include <dngl_stats.h>
+#include <dhd.h>
+
+#include <linux/kthread.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/etherdevice.h>
+#include <linux/wireless.h>
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+
+#include <net/rtnetlink.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/firmware.h>
+#include <wl_cfg80211.h>
+
+static struct sdio_func *cfg80211_sdio_func;
+static struct wl_dev *wl_cfg80211_dev;
+
+u32 wl_dbg_level = WL_DBG_ERR | WL_DBG_INFO;
+
+#define WL_4329_FW_FILE "brcm/bcm4329-fullmac-4.bin"
+#define WL_4329_NVRAM_FILE "brcm/bcm4329-fullmac-4.txt"
+
+/*
+** cfg80211_ops api/callback list
+*/
+static s32 wl_cfg80211_change_iface(struct wiphy *wiphy,
+ struct net_device *ndev,
+ enum nl80211_iftype type, u32 *flags,
+ struct vif_params *params);
+static s32 __wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_scan_request *request,
+ struct cfg80211_ssid *this_ssid);
+static s32 wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_scan_request *request);
+static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed);
+static s32 wl_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ibss_params *params);
+static s32 wl_cfg80211_leave_ibss(struct wiphy *wiphy,
+ struct net_device *dev);
+static s32 wl_cfg80211_get_station(struct wiphy *wiphy,
+ struct net_device *dev, u8 *mac,
+ struct station_info *sinfo);
+static s32 wl_cfg80211_set_power_mgmt(struct wiphy *wiphy,
+ struct net_device *dev, bool enabled,
+ s32 timeout);
+static s32 wl_cfg80211_set_bitrate_mask(struct wiphy *wiphy,
+ struct net_device *dev,
+ const u8 *addr,
+ const struct cfg80211_bitrate_mask
+ *mask);
+static int wl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code);
+static s32 wl_cfg80211_set_tx_power(struct wiphy *wiphy,
+ enum nl80211_tx_power_setting type,
+ s32 dbm);
+static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, s32 *dbm);
+static s32 wl_cfg80211_config_default_key(struct wiphy *wiphy,
+ struct net_device *dev,
+ u8 key_idx);
+static s32 wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr,
+ struct key_params *params);
+static s32 wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr);
+static s32 wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr,
+ void *cookie, void (*callback) (void *cookie,
+ struct
+ key_params *
+ params));
+static s32 wl_cfg80211_config_default_mgmt_key(struct wiphy *wiphy,
+ struct net_device *dev,
+ u8 key_idx);
+static s32 wl_cfg80211_resume(struct wiphy *wiphy);
+static s32 wl_cfg80211_suspend(struct wiphy *wiphy);
+static s32 wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_pmksa *pmksa);
+static s32 wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_pmksa *pmksa);
+static s32 wl_cfg80211_flush_pmksa(struct wiphy *wiphy,
+ struct net_device *dev);
+/*
+** event & event Q handlers for cfg80211 interfaces
+*/
+static s32 wl_create_event_handler(struct wl_priv *wl);
+static void wl_destroy_event_handler(struct wl_priv *wl);
+static s32 wl_event_handler(void *data);
+static void wl_init_eq(struct wl_priv *wl);
+static void wl_flush_eq(struct wl_priv *wl);
+static void wl_lock_eq(struct wl_priv *wl);
+static void wl_unlock_eq(struct wl_priv *wl);
+static void wl_init_eq_lock(struct wl_priv *wl);
+static void wl_init_eloop_handler(struct wl_event_loop *el);
+static struct wl_event_q *wl_deq_event(struct wl_priv *wl);
+static s32 wl_enq_event(struct wl_priv *wl, u32 type,
+ const wl_event_msg_t *msg, void *data);
+static void wl_put_event(struct wl_event_q *e);
+static void wl_wakeup_event(struct wl_priv *wl);
+static s32 wl_notify_connect_status(struct wl_priv *wl,
+ struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+static s32 wl_notify_roaming_status(struct wl_priv *wl,
+ struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+static s32 wl_notify_scan_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+static s32 wl_bss_connect_done(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data,
+ bool completed);
+static s32 wl_bss_roaming_done(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+static s32 wl_notify_mic_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+
+/*
+** register/deregister sdio function
+*/
+struct sdio_func *wl_cfg80211_get_sdio_func(void);
+static void wl_clear_sdio_func(void);
+
+/*
+** ioctl utilites
+*/
+static s32 wl_dev_bufvar_get(struct net_device *dev, s8 *name, s8 *buf,
+ s32 buf_len);
+static __used s32 wl_dev_bufvar_set(struct net_device *dev, s8 *name,
+ s8 *buf, s32 len);
+static s32 wl_dev_intvar_set(struct net_device *dev, s8 *name, s32 val);
+static s32 wl_dev_intvar_get(struct net_device *dev, s8 *name,
+ s32 *retval);
+static s32 wl_dev_ioctl(struct net_device *dev, u32 cmd, void *arg,
+ u32 len);
+
+/*
+** cfg80211 set_wiphy_params utilities
+*/
+static s32 wl_set_frag(struct net_device *dev, u32 frag_threshold);
+static s32 wl_set_rts(struct net_device *dev, u32 frag_threshold);
+static s32 wl_set_retry(struct net_device *dev, u32 retry, bool l);
+
+/*
+** wl profile utilities
+*/
+static s32 wl_update_prof(struct wl_priv *wl, const wl_event_msg_t *e,
+ void *data, s32 item);
+static void *wl_read_prof(struct wl_priv *wl, s32 item);
+static void wl_init_prof(struct wl_profile *prof);
+
+/*
+** cfg80211 connect utilites
+*/
+static s32 wl_set_wpa_version(struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_set_auth_type(struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_set_set_cipher(struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_set_key_mgmt(struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_set_set_sharedkey(struct net_device *dev,
+ struct cfg80211_connect_params *sme);
+static s32 wl_get_assoc_ies(struct wl_priv *wl);
+static void wl_ch_to_chanspec(int ch,
+ struct wl_join_params *join_params, size_t *join_params_size);
+
+/*
+** information element utilities
+*/
+static void wl_rst_ie(struct wl_priv *wl);
+static __used s32 wl_add_ie(struct wl_priv *wl, u8 t, u8 l, u8 *v);
+static s32 wl_mrg_ie(struct wl_priv *wl, u8 *ie_stream, u16 ie_size);
+static s32 wl_cp_ie(struct wl_priv *wl, u8 *dst, u16 dst_size);
+static u32 wl_get_ielen(struct wl_priv *wl);
+
+static s32 wl_mode_to_nl80211_iftype(s32 mode);
+
+static struct wireless_dev *wl_alloc_wdev(s32 sizeof_iface,
+ struct device *dev);
+static void wl_free_wdev(struct wl_priv *wl);
+
+static s32 wl_inform_bss(struct wl_priv *wl);
+static s32 wl_inform_single_bss(struct wl_priv *wl, struct wl_bss_info *bi);
+static s32 wl_update_bss_info(struct wl_priv *wl);
+
+static s32 wl_add_keyext(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr,
+ struct key_params *params);
+
+/*
+** key indianess swap utilities
+*/
+static void swap_key_from_BE(struct wl_wsec_key *key);
+static void swap_key_to_BE(struct wl_wsec_key *key);
+
+/*
+** wl_priv memory init/deinit utilities
+*/
+static s32 wl_init_priv_mem(struct wl_priv *wl);
+static void wl_deinit_priv_mem(struct wl_priv *wl);
+
+static void wl_delay(u32 ms);
+
+/*
+** store/restore cfg80211 instance data
+*/
+static void wl_set_drvdata(struct wl_dev *dev, void *data);
+static void *wl_get_drvdata(struct wl_dev *dev);
+
+/*
+** ibss mode utilities
+*/
+static bool wl_is_ibssmode(struct wl_priv *wl);
+static bool wl_is_ibssstarter(struct wl_priv *wl);
+
+/*
+** dongle up/down , default configuration utilities
+*/
+static bool wl_is_linkdown(struct wl_priv *wl, const wl_event_msg_t *e);
+static bool wl_is_linkup(struct wl_priv *wl, const wl_event_msg_t *e);
+static bool wl_is_nonetwork(struct wl_priv *wl, const wl_event_msg_t *e);
+static void wl_link_up(struct wl_priv *wl);
+static void wl_link_down(struct wl_priv *wl);
+static s32 wl_dongle_mode(struct net_device *ndev, s32 iftype);
+static s32 __wl_cfg80211_up(struct wl_priv *wl);
+static s32 __wl_cfg80211_down(struct wl_priv *wl);
+static s32 wl_dongle_probecap(struct wl_priv *wl);
+static void wl_init_conf(struct wl_conf *conf);
+
+/*
+** dongle configuration utilities
+*/
+#ifndef EMBEDDED_PLATFORM
+static s32 wl_dongle_mode(struct net_device *ndev, s32 iftype);
+static s32 wl_dongle_country(struct net_device *ndev, u8 ccode);
+static s32 wl_dongle_up(struct net_device *ndev, u32 up);
+static s32 wl_dongle_power(struct net_device *ndev, u32 power_mode);
+static s32 wl_dongle_glom(struct net_device *ndev, u32 glom,
+ u32 dongle_align);
+static s32 wl_dongle_roam(struct net_device *ndev, u32 roamvar,
+ u32 bcn_timeout);
+static s32 wl_dongle_eventmsg(struct net_device *ndev);
+static s32 wl_dongle_scantime(struct net_device *ndev, s32 scan_assoc_time,
+ s32 scan_unassoc_time);
+static s32 wl_dongle_offload(struct net_device *ndev, s32 arpoe,
+ s32 arp_ol);
+static s32 wl_pattern_atoh(s8 *src, s8 *dst);
+static s32 wl_dongle_filter(struct net_device *ndev, u32 filter_mode);
+static s32 wl_update_wiphybands(struct wl_priv *wl);
+#endif /* !EMBEDDED_PLATFORM */
+static s32 wl_config_dongle(struct wl_priv *wl, bool need_lock);
+
+/*
+** iscan handler
+*/
+static void wl_iscan_timer(unsigned long data);
+static void wl_term_iscan(struct wl_priv *wl);
+static s32 wl_init_iscan(struct wl_priv *wl);
+static s32 wl_iscan_thread(void *data);
+static s32 wl_dev_iovar_setbuf(struct net_device *dev, s8 *iovar,
+ void *param, s32 paramlen, void *bufptr,
+ s32 buflen);
+static s32 wl_dev_iovar_getbuf(struct net_device *dev, s8 *iovar,
+ void *param, s32 paramlen, void *bufptr,
+ s32 buflen);
+static s32 wl_run_iscan(struct wl_iscan_ctrl *iscan, struct wlc_ssid *ssid,
+ u16 action);
+static s32 wl_do_iscan(struct wl_priv *wl);
+static s32 wl_wakeup_iscan(struct wl_iscan_ctrl *iscan);
+static s32 wl_invoke_iscan(struct wl_priv *wl);
+static s32 wl_get_iscan_results(struct wl_iscan_ctrl *iscan, u32 *status,
+ struct wl_scan_results **bss_list);
+static void wl_notify_iscan_complete(struct wl_iscan_ctrl *iscan, bool aborted);
+static void wl_init_iscan_eloop(struct wl_iscan_eloop *el);
+static s32 wl_iscan_done(struct wl_priv *wl);
+static s32 wl_iscan_pending(struct wl_priv *wl);
+static s32 wl_iscan_inprogress(struct wl_priv *wl);
+static s32 wl_iscan_aborted(struct wl_priv *wl);
+
+/*
+** fw/nvram downloading handler
+*/
+static void wl_init_fw(struct wl_fw_ctrl *fw);
+
+/*
+* find most significant bit set
+*/
+static __used u32 wl_find_msb(u16 bit16);
+
+/*
+* update pmklist to dongle
+*/
+static __used s32 wl_update_pmklist(struct net_device *dev,
+ struct wl_pmk_list *pmk_list, s32 err);
+
+static void wl_set_mpc(struct net_device *ndev, int mpc);
+
+/*
+* debufs support
+*/
+static int wl_debugfs_add_netdev_params(struct wl_priv *wl);
+static void wl_debugfs_remove_netdev(struct wl_priv *wl);
+
+#define WL_PRIV_GET() \
+ ({ \
+ struct wl_iface *ci; \
+ if (unlikely(!(wl_cfg80211_dev && \
+ (ci = wl_get_drvdata(wl_cfg80211_dev))))) { \
+ WL_ERR(("wl_cfg80211_dev is unavailable\n")); \
+ BUG(); \
+ } \
+ ci_to_wl(ci); \
+})
+
+#define CHECK_SYS_UP() \
+do { \
+ struct wl_priv *wl = wiphy_to_wl(wiphy); \
+ if (unlikely(!test_bit(WL_STATUS_READY, &wl->status))) { \
+ WL_INFO(("device is not ready : status (%d)\n", \
+ (int)wl->status)); \
+ return -EIO; \
+ } \
+} while (0)
+
+extern int dhd_wait_pend8021x(struct net_device *dev);
+
+#if (WL_DBG_LEVEL > 0)
+#define WL_DBG_ESTR_MAX 32
+static s8 wl_dbg_estr[][WL_DBG_ESTR_MAX] = {
+ "SET_SSID", "JOIN", "START", "AUTH", "AUTH_IND",
+ "DEAUTH", "DEAUTH_IND", "ASSOC", "ASSOC_IND", "REASSOC",
+ "REASSOC_IND", "DISASSOC", "DISASSOC_IND", "QUIET_START", "QUIET_END",
+ "BEACON_RX", "LINK", "MIC_ERROR", "NDIS_LINK", "ROAM",
+ "TXFAIL", "PMKID_CACHE", "RETROGRADE_TSF", "PRUNE", "AUTOAUTH",
+ "EAPOL_MSG", "SCAN_COMPLETE", "ADDTS_IND", "DELTS_IND", "BCNSENT_IND",
+ "BCNRX_MSG", "BCNLOST_MSG", "ROAM_PREP", "PFN_NET_FOUND",
+ "PFN_NET_LOST",
+ "RESET_COMPLETE", "JOIN_START", "ROAM_START", "ASSOC_START",
+ "IBSS_ASSOC",
+ "RADIO", "PSM_WATCHDOG",
+ "PROBREQ_MSG",
+ "SCAN_CONFIRM_IND", "PSK_SUP", "COUNTRY_CODE_CHANGED",
+ "EXCEEDED_MEDIUM_TIME", "ICV_ERROR",
+ "UNICAST_DECODE_ERROR", "MULTICAST_DECODE_ERROR", "TRACE",
+ "IF",
+ "RSSI", "PFN_SCAN_COMPLETE", "ACTION_FRAME", "ACTION_FRAME_COMPLETE",
+};
+#endif /* WL_DBG_LEVEL */
+
+#define CHAN2G(_channel, _freq, _flags) { \
+ .band = IEEE80211_BAND_2GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define CHAN5G(_channel, _flags) { \
+ .band = IEEE80211_BAND_5GHZ, \
+ .center_freq = 5000 + (5 * (_channel)), \
+ .hw_value = (_channel), \
+ .flags = (_flags), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2)
+#define RATETAB_ENT(_rateid, _flags) \
+ { \
+ .bitrate = RATE_TO_BASE100KBPS(_rateid), \
+ .hw_value = (_rateid), \
+ .flags = (_flags), \
+ }
+
+static struct ieee80211_rate __wl_rates[] = {
+ RATETAB_ENT(WLC_RATE_1M, 0),
+ RATETAB_ENT(WLC_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(WLC_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(WLC_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(WLC_RATE_6M, 0),
+ RATETAB_ENT(WLC_RATE_9M, 0),
+ RATETAB_ENT(WLC_RATE_12M, 0),
+ RATETAB_ENT(WLC_RATE_18M, 0),
+ RATETAB_ENT(WLC_RATE_24M, 0),
+ RATETAB_ENT(WLC_RATE_36M, 0),
+ RATETAB_ENT(WLC_RATE_48M, 0),
+ RATETAB_ENT(WLC_RATE_54M, 0),
+};
+
+#define wl_a_rates (__wl_rates + 4)
+#define wl_a_rates_size 8
+#define wl_g_rates (__wl_rates + 0)
+#define wl_g_rates_size 12
+
+static struct ieee80211_channel __wl_2ghz_channels[] = {
+ CHAN2G(1, 2412, 0),
+ CHAN2G(2, 2417, 0),
+ CHAN2G(3, 2422, 0),
+ CHAN2G(4, 2427, 0),
+ CHAN2G(5, 2432, 0),
+ CHAN2G(6, 2437, 0),
+ CHAN2G(7, 2442, 0),
+ CHAN2G(8, 2447, 0),
+ CHAN2G(9, 2452, 0),
+ CHAN2G(10, 2457, 0),
+ CHAN2G(11, 2462, 0),
+ CHAN2G(12, 2467, 0),
+ CHAN2G(13, 2472, 0),
+ CHAN2G(14, 2484, 0),
+};
+
+static struct ieee80211_channel __wl_5ghz_a_channels[] = {
+ CHAN5G(34, 0), CHAN5G(36, 0),
+ CHAN5G(38, 0), CHAN5G(40, 0),
+ CHAN5G(42, 0), CHAN5G(44, 0),
+ CHAN5G(46, 0), CHAN5G(48, 0),
+ CHAN5G(52, 0), CHAN5G(56, 0),
+ CHAN5G(60, 0), CHAN5G(64, 0),
+ CHAN5G(100, 0), CHAN5G(104, 0),
+ CHAN5G(108, 0), CHAN5G(112, 0),
+ CHAN5G(116, 0), CHAN5G(120, 0),
+ CHAN5G(124, 0), CHAN5G(128, 0),
+ CHAN5G(132, 0), CHAN5G(136, 0),
+ CHAN5G(140, 0), CHAN5G(149, 0),
+ CHAN5G(153, 0), CHAN5G(157, 0),
+ CHAN5G(161, 0), CHAN5G(165, 0),
+ CHAN5G(184, 0), CHAN5G(188, 0),
+ CHAN5G(192, 0), CHAN5G(196, 0),
+ CHAN5G(200, 0), CHAN5G(204, 0),
+ CHAN5G(208, 0), CHAN5G(212, 0),
+ CHAN5G(216, 0),
+};
+
+static struct ieee80211_channel __wl_5ghz_n_channels[] = {
+ CHAN5G(32, 0), CHAN5G(34, 0),
+ CHAN5G(36, 0), CHAN5G(38, 0),
+ CHAN5G(40, 0), CHAN5G(42, 0),
+ CHAN5G(44, 0), CHAN5G(46, 0),
+ CHAN5G(48, 0), CHAN5G(50, 0),
+ CHAN5G(52, 0), CHAN5G(54, 0),
+ CHAN5G(56, 0), CHAN5G(58, 0),
+ CHAN5G(60, 0), CHAN5G(62, 0),
+ CHAN5G(64, 0), CHAN5G(66, 0),
+ CHAN5G(68, 0), CHAN5G(70, 0),
+ CHAN5G(72, 0), CHAN5G(74, 0),
+ CHAN5G(76, 0), CHAN5G(78, 0),
+ CHAN5G(80, 0), CHAN5G(82, 0),
+ CHAN5G(84, 0), CHAN5G(86, 0),
+ CHAN5G(88, 0), CHAN5G(90, 0),
+ CHAN5G(92, 0), CHAN5G(94, 0),
+ CHAN5G(96, 0), CHAN5G(98, 0),
+ CHAN5G(100, 0), CHAN5G(102, 0),
+ CHAN5G(104, 0), CHAN5G(106, 0),
+ CHAN5G(108, 0), CHAN5G(110, 0),
+ CHAN5G(112, 0), CHAN5G(114, 0),
+ CHAN5G(116, 0), CHAN5G(118, 0),
+ CHAN5G(120, 0), CHAN5G(122, 0),
+ CHAN5G(124, 0), CHAN5G(126, 0),
+ CHAN5G(128, 0), CHAN5G(130, 0),
+ CHAN5G(132, 0), CHAN5G(134, 0),
+ CHAN5G(136, 0), CHAN5G(138, 0),
+ CHAN5G(140, 0), CHAN5G(142, 0),
+ CHAN5G(144, 0), CHAN5G(145, 0),
+ CHAN5G(146, 0), CHAN5G(147, 0),
+ CHAN5G(148, 0), CHAN5G(149, 0),
+ CHAN5G(150, 0), CHAN5G(151, 0),
+ CHAN5G(152, 0), CHAN5G(153, 0),
+ CHAN5G(154, 0), CHAN5G(155, 0),
+ CHAN5G(156, 0), CHAN5G(157, 0),
+ CHAN5G(158, 0), CHAN5G(159, 0),
+ CHAN5G(160, 0), CHAN5G(161, 0),
+ CHAN5G(162, 0), CHAN5G(163, 0),
+ CHAN5G(164, 0), CHAN5G(165, 0),
+ CHAN5G(166, 0), CHAN5G(168, 0),
+ CHAN5G(170, 0), CHAN5G(172, 0),
+ CHAN5G(174, 0), CHAN5G(176, 0),
+ CHAN5G(178, 0), CHAN5G(180, 0),
+ CHAN5G(182, 0), CHAN5G(184, 0),
+ CHAN5G(186, 0), CHAN5G(188, 0),
+ CHAN5G(190, 0), CHAN5G(192, 0),
+ CHAN5G(194, 0), CHAN5G(196, 0),
+ CHAN5G(198, 0), CHAN5G(200, 0),
+ CHAN5G(202, 0), CHAN5G(204, 0),
+ CHAN5G(206, 0), CHAN5G(208, 0),
+ CHAN5G(210, 0), CHAN5G(212, 0),
+ CHAN5G(214, 0), CHAN5G(216, 0),
+ CHAN5G(218, 0), CHAN5G(220, 0),
+ CHAN5G(222, 0), CHAN5G(224, 0),
+ CHAN5G(226, 0), CHAN5G(228, 0),
+};
+
+static struct ieee80211_supported_band __wl_band_2ghz = {
+ .band = IEEE80211_BAND_2GHZ,
+ .channels = __wl_2ghz_channels,
+ .n_channels = ARRAY_SIZE(__wl_2ghz_channels),
+ .bitrates = wl_g_rates,
+ .n_bitrates = wl_g_rates_size,
+};
+
+static struct ieee80211_supported_band __wl_band_5ghz_a = {
+ .band = IEEE80211_BAND_5GHZ,
+ .channels = __wl_5ghz_a_channels,
+ .n_channels = ARRAY_SIZE(__wl_5ghz_a_channels),
+ .bitrates = wl_a_rates,
+ .n_bitrates = wl_a_rates_size,
+};
+
+static struct ieee80211_supported_band __wl_band_5ghz_n = {
+ .band = IEEE80211_BAND_5GHZ,
+ .channels = __wl_5ghz_n_channels,
+ .n_channels = ARRAY_SIZE(__wl_5ghz_n_channels),
+ .bitrates = wl_a_rates,
+ .n_bitrates = wl_a_rates_size,
+};
+
+static const u32 __wl_cipher_suites[] = {
+ WLAN_CIPHER_SUITE_WEP40,
+ WLAN_CIPHER_SUITE_WEP104,
+ WLAN_CIPHER_SUITE_TKIP,
+ WLAN_CIPHER_SUITE_CCMP,
+ WLAN_CIPHER_SUITE_AES_CMAC,
+};
+
+static void swap_key_from_BE(struct wl_wsec_key *key)
+{
+ key->index = htod32(key->index);
+ key->len = htod32(key->len);
+ key->algo = htod32(key->algo);
+ key->flags = htod32(key->flags);
+ key->rxiv.hi = htod32(key->rxiv.hi);
+ key->rxiv.lo = htod16(key->rxiv.lo);
+ key->iv_initialized = htod32(key->iv_initialized);
+}
+
+static void swap_key_to_BE(struct wl_wsec_key *key)
+{
+ key->index = dtoh32(key->index);
+ key->len = dtoh32(key->len);
+ key->algo = dtoh32(key->algo);
+ key->flags = dtoh32(key->flags);
+ key->rxiv.hi = dtoh32(key->rxiv.hi);
+ key->rxiv.lo = dtoh16(key->rxiv.lo);
+ key->iv_initialized = dtoh32(key->iv_initialized);
+}
+
+static s32
+wl_dev_ioctl(struct net_device *dev, u32 cmd, void *arg, u32 len)
+{
+ struct ifreq ifr;
+ struct wl_ioctl ioc;
+ mm_segment_t fs;
+ s32 err = 0;
+
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = cmd;
+ ioc.buf = arg;
+ ioc.len = len;
+ strcpy(ifr.ifr_name, dev->name);
+ ifr.ifr_data = (caddr_t)&ioc;
+
+ fs = get_fs();
+ set_fs(get_ds());
+ err = dev->netdev_ops->ndo_do_ioctl(dev, &ifr, SIOCDEVPRIVATE);
+ set_fs(fs);
+
+ return err;
+}
+
+static s32
+wl_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
+ enum nl80211_iftype type, u32 *flags,
+ struct vif_params *params)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct wireless_dev *wdev;
+ s32 infra = 0;
+ s32 ap = 0;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ switch (type) {
+ case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_WDS:
+ WL_ERR(("type (%d) : currently we do not support this type\n",
+ type));
+ return -EOPNOTSUPP;
+ case NL80211_IFTYPE_ADHOC:
+ wl->conf->mode = WL_MODE_IBSS;
+ break;
+ case NL80211_IFTYPE_STATION:
+ wl->conf->mode = WL_MODE_BSS;
+ infra = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ infra = htod32(infra);
+ ap = htod32(ap);
+ wdev = ndev->ieee80211_ptr;
+ wdev->iftype = type;
+ WL_DBG(("%s : ap (%d), infra (%d)\n", ndev->name, ap, infra));
+ err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_INFRA error (%d)\n", err));
+ return err;
+ }
+ err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_AP error (%d)\n", err));
+ return err;
+ }
+
+ /* -EINPROGRESS: Call commit handler */
+ return -EINPROGRESS;
+}
+
+static void wl_iscan_prep(struct wl_scan_params *params, struct wlc_ssid *ssid)
+{
+ memcpy(&params->bssid, &ether_bcast, ETHER_ADDR_LEN);
+ params->bss_type = DOT11_BSSTYPE_ANY;
+ params->scan_type = 0;
+ params->nprobes = -1;
+ params->active_time = -1;
+ params->passive_time = -1;
+ params->home_time = -1;
+ params->channel_num = 0;
+
+ params->nprobes = htod32(params->nprobes);
+ params->active_time = htod32(params->active_time);
+ params->passive_time = htod32(params->passive_time);
+ params->home_time = htod32(params->home_time);
+ if (ssid && ssid->SSID_len)
+ memcpy(&params->ssid, ssid, sizeof(wlc_ssid_t));
+
+}
+
+static s32
+wl_dev_iovar_setbuf(struct net_device *dev, s8 * iovar, void *param,
+ s32 paramlen, void *bufptr, s32 buflen)
+{
+ s32 iolen;
+
+ iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen);
+ BUG_ON(unlikely(!iolen));
+
+ return wl_dev_ioctl(dev, WLC_SET_VAR, bufptr, iolen);
+}
+
+static s32
+wl_dev_iovar_getbuf(struct net_device *dev, s8 * iovar, void *param,
+ s32 paramlen, void *bufptr, s32 buflen)
+{
+ s32 iolen;
+
+ iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen);
+ BUG_ON(unlikely(!iolen));
+
+ return wl_dev_ioctl(dev, WLC_GET_VAR, bufptr, buflen);
+}
+
+static s32
+wl_run_iscan(struct wl_iscan_ctrl *iscan, struct wlc_ssid *ssid, u16 action)
+{
+ s32 params_size =
+ (WL_SCAN_PARAMS_FIXED_SIZE + offsetof(wl_iscan_params_t, params));
+ struct wl_iscan_params *params;
+ s32 err = 0;
+
+ if (ssid && ssid->SSID_len)
+ params_size += sizeof(struct wlc_ssid);
+ params = (struct wl_iscan_params *)kzalloc(params_size, GFP_KERNEL);
+ if (unlikely(!params))
+ return -ENOMEM;
+ memset(params, 0, params_size);
+ BUG_ON(unlikely(params_size >= WLC_IOCTL_SMLEN));
+
+ wl_iscan_prep(&params->params, ssid);
+
+ params->version = htod32(ISCAN_REQ_VERSION);
+ params->action = htod16(action);
+ params->scan_duration = htod16(0);
+
+ /* params_size += offsetof(wl_iscan_params_t, params); */
+ err = wl_dev_iovar_setbuf(iscan->dev, "iscan", params, params_size,
+ iscan->ioctl_buf, WLC_IOCTL_SMLEN);
+ if (unlikely(err)) {
+ if (err == -EBUSY) {
+ WL_INFO(("system busy : iscan canceled\n"));
+ } else {
+ WL_ERR(("error (%d)\n", err));
+ }
+ }
+ kfree(params);
+ return err;
+}
+
+static s32 wl_do_iscan(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl_to_iscan(wl);
+ struct net_device *ndev = wl_to_ndev(wl);
+ struct wlc_ssid ssid;
+ s32 passive_scan;
+ s32 err = 0;
+
+ /* Broadcast scan by default */
+ memset(&ssid, 0, sizeof(ssid));
+
+ iscan->state = WL_ISCAN_STATE_SCANING;
+
+ passive_scan = wl->active_scan ? 0 : 1;
+ err = wl_dev_ioctl(wl_to_ndev(wl), WLC_SET_PASSIVE_SCAN,
+ &passive_scan, sizeof(passive_scan));
+ if (unlikely(err)) {
+ WL_DBG(("error (%d)\n", err));
+ return err;
+ }
+ wl_set_mpc(ndev, 0);
+ wl->iscan_kickstart = true;
+ wl_run_iscan(iscan, &ssid, WL_SCAN_ACTION_START);
+ mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000);
+ iscan->timer_on = 1;
+
+ return err;
+}
+
+static s32
+__wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_scan_request *request,
+ struct cfg80211_ssid *this_ssid)
+{
+ struct wl_priv *wl = ndev_to_wl(ndev);
+ struct cfg80211_ssid *ssids;
+ struct wl_scan_req *sr = wl_to_sr(wl);
+ s32 passive_scan;
+ bool iscan_req;
+ bool spec_scan;
+ s32 err = 0;
+
+ if (unlikely(test_bit(WL_STATUS_SCANNING, &wl->status))) {
+ WL_ERR(("Scanning already : status (%d)\n", (int)wl->status));
+ return -EAGAIN;
+ }
+ if (unlikely(test_bit(WL_STATUS_SCAN_ABORTING, &wl->status))) {
+ WL_ERR(("Scanning being aborted : status (%d)\n",
+ (int)wl->status));
+ return -EAGAIN;
+ }
+
+ iscan_req = false;
+ spec_scan = false;
+ if (request) { /* scan bss */
+ ssids = request->ssids;
+ if (wl->iscan_on && (!ssids || !ssids->ssid_len)) { /* for
+ * specific scan,
+ * ssids->ssid_len has
+ * non-zero(ssid string)
+ * length.
+ * Otherwise this is 0.
+ * we do not iscan for
+ * specific scan request
+ */
+ iscan_req = true;
+ }
+ } else { /* scan in ibss */
+ /* we don't do iscan in ibss */
+ ssids = this_ssid;
+ }
+ wl->scan_request = request;
+ set_bit(WL_STATUS_SCANNING, &wl->status);
+ if (iscan_req) {
+ err = wl_do_iscan(wl);
+ if (likely(!err))
+ return err;
+ else
+ goto scan_out;
+ } else {
+ WL_DBG(("ssid \"%s\", ssid_len (%d)\n",
+ ssids->ssid, ssids->ssid_len));
+ memset(&sr->ssid, 0, sizeof(sr->ssid));
+ sr->ssid.SSID_len =
+ min_t(u8, sizeof(sr->ssid.SSID), ssids->ssid_len);
+ if (sr->ssid.SSID_len) {
+ memcpy(sr->ssid.SSID, ssids->ssid, sr->ssid.SSID_len);
+ sr->ssid.SSID_len = htod32(sr->ssid.SSID_len);
+ WL_DBG(("Specific scan ssid=\"%s\" len=%d\n",
+ sr->ssid.SSID, sr->ssid.SSID_len));
+ spec_scan = true;
+ } else {
+ WL_DBG(("Broadcast scan\n"));
+ }
+ WL_DBG(("sr->ssid.SSID_len (%d)\n", sr->ssid.SSID_len));
+ passive_scan = wl->active_scan ? 0 : 1;
+ err = wl_dev_ioctl(ndev, WLC_SET_PASSIVE_SCAN,
+ &passive_scan, sizeof(passive_scan));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_PASSIVE_SCAN error (%d)\n", err));
+ goto scan_out;
+ }
+ wl_set_mpc(ndev, 0);
+ err = wl_dev_ioctl(ndev, WLC_SCAN, &sr->ssid,
+ sizeof(sr->ssid));
+ if (err) {
+ if (err == -EBUSY) {
+ WL_INFO(("system busy : scan for \"%s\" "
+ "canceled\n", sr->ssid.SSID));
+ } else {
+ WL_ERR(("WLC_SCAN error (%d)\n", err));
+ }
+ wl_set_mpc(ndev, 1);
+ goto scan_out;
+ }
+ }
+
+ return 0;
+
+scan_out:
+ clear_bit(WL_STATUS_SCANNING, &wl->status);
+ wl->scan_request = NULL;
+ return err;
+}
+
+static s32
+wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev,
+ struct cfg80211_scan_request *request)
+{
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ err = __wl_cfg80211_scan(wiphy, ndev, request, NULL);
+ if (unlikely(err)) {
+ WL_DBG(("scan error (%d)\n", err));
+ return err;
+ }
+
+ return err;
+}
+
+static s32 wl_dev_intvar_set(struct net_device *dev, s8 *name, s32 val)
+{
+ s8 buf[WLC_IOCTL_SMLEN];
+ u32 len;
+ s32 err = 0;
+
+ val = htod32(val);
+ len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf));
+ BUG_ON(unlikely(!len));
+
+ err = wl_dev_ioctl(dev, WLC_SET_VAR, buf, len);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ }
+
+ return err;
+}
+
+static s32
+wl_dev_intvar_get(struct net_device *dev, s8 *name, s32 *retval)
+{
+ union {
+ s8 buf[WLC_IOCTL_SMLEN];
+ s32 val;
+ } var;
+ u32 len;
+ u32 data_null;
+ s32 err = 0;
+
+ len =
+ bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var),
+ sizeof(var.buf));
+ BUG_ON(unlikely(!len));
+ err = wl_dev_ioctl(dev, WLC_GET_VAR, &var, len);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ }
+ *retval = dtoh32(var.val);
+
+ return err;
+}
+
+static s32 wl_set_rts(struct net_device *dev, u32 rts_threshold)
+{
+ s32 err = 0;
+
+ err = wl_dev_intvar_set(dev, "rtsthresh", rts_threshold);
+ if (unlikely(err)) {
+ WL_ERR(("Error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static s32 wl_set_frag(struct net_device *dev, u32 frag_threshold)
+{
+ s32 err = 0;
+
+ err = wl_dev_intvar_set(dev, "fragthresh", frag_threshold);
+ if (unlikely(err)) {
+ WL_ERR(("Error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static s32 wl_set_retry(struct net_device *dev, u32 retry, bool l)
+{
+ s32 err = 0;
+ u32 cmd = (l ? WLC_SET_LRL : WLC_SET_SRL);
+
+ retry = htod32(retry);
+ err = wl_dev_ioctl(dev, cmd, &retry, sizeof(retry));
+ if (unlikely(err)) {
+ WL_ERR(("cmd (%d) , error (%d)\n", cmd, err));
+ return err;
+ }
+ return err;
+}
+
+static s32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct net_device *ndev = wl_to_ndev(wl);
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ if (changed & WIPHY_PARAM_RTS_THRESHOLD &&
+ (wl->conf->rts_threshold != wiphy->rts_threshold)) {
+ wl->conf->rts_threshold = wiphy->rts_threshold;
+ err = wl_set_rts(ndev, wl->conf->rts_threshold);
+ if (!err)
+ return err;
+ }
+ if (changed & WIPHY_PARAM_FRAG_THRESHOLD &&
+ (wl->conf->frag_threshold != wiphy->frag_threshold)) {
+ wl->conf->frag_threshold = wiphy->frag_threshold;
+ err = wl_set_frag(ndev, wl->conf->frag_threshold);
+ if (!err)
+ return err;
+ }
+ if (changed & WIPHY_PARAM_RETRY_LONG
+ && (wl->conf->retry_long != wiphy->retry_long)) {
+ wl->conf->retry_long = wiphy->retry_long;
+ err = wl_set_retry(ndev, wl->conf->retry_long, true);
+ if (!err)
+ return err;
+ }
+ if (changed & WIPHY_PARAM_RETRY_SHORT
+ && (wl->conf->retry_short != wiphy->retry_short)) {
+ wl->conf->retry_short = wiphy->retry_short;
+ err = wl_set_retry(ndev, wl->conf->retry_short, false);
+ if (!err) {
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static s32
+wl_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_ibss_params *params)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct cfg80211_bss *bss;
+ struct ieee80211_channel *chan;
+ struct wl_join_params join_params;
+ struct cfg80211_ssid ssid;
+ s32 scan_retry = 0;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ if (params->bssid) {
+ WL_ERR(("Invalid bssid\n"));
+ return -EOPNOTSUPP;
+ }
+ bss = cfg80211_get_ibss(wiphy, NULL, params->ssid, params->ssid_len);
+ if (!bss) {
+ memcpy(ssid.ssid, params->ssid, params->ssid_len);
+ ssid.ssid_len = params->ssid_len;
+ do {
+ if (unlikely
+ (__wl_cfg80211_scan(wiphy, dev, NULL, &ssid) ==
+ -EBUSY)) {
+ wl_delay(150);
+ } else {
+ break;
+ }
+ } while (++scan_retry < WL_SCAN_RETRY_MAX);
+ rtnl_unlock(); /* to allow scan_inform to paropagate
+ to cfg80211 plane */
+ schedule_timeout_interruptible(4 * HZ); /* wait 4 secons
+ till scan done.... */
+ rtnl_lock();
+ bss = cfg80211_get_ibss(wiphy, NULL,
+ params->ssid, params->ssid_len);
+ }
+ if (bss) {
+ wl->ibss_starter = false;
+ WL_DBG(("Found IBSS\n"));
+ } else {
+ wl->ibss_starter = true;
+ }
+ chan = params->channel;
+ if (chan)
+ wl->channel = ieee80211_frequency_to_channel(chan->center_freq);
+ /*
+ ** Join with specific BSSID and cached SSID
+ ** If SSID is zero join based on BSSID only
+ */
+ memset(&join_params, 0, sizeof(join_params));
+ memcpy((void *)join_params.ssid.SSID, (void *)params->ssid,
+ params->ssid_len);
+ join_params.ssid.SSID_len = htod32(params->ssid_len);
+ if (params->bssid)
+ memcpy(&join_params.params.bssid, params->bssid,
+ ETHER_ADDR_LEN);
+ else
+ memset(&join_params.params.bssid, 0, ETHER_ADDR_LEN);
+
+ err = wl_dev_ioctl(dev, WLC_SET_SSID, &join_params,
+ sizeof(join_params));
+ if (unlikely(err)) {
+ WL_ERR(("Error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static s32 wl_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ wl_link_down(wl);
+
+ return err;
+}
+
+static s32
+wl_set_wpa_version(struct net_device *dev, struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ struct wl_security *sec;
+ s32 val = 0;
+ s32 err = 0;
+
+ if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1)
+ val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED;
+ else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2)
+ val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
+ else
+ val = WPA_AUTH_DISABLED;
+ WL_DBG(("setting wpa_auth to 0x%0x\n", val));
+ err = wl_dev_intvar_set(dev, "wpa_auth", val);
+ if (unlikely(err)) {
+ WL_ERR(("set wpa_auth failed (%d)\n", err));
+ return err;
+ }
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ sec->wpa_versions = sme->crypto.wpa_versions;
+ return err;
+}
+
+static s32
+wl_set_auth_type(struct net_device *dev, struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ struct wl_security *sec;
+ s32 val = 0;
+ s32 err = 0;
+
+ switch (sme->auth_type) {
+ case NL80211_AUTHTYPE_OPEN_SYSTEM:
+ val = 0;
+ WL_DBG(("open system\n"));
+ break;
+ case NL80211_AUTHTYPE_SHARED_KEY:
+ val = 1;
+ WL_DBG(("shared key\n"));
+ break;
+ case NL80211_AUTHTYPE_AUTOMATIC:
+ val = 2;
+ WL_DBG(("automatic\n"));
+ break;
+ case NL80211_AUTHTYPE_NETWORK_EAP:
+ WL_DBG(("network eap\n"));
+ default:
+ val = 2;
+ WL_ERR(("invalid auth type (%d)\n", sme->auth_type));
+ break;
+ }
+
+ err = wl_dev_intvar_set(dev, "auth", val);
+ if (unlikely(err)) {
+ WL_ERR(("set auth failed (%d)\n", err));
+ return err;
+ }
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ sec->auth_type = sme->auth_type;
+ return err;
+}
+
+static s32
+wl_set_set_cipher(struct net_device *dev, struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ struct wl_security *sec;
+ s32 pval = 0;
+ s32 gval = 0;
+ s32 err = 0;
+
+ if (sme->crypto.n_ciphers_pairwise) {
+ switch (sme->crypto.ciphers_pairwise[0]) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ pval = WEP_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ pval = TKIP_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ pval = AES_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ pval = AES_ENABLED;
+ break;
+ default:
+ WL_ERR(("invalid cipher pairwise (%d)\n",
+ sme->crypto.ciphers_pairwise[0]));
+ return -EINVAL;
+ }
+ }
+ if (sme->crypto.cipher_group) {
+ switch (sme->crypto.cipher_group) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ gval = WEP_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ gval = TKIP_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ gval = AES_ENABLED;
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ gval = AES_ENABLED;
+ break;
+ default:
+ WL_ERR(("invalid cipher group (%d)\n",
+ sme->crypto.cipher_group));
+ return -EINVAL;
+ }
+ }
+
+ WL_DBG(("pval (%d) gval (%d)\n", pval, gval));
+ err = wl_dev_intvar_set(dev, "wsec", pval | gval);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0];
+ sec->cipher_group = sme->crypto.cipher_group;
+
+ return err;
+}
+
+static s32
+wl_set_key_mgmt(struct net_device *dev, struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ struct wl_security *sec;
+ s32 val = 0;
+ s32 err = 0;
+
+ if (sme->crypto.n_akm_suites) {
+ err = wl_dev_intvar_get(dev, "wpa_auth", &val);
+ if (unlikely(err)) {
+ WL_ERR(("could not get wpa_auth (%d)\n", err));
+ return err;
+ }
+ if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) {
+ switch (sme->crypto.akm_suites[0]) {
+ case WLAN_AKM_SUITE_8021X:
+ val = WPA_AUTH_UNSPECIFIED;
+ break;
+ case WLAN_AKM_SUITE_PSK:
+ val = WPA_AUTH_PSK;
+ break;
+ default:
+ WL_ERR(("invalid cipher group (%d)\n",
+ sme->crypto.cipher_group));
+ return -EINVAL;
+ }
+ } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) {
+ switch (sme->crypto.akm_suites[0]) {
+ case WLAN_AKM_SUITE_8021X:
+ val = WPA2_AUTH_UNSPECIFIED;
+ break;
+ case WLAN_AKM_SUITE_PSK:
+ val = WPA2_AUTH_PSK;
+ break;
+ default:
+ WL_ERR(("invalid cipher group (%d)\n",
+ sme->crypto.cipher_group));
+ return -EINVAL;
+ }
+ }
+
+ WL_DBG(("setting wpa_auth to %d\n", val));
+ err = wl_dev_intvar_set(dev, "wpa_auth", val);
+ if (unlikely(err)) {
+ WL_ERR(("could not set wpa_auth (%d)\n", err));
+ return err;
+ }
+ }
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ sec->wpa_auth = sme->crypto.akm_suites[0];
+
+ return err;
+}
+
+static s32
+wl_set_set_sharedkey(struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ struct wl_security *sec;
+ struct wl_wsec_key key;
+ s32 val;
+ s32 err = 0;
+
+ WL_DBG(("key len (%d)\n", sme->key_len));
+ if (sme->key_len) {
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ WL_DBG(("wpa_versions 0x%x cipher_pairwise 0x%x\n",
+ sec->wpa_versions, sec->cipher_pairwise));
+ if (!
+ (sec->wpa_versions & (NL80211_WPA_VERSION_1 |
+ NL80211_WPA_VERSION_2))
+&& (sec->cipher_pairwise & (WLAN_CIPHER_SUITE_WEP40 |
+ WLAN_CIPHER_SUITE_WEP104))) {
+ memset(&key, 0, sizeof(key));
+ key.len = (u32) sme->key_len;
+ key.index = (u32) sme->key_idx;
+ if (unlikely(key.len > sizeof(key.data))) {
+ WL_ERR(("Too long key length (%u)\n", key.len));
+ return -EINVAL;
+ }
+ memcpy(key.data, sme->key, key.len);
+ key.flags = WL_PRIMARY_KEY;
+ switch (sec->cipher_pairwise) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ key.algo = CRYPTO_ALGO_WEP1;
+ break;
+ case WLAN_CIPHER_SUITE_WEP104:
+ key.algo = CRYPTO_ALGO_WEP128;
+ break;
+ default:
+ WL_ERR(("Invalid algorithm (%d)\n",
+ sme->crypto.ciphers_pairwise[0]));
+ return -EINVAL;
+ }
+ /* Set the new key/index */
+ WL_DBG(("key length (%d) key index (%d) algo (%d)\n",
+ key.len, key.index, key.algo));
+ WL_DBG(("key \"%s\"\n", key.data));
+ swap_key_from_BE(&key);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY, &key,
+ sizeof(key));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_KEY error (%d)\n", err));
+ return err;
+ }
+ if (sec->auth_type == NL80211_AUTHTYPE_OPEN_SYSTEM) {
+ WL_DBG(("set auth_type to shared key\n"));
+ val = 1; /* shared key */
+ err = wl_dev_intvar_set(dev, "auth", val);
+ if (unlikely(err)) {
+ WL_ERR(("set auth failed (%d)\n", err));
+ return err;
+ }
+ }
+ }
+ }
+ return err;
+}
+
+static s32
+wl_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_connect_params *sme)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct ieee80211_channel *chan = sme->channel;
+ struct wl_join_params join_params;
+ size_t join_params_size;
+
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ if (unlikely(!sme->ssid)) {
+ WL_ERR(("Invalid ssid\n"));
+ return -EOPNOTSUPP;
+ }
+ if (chan) {
+ wl->channel = ieee80211_frequency_to_channel(chan->center_freq);
+ WL_DBG(("channel (%d), center_req (%d)\n", wl->channel,
+ chan->center_freq));
+ }
+ WL_DBG(("ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len));
+ err = wl_set_wpa_version(dev, sme);
+ if (unlikely(err))
+ return err;
+
+ err = wl_set_auth_type(dev, sme);
+ if (unlikely(err))
+ return err;
+
+ err = wl_set_set_cipher(dev, sme);
+ if (unlikely(err))
+ return err;
+
+ err = wl_set_key_mgmt(dev, sme);
+ if (unlikely(err))
+ return err;
+
+ err = wl_set_set_sharedkey(dev, sme);
+ if (unlikely(err))
+ return err;
+
+ wl_update_prof(wl, NULL, sme->bssid, WL_PROF_BSSID);
+ /*
+ ** Join with specific BSSID and cached SSID
+ ** If SSID is zero join based on BSSID only
+ */
+ memset(&join_params, 0, sizeof(join_params));
+ join_params_size = sizeof(join_params.ssid);
+
+ join_params.ssid.SSID_len = min(sizeof(join_params.ssid.SSID), sme->ssid_len);
+ memcpy(&join_params.ssid.SSID, sme->ssid, join_params.ssid.SSID_len);
+ join_params.ssid.SSID_len = htod32(join_params.ssid.SSID_len);
+ wl_update_prof(wl, NULL, &join_params.ssid, WL_PROF_SSID);
+ memcpy(&join_params.params.bssid, &ether_bcast, ETHER_ADDR_LEN);
+
+ wl_ch_to_chanspec(wl->channel, &join_params, &join_params_size);
+ WL_DBG(("join_param_size %d\n", join_params_size));
+
+ if (join_params.ssid.SSID_len < IEEE80211_MAX_SSID_LEN) {
+ WL_DBG(("ssid \"%s\", len (%d)\n", join_params.ssid.SSID,
+ join_params.ssid.SSID_len));
+ }
+ err = wl_dev_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ set_bit(WL_STATUS_CONNECTING, &wl->status);
+
+ return err;
+}
+
+static s32
+wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev,
+ u16 reason_code)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ scb_val_t scbval;
+ bool act = false;
+ s32 err = 0;
+
+ WL_DBG(("Reason %d\n", reason_code));
+ CHECK_SYS_UP();
+ act = *(bool *) wl_read_prof(wl, WL_PROF_ACT);
+ if (likely(act)) {
+ scbval.val = reason_code;
+ memcpy(&scbval.ea, &wl->bssid, ETHER_ADDR_LEN);
+ scbval.val = htod32(scbval.val);
+ err = wl_dev_ioctl(dev, WLC_DISASSOC, &scbval,
+ sizeof(scb_val_t));
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ }
+
+ return err;
+}
+
+static s32
+wl_cfg80211_set_tx_power(struct wiphy *wiphy,
+ enum nl80211_tx_power_setting type, s32 dbm)
+{
+
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct net_device *ndev = wl_to_ndev(wl);
+ u16 txpwrmw;
+ s32 err = 0;
+ s32 disable = 0;
+
+ CHECK_SYS_UP();
+ switch (type) {
+ case NL80211_TX_POWER_AUTOMATIC:
+ break;
+ case NL80211_TX_POWER_LIMITED:
+ if (dbm < 0) {
+ WL_ERR(("TX_POWER_LIMITTED - dbm is negative\n"));
+ return -EINVAL;
+ }
+ break;
+ case NL80211_TX_POWER_FIXED:
+ if (dbm < 0) {
+ WL_ERR(("TX_POWER_FIXED - dbm is negative..\n"));
+ return -EINVAL;
+ }
+ break;
+ }
+ /* Make sure radio is off or on as far as software is concerned */
+ disable = WL_RADIO_SW_DISABLE << 16;
+ disable = htod32(disable);
+ err = wl_dev_ioctl(ndev, WLC_SET_RADIO, &disable, sizeof(disable));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_RADIO error (%d)\n", err));
+ return err;
+ }
+
+ if (dbm > 0xffff)
+ txpwrmw = 0xffff;
+ else
+ txpwrmw = (u16) dbm;
+ err = wl_dev_intvar_set(ndev, "qtxpower",
+ (s32) (bcm_mw_to_qdbm(txpwrmw)));
+ if (unlikely(err)) {
+ WL_ERR(("qtxpower error (%d)\n", err));
+ return err;
+ }
+ wl->conf->tx_power = dbm;
+
+ return err;
+}
+
+static s32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, s32 *dbm)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct net_device *ndev = wl_to_ndev(wl);
+ s32 txpwrdbm;
+ u8 result;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ err = wl_dev_intvar_get(ndev, "qtxpower", &txpwrdbm);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE);
+ *dbm = (s32) bcm_qdbm_to_mw(result);
+
+ return err;
+}
+
+static s32
+wl_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx)
+{
+ u32 index;
+ s32 wsec;
+ s32 err = 0;
+
+ WL_DBG(("key index (%d)\n", key_idx));
+ CHECK_SYS_UP();
+
+ err = wl_dev_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_GET_WSEC error (%d)\n", err));
+ return err;
+ }
+ wsec = dtoh32(wsec);
+ if (wsec & WEP_ENABLED) {
+ /* Just select a new current key */
+ index = (u32) key_idx;
+ index = htod32(index);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY_PRIMARY, &index,
+ sizeof(index));
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ }
+ }
+ return err;
+}
+
+static s32
+wl_add_keyext(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr, struct key_params *params)
+{
+ struct wl_wsec_key key;
+ s32 err = 0;
+
+ memset(&key, 0, sizeof(key));
+ key.index = (u32) key_idx;
+ /* Instead of bcast for ea address for default wep keys,
+ driver needs it to be Null */
+ if (!ETHER_ISMULTI(mac_addr))
+ memcpy((char *)&key.ea, (void *)mac_addr, ETHER_ADDR_LEN);
+ key.len = (u32) params->key_len;
+ /* check for key index change */
+ if (key.len == 0) {
+ /* key delete */
+ swap_key_from_BE(&key);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (unlikely(err)) {
+ WL_ERR(("key delete error (%d)\n", err));
+ return err;
+ }
+ } else {
+ if (key.len > sizeof(key.data)) {
+ WL_ERR(("Invalid key length (%d)\n", key.len));
+ return -EINVAL;
+ }
+
+ WL_DBG(("Setting the key index %d\n", key.index));
+ memcpy(key.data, params->key, key.len);
+
+ if (params->cipher == WLAN_CIPHER_SUITE_TKIP) {
+ u8 keybuf[8];
+ memcpy(keybuf, &key.data[24], sizeof(keybuf));
+ memcpy(&key.data[24], &key.data[16], sizeof(keybuf));
+ memcpy(&key.data[16], keybuf, sizeof(keybuf));
+ }
+
+ /* if IW_ENCODE_EXT_RX_SEQ_VALID set */
+ if (params->seq && params->seq_len == 6) {
+ /* rx iv */
+ u8 *ivptr;
+ ivptr = (u8 *) params->seq;
+ key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) |
+ (ivptr[3] << 8) | ivptr[2];
+ key.rxiv.lo = (ivptr[1] << 8) | ivptr[0];
+ key.iv_initialized = true;
+ }
+
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ key.algo = CRYPTO_ALGO_WEP1;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP40\n"));
+ break;
+ case WLAN_CIPHER_SUITE_WEP104:
+ key.algo = CRYPTO_ALGO_WEP128;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP104\n"));
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ key.algo = CRYPTO_ALGO_TKIP;
+ WL_DBG(("WLAN_CIPHER_SUITE_TKIP\n"));
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ WL_DBG(("WLAN_CIPHER_SUITE_AES_CMAC\n"));
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ WL_DBG(("WLAN_CIPHER_SUITE_CCMP\n"));
+ break;
+ default:
+ WL_ERR(("Invalid cipher (0x%x)\n", params->cipher));
+ return -EINVAL;
+ }
+ swap_key_from_BE(&key);
+
+ dhd_wait_pend8021x(dev);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_KEY error (%d)\n", err));
+ return err;
+ }
+ }
+ return err;
+}
+
+static s32
+wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr,
+ struct key_params *params)
+{
+ struct wl_wsec_key key;
+ s32 val;
+ s32 wsec;
+ s32 err = 0;
+
+ WL_DBG(("key index (%d)\n", key_idx));
+ CHECK_SYS_UP();
+
+ if (mac_addr)
+ return wl_add_keyext(wiphy, dev, key_idx, mac_addr, params);
+ memset(&key, 0, sizeof(key));
+
+ key.len = (u32) params->key_len;
+ key.index = (u32) key_idx;
+
+ if (unlikely(key.len > sizeof(key.data))) {
+ WL_ERR(("Too long key length (%u)\n", key.len));
+ return -EINVAL;
+ }
+ memcpy(key.data, params->key, key.len);
+
+ key.flags = WL_PRIMARY_KEY;
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ key.algo = CRYPTO_ALGO_WEP1;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP40\n"));
+ break;
+ case WLAN_CIPHER_SUITE_WEP104:
+ key.algo = CRYPTO_ALGO_WEP128;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP104\n"));
+ break;
+ case WLAN_CIPHER_SUITE_TKIP:
+ key.algo = CRYPTO_ALGO_TKIP;
+ WL_DBG(("WLAN_CIPHER_SUITE_TKIP\n"));
+ break;
+ case WLAN_CIPHER_SUITE_AES_CMAC:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ WL_DBG(("WLAN_CIPHER_SUITE_AES_CMAC\n"));
+ break;
+ case WLAN_CIPHER_SUITE_CCMP:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ WL_DBG(("WLAN_CIPHER_SUITE_CCMP\n"));
+ break;
+ default:
+ WL_ERR(("Invalid cipher (0x%x)\n", params->cipher));
+ return -EINVAL;
+ }
+
+ /* Set the new key/index */
+ swap_key_from_BE(&key);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_KEY error (%d)\n", err));
+ return err;
+ }
+
+ val = WEP_ENABLED;
+ err = wl_dev_intvar_get(dev, "wsec", &wsec);
+ if (unlikely(err)) {
+ WL_ERR(("get wsec error (%d)\n", err));
+ return err;
+ }
+ wsec &= ~(WEP_ENABLED);
+ wsec |= val;
+ err = wl_dev_intvar_set(dev, "wsec", wsec);
+ if (unlikely(err)) {
+ WL_ERR(("set wsec error (%d)\n", err));
+ return err;
+ }
+
+ val = 1; /* assume shared key. otherwise 0 */
+ val = htod32(val);
+ err = wl_dev_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_AUTH error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static s32
+wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr)
+{
+ struct wl_wsec_key key;
+ s32 err = 0;
+ s32 val;
+ s32 wsec;
+
+ CHECK_SYS_UP();
+ memset(&key, 0, sizeof(key));
+
+ key.index = (u32) key_idx;
+ key.flags = WL_PRIMARY_KEY;
+ key.algo = CRYPTO_ALGO_OFF;
+
+ WL_DBG(("key index (%d)\n", key_idx));
+ /* Set the new key/index */
+ swap_key_from_BE(&key);
+ err = wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (unlikely(err)) {
+ if (err == -EINVAL) {
+ if (key.index >= DOT11_MAX_DEFAULT_KEYS) {
+ /* we ignore this key index in this case */
+ WL_DBG(("invalid key index (%d)\n", key_idx));
+ }
+ } else {
+ WL_ERR(("WLC_SET_KEY error (%d)\n", err));
+ }
+ return err;
+ }
+
+ val = 0;
+ err = wl_dev_intvar_get(dev, "wsec", &wsec);
+ if (unlikely(err)) {
+ WL_ERR(("get wsec error (%d)\n", err));
+ return err;
+ }
+ wsec &= ~(WEP_ENABLED);
+ wsec |= val;
+ err = wl_dev_intvar_set(dev, "wsec", wsec);
+ if (unlikely(err)) {
+ WL_ERR(("set wsec error (%d)\n", err));
+ return err;
+ }
+
+ val = 0; /* assume open key. otherwise 1 */
+ val = htod32(val);
+ err = wl_dev_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_AUTH error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static s32
+wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev,
+ u8 key_idx, const u8 *mac_addr, void *cookie,
+ void (*callback) (void *cookie, struct key_params * params))
+{
+ struct key_params params;
+ struct wl_wsec_key key;
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct wl_security *sec;
+ s32 wsec;
+ s32 err = 0;
+
+ WL_DBG(("key index (%d)\n", key_idx));
+ CHECK_SYS_UP();
+
+ memset(&key, 0, sizeof(key));
+ key.index = key_idx;
+ swap_key_to_BE(&key);
+ memset(&params, 0, sizeof(params));
+ params.key_len = (u8) min_t(u8, DOT11_MAX_KEY_SIZE, key.len);
+ memcpy(params.key, key.data, params.key_len);
+
+ err = wl_dev_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_GET_WSEC error (%d)\n", err));
+ return err;
+ }
+ wsec = dtoh32(wsec);
+ switch (wsec) {
+ case WEP_ENABLED:
+ sec = wl_read_prof(wl, WL_PROF_SEC);
+ if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP40) {
+ params.cipher = WLAN_CIPHER_SUITE_WEP40;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP40\n"));
+ } else if (sec->cipher_pairwise & WLAN_CIPHER_SUITE_WEP104) {
+ params.cipher = WLAN_CIPHER_SUITE_WEP104;
+ WL_DBG(("WLAN_CIPHER_SUITE_WEP104\n"));
+ }
+ break;
+ case TKIP_ENABLED:
+ params.cipher = WLAN_CIPHER_SUITE_TKIP;
+ WL_DBG(("WLAN_CIPHER_SUITE_TKIP\n"));
+ break;
+ case AES_ENABLED:
+ params.cipher = WLAN_CIPHER_SUITE_AES_CMAC;
+ WL_DBG(("WLAN_CIPHER_SUITE_AES_CMAC\n"));
+ break;
+ default:
+ WL_ERR(("Invalid algo (0x%x)\n", wsec));
+ return -EINVAL;
+ }
+
+ callback(cookie, &params);
+ return err;
+}
+
+static s32
+wl_cfg80211_config_default_mgmt_key(struct wiphy *wiphy,
+ struct net_device *dev, u8 key_idx)
+{
+ WL_INFO(("Not supported\n"));
+ CHECK_SYS_UP();
+ return -EOPNOTSUPP;
+}
+
+static s32
+wl_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev,
+ u8 *mac, struct station_info *sinfo)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ scb_val_t scb_val;
+ int rssi;
+ s32 rate;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ if (unlikely
+ (memcmp(mac, wl_read_prof(wl, WL_PROF_BSSID), ETHER_ADDR_LEN))) {
+ WL_ERR(("Wrong Mac address\n"));
+ return -ENOENT;
+ }
+
+ /* Report the current tx rate */
+ err = wl_dev_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate));
+ if (err) {
+ WL_ERR(("Could not get rate (%d)\n", err));
+ } else {
+ rate = dtoh32(rate);
+ sinfo->filled |= STATION_INFO_TX_BITRATE;
+ sinfo->txrate.legacy = rate * 5;
+ WL_DBG(("Rate %d Mbps\n", (rate / 2)));
+ }
+
+ if (test_bit(WL_STATUS_CONNECTED, &wl->status)) {
+ scb_val.val = 0;
+ err = wl_dev_ioctl(dev, WLC_GET_RSSI, &scb_val,
+ sizeof(scb_val_t));
+ if (unlikely(err)) {
+ WL_ERR(("Could not get rssi (%d)\n", err));
+ return err;
+ }
+ rssi = dtoh32(scb_val.val);
+ sinfo->filled |= STATION_INFO_SIGNAL;
+ sinfo->signal = rssi;
+ WL_DBG(("RSSI %d dBm\n", rssi));
+ }
+
+ return err;
+}
+
+static s32
+wl_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev,
+ bool enabled, s32 timeout)
+{
+ s32 pm;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ pm = enabled ? PM_FAST : PM_OFF;
+ pm = htod32(pm);
+ WL_DBG(("power save %s\n", (pm ? "enabled" : "disabled")));
+ err = wl_dev_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm));
+ if (unlikely(err)) {
+ if (err == -ENODEV)
+ WL_DBG(("net_device is not ready yet\n"));
+ else
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ return err;
+}
+
+static __used u32 wl_find_msb(u16 bit16)
+{
+ u32 ret = 0;
+
+ if (bit16 & 0xff00) {
+ ret += 8;
+ bit16 >>= 8;
+ }
+
+ if (bit16 & 0xf0) {
+ ret += 4;
+ bit16 >>= 4;
+ }
+
+ if (bit16 & 0xc) {
+ ret += 2;
+ bit16 >>= 2;
+ }
+
+ if (bit16 & 2)
+ ret += bit16 & 2;
+ else if (bit16)
+ ret += bit16;
+
+ return ret;
+}
+
+static s32
+wl_cfg80211_set_bitrate_mask(struct wiphy *wiphy, struct net_device *dev,
+ const u8 *addr,
+ const struct cfg80211_bitrate_mask *mask)
+{
+ struct wl_rateset rateset;
+ s32 rate;
+ s32 val;
+ s32 err_bg;
+ s32 err_a;
+ u32 legacy;
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ /* addr param is always NULL. ignore it */
+ /* Get current rateset */
+ err = wl_dev_ioctl(dev, WLC_GET_CURR_RATESET, &rateset,
+ sizeof(rateset));
+ if (unlikely(err)) {
+ WL_ERR(("could not get current rateset (%d)\n", err));
+ return err;
+ }
+
+ rateset.count = dtoh32(rateset.count);
+
+ legacy = wl_find_msb(mask->control[IEEE80211_BAND_2GHZ].legacy);
+ if (!legacy)
+ legacy = wl_find_msb(mask->control[IEEE80211_BAND_5GHZ].legacy);
+
+ val = wl_g_rates[legacy - 1].bitrate * 100000;
+
+ if (val < rateset.count) {
+ /* Select rate by rateset index */
+ rate = rateset.rates[val] & 0x7f;
+ } else {
+ /* Specified rate in bps */
+ rate = val / 500000;
+ }
+
+ WL_DBG(("rate %d mbps\n", (rate / 2)));
+
+ /*
+ *
+ * Set rate override,
+ * Since the is a/b/g-blind, both a/bg_rate are enforced.
+ */
+ err_bg = wl_dev_intvar_set(dev, "bg_rate", rate);
+ err_a = wl_dev_intvar_set(dev, "a_rate", rate);
+ if (unlikely(err_bg && err_a)) {
+ WL_ERR(("could not set fixed rate (%d) (%d)\n", err_bg, err_a));
+ return err_bg | err_a;
+ }
+
+ return err;
+}
+
+static s32 wl_cfg80211_resume(struct wiphy *wiphy)
+{
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ wl_invoke_iscan(wiphy_to_wl(wiphy));
+
+ return err;
+}
+
+static s32 wl_cfg80211_suspend(struct wiphy *wiphy)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct net_device *ndev = wl_to_ndev(wl);
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+
+ set_bit(WL_STATUS_SCAN_ABORTING, &wl->status);
+ wl_term_iscan(wl);
+ if (wl->scan_request) {
+ cfg80211_scan_done(wl->scan_request, true); /* true means
+ abort */
+ wl_set_mpc(ndev, 1);
+ wl->scan_request = NULL;
+ }
+ clear_bit(WL_STATUS_SCANNING, &wl->status);
+ clear_bit(WL_STATUS_SCAN_ABORTING, &wl->status);
+
+ return err;
+}
+
+static __used s32
+wl_update_pmklist(struct net_device *dev, struct wl_pmk_list *pmk_list,
+ s32 err)
+{
+ int i, j;
+
+ WL_DBG(("No of elements %d\n", pmk_list->pmkids.npmkid));
+ for (i = 0; i < pmk_list->pmkids.npmkid; i++) {
+ WL_DBG(("PMKID[%d]: %pM =\n", i,
+ &pmk_list->pmkids.pmkid[i].BSSID));
+ for (j = 0; j < WPA2_PMKID_LEN; j++) {
+ WL_DBG(("%02x\n", pmk_list->pmkids.pmkid[i].PMKID[j]));
+ }
+ }
+ if (likely(!err)) {
+ err = wl_dev_bufvar_set(dev, "pmkid_info", (char *)pmk_list,
+ sizeof(*pmk_list));
+ }
+
+ return err;
+}
+
+static s32
+wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_pmksa *pmksa)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ s32 err = 0;
+ int i;
+
+ CHECK_SYS_UP();
+ for (i = 0; i < wl->pmk_list->pmkids.npmkid; i++)
+ if (!memcmp(pmksa->bssid, &wl->pmk_list->pmkids.pmkid[i].BSSID,
+ ETHER_ADDR_LEN))
+ break;
+ if (i < WL_NUM_PMKIDS_MAX) {
+ memcpy(&wl->pmk_list->pmkids.pmkid[i].BSSID, pmksa->bssid,
+ ETHER_ADDR_LEN);
+ memcpy(&wl->pmk_list->pmkids.pmkid[i].PMKID, pmksa->pmkid,
+ WPA2_PMKID_LEN);
+ if (i == wl->pmk_list->pmkids.npmkid)
+ wl->pmk_list->pmkids.npmkid++;
+ } else {
+ err = -EINVAL;
+ }
+ WL_DBG(("set_pmksa,IW_PMKSA_ADD - PMKID: %pM =\n",
+ &wl->pmk_list->pmkids.pmkid[wl->pmk_list->pmkids.npmkid].BSSID));
+ for (i = 0; i < WPA2_PMKID_LEN; i++) {
+ WL_DBG(("%02x\n",
+ wl->pmk_list->pmkids.pmkid[wl->pmk_list->pmkids.npmkid].
+ PMKID[i]));
+ }
+
+ err = wl_update_pmklist(dev, wl->pmk_list, err);
+
+ return err;
+}
+
+static s32
+wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_pmksa *pmksa)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ struct _pmkid_list pmkid;
+ s32 err = 0;
+ int i;
+
+ CHECK_SYS_UP();
+ memcpy(&pmkid.pmkid[0].BSSID, pmksa->bssid, ETHER_ADDR_LEN);
+ memcpy(&pmkid.pmkid[0].PMKID, pmksa->pmkid, WPA2_PMKID_LEN);
+
+ WL_DBG(("del_pmksa,IW_PMKSA_REMOVE - PMKID: %pM =\n",
+ &pmkid.pmkid[0].BSSID));
+ for (i = 0; i < WPA2_PMKID_LEN; i++) {
+ WL_DBG(("%02x\n", pmkid.pmkid[0].PMKID[i]));
+ }
+
+ for (i = 0; i < wl->pmk_list->pmkids.npmkid; i++)
+ if (!memcmp
+ (pmksa->bssid, &wl->pmk_list->pmkids.pmkid[i].BSSID,
+ ETHER_ADDR_LEN))
+ break;
+
+ if ((wl->pmk_list->pmkids.npmkid > 0)
+ && (i < wl->pmk_list->pmkids.npmkid)) {
+ memset(&wl->pmk_list->pmkids.pmkid[i], 0, sizeof(pmkid_t));
+ for (; i < (wl->pmk_list->pmkids.npmkid - 1); i++) {
+ memcpy(&wl->pmk_list->pmkids.pmkid[i].BSSID,
+ &wl->pmk_list->pmkids.pmkid[i + 1].BSSID,
+ ETHER_ADDR_LEN);
+ memcpy(&wl->pmk_list->pmkids.pmkid[i].PMKID,
+ &wl->pmk_list->pmkids.pmkid[i + 1].PMKID,
+ WPA2_PMKID_LEN);
+ }
+ wl->pmk_list->pmkids.npmkid--;
+ } else {
+ err = -EINVAL;
+ }
+
+ err = wl_update_pmklist(dev, wl->pmk_list, err);
+
+ return err;
+
+}
+
+static s32
+wl_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *dev)
+{
+ struct wl_priv *wl = wiphy_to_wl(wiphy);
+ s32 err = 0;
+
+ CHECK_SYS_UP();
+ memset(wl->pmk_list, 0, sizeof(*wl->pmk_list));
+ err = wl_update_pmklist(dev, wl->pmk_list, err);
+ return err;
+
+}
+
+static struct cfg80211_ops wl_cfg80211_ops = {
+ .change_virtual_intf = wl_cfg80211_change_iface,
+ .scan = wl_cfg80211_scan,
+ .set_wiphy_params = wl_cfg80211_set_wiphy_params,
+ .join_ibss = wl_cfg80211_join_ibss,
+ .leave_ibss = wl_cfg80211_leave_ibss,
+ .get_station = wl_cfg80211_get_station,
+ .set_tx_power = wl_cfg80211_set_tx_power,
+ .get_tx_power = wl_cfg80211_get_tx_power,
+ .add_key = wl_cfg80211_add_key,
+ .del_key = wl_cfg80211_del_key,
+ .get_key = wl_cfg80211_get_key,
+ .set_default_key = wl_cfg80211_config_default_key,
+ .set_default_mgmt_key = wl_cfg80211_config_default_mgmt_key,
+ .set_power_mgmt = wl_cfg80211_set_power_mgmt,
+ .set_bitrate_mask = wl_cfg80211_set_bitrate_mask,
+ .connect = wl_cfg80211_connect,
+ .disconnect = wl_cfg80211_disconnect,
+ .suspend = wl_cfg80211_suspend,
+ .resume = wl_cfg80211_resume,
+ .set_pmksa = wl_cfg80211_set_pmksa,
+ .del_pmksa = wl_cfg80211_del_pmksa,
+ .flush_pmksa = wl_cfg80211_flush_pmksa
+};
+
+static s32 wl_mode_to_nl80211_iftype(s32 mode)
+{
+ s32 err = 0;
+
+ switch (mode) {
+ case WL_MODE_BSS:
+ return NL80211_IFTYPE_STATION;
+ case WL_MODE_IBSS:
+ return NL80211_IFTYPE_ADHOC;
+ default:
+ return NL80211_IFTYPE_UNSPECIFIED;
+ }
+
+ return err;
+}
+
+static struct wireless_dev *wl_alloc_wdev(s32 sizeof_iface,
+ struct device *dev)
+{
+ struct wireless_dev *wdev;
+ s32 err = 0;
+
+ wdev = kzalloc(sizeof(*wdev), GFP_KERNEL);
+ if (unlikely(!wdev)) {
+ WL_ERR(("Could not allocate wireless device\n"));
+ return ERR_PTR(-ENOMEM);
+ }
+ wdev->wiphy =
+ wiphy_new(&wl_cfg80211_ops, sizeof(struct wl_priv) + sizeof_iface);
+ if (unlikely(!wdev->wiphy)) {
+ WL_ERR(("Couldn not allocate wiphy device\n"));
+ err = -ENOMEM;
+ goto wiphy_new_out;
+ }
+ set_wiphy_dev(wdev->wiphy, dev);
+ wdev->wiphy->max_scan_ssids = WL_NUM_SCAN_MAX;
+ wdev->wiphy->max_num_pmkids = WL_NUM_PMKIDS_MAX;
+ wdev->wiphy->interface_modes =
+ BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_ADHOC);
+ wdev->wiphy->bands[IEEE80211_BAND_2GHZ] = &__wl_band_2ghz;
+ wdev->wiphy->bands[IEEE80211_BAND_5GHZ] = &__wl_band_5ghz_a; /* Set
+ * it as 11a by default.
+ * This will be updated with
+ * 11n phy tables in
+ * "ifconfig up"
+ * if phy has 11n capability
+ */
+ wdev->wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+ wdev->wiphy->cipher_suites = __wl_cipher_suites;
+ wdev->wiphy->n_cipher_suites = ARRAY_SIZE(__wl_cipher_suites);
+#ifndef WL_POWERSAVE_DISABLED
+ wdev->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; /* enable power
+ * save mode
+ * by default
+ */
+#else
+ wdev->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
+#endif /* !WL_POWERSAVE_DISABLED */
+ err = wiphy_register(wdev->wiphy);
+ if (unlikely(err < 0)) {
+ WL_ERR(("Couldn not register wiphy device (%d)\n", err));
+ goto wiphy_register_out;
+ }
+ return wdev;
+
+wiphy_register_out:
+ wiphy_free(wdev->wiphy);
+
+wiphy_new_out:
+ kfree(wdev);
+
+ return ERR_PTR(err);
+}
+
+static void wl_free_wdev(struct wl_priv *wl)
+{
+ struct wireless_dev *wdev = wl_to_wdev(wl);
+
+ if (unlikely(!wdev)) {
+ WL_ERR(("wdev is invalid\n"));
+ return;
+ }
+ wiphy_unregister(wdev->wiphy);
+ wiphy_free(wdev->wiphy);
+ kfree(wdev);
+ wl_to_wdev(wl) = NULL;
+}
+
+static s32 wl_inform_bss(struct wl_priv *wl)
+{
+ struct wl_scan_results *bss_list;
+ struct wl_bss_info *bi = NULL; /* must be initialized */
+ s32 err = 0;
+ int i;
+
+ bss_list = wl->bss_list;
+ if (unlikely(bss_list->version != WL_BSS_INFO_VERSION)) {
+ WL_ERR(("Version %d != WL_BSS_INFO_VERSION\n",
+ bss_list->version));
+ return -EOPNOTSUPP;
+ }
+ WL_DBG(("scanned AP count (%d)\n", bss_list->count));
+ bi = next_bss(bss_list, bi);
+ for_each_bss(bss_list, bi, i) {
+ err = wl_inform_single_bss(wl, bi);
+ if (unlikely(err))
+ break;
+ }
+ return err;
+}
+
+static s32 wl_inform_single_bss(struct wl_priv *wl, struct wl_bss_info *bi)
+{
+ struct wiphy *wiphy = wl_to_wiphy(wl);
+ struct ieee80211_mgmt *mgmt;
+ struct ieee80211_channel *channel;
+ struct ieee80211_supported_band *band;
+ struct wl_cfg80211_bss_info *notif_bss_info;
+ struct wl_scan_req *sr = wl_to_sr(wl);
+ struct beacon_proberesp *beacon_proberesp;
+ s32 mgmt_type;
+ u32 signal;
+ u32 freq;
+ s32 err = 0;
+
+ if (unlikely(dtoh32(bi->length) > WL_BSS_INFO_MAX)) {
+ WL_DBG(("Beacon is larger than buffer. Discarding\n"));
+ return err;
+ }
+ notif_bss_info =
+ kzalloc(sizeof(*notif_bss_info) + sizeof(*mgmt) - sizeof(u8) +
+ WL_BSS_INFO_MAX, GFP_KERNEL);
+ if (unlikely(!notif_bss_info)) {
+ WL_ERR(("notif_bss_info alloc failed\n"));
+ return -ENOMEM;
+ }
+ mgmt = (struct ieee80211_mgmt *)notif_bss_info->frame_buf;
+ notif_bss_info->channel =
+ bi->ctl_ch ? bi->ctl_ch : CHSPEC_CHANNEL(bi->chanspec);
+
+ if (notif_bss_info->channel <= CH_MAX_2G_CHANNEL)
+ band = wiphy->bands[IEEE80211_BAND_2GHZ];
+ else
+ band = wiphy->bands[IEEE80211_BAND_5GHZ];
+ notif_bss_info->rssi = bi->RSSI;
+ memcpy(mgmt->bssid, &bi->BSSID, ETHER_ADDR_LEN);
+ mgmt_type = wl->active_scan ?
+ IEEE80211_STYPE_PROBE_RESP : IEEE80211_STYPE_BEACON;
+ if (!memcmp(bi->SSID, sr->ssid.SSID, bi->SSID_len)) {
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ mgmt_type);
+ }
+ beacon_proberesp = wl->active_scan ?
+ (struct beacon_proberesp *)&mgmt->u.probe_resp :
+ (struct beacon_proberesp *)&mgmt->u.beacon;
+ beacon_proberesp->timestamp = 0;
+ beacon_proberesp->beacon_int = cpu_to_le16(bi->beacon_period);
+ beacon_proberesp->capab_info = cpu_to_le16(bi->capability);
+ wl_rst_ie(wl);
+ /*
+ * wl_add_ie is not necessary because it can only add duplicated
+ * SSID, rate information to frame_buf
+ */
+ /*
+ * wl_add_ie(wl, WLAN_EID_SSID, bi->SSID_len, bi->SSID);
+ * wl_add_ie(wl, WLAN_EID_SUPP_RATES, bi->rateset.count,
+ * bi->rateset.rates);
+ */
+ wl_mrg_ie(wl, ((u8 *) bi) + bi->ie_offset, bi->ie_length);
+ wl_cp_ie(wl, beacon_proberesp->variable, WL_BSS_INFO_MAX -
+ offsetof(struct wl_cfg80211_bss_info, frame_buf));
+ notif_bss_info->frame_len =
+ offsetof(struct ieee80211_mgmt,
+ u.beacon.variable) + wl_get_ielen(wl);
+ freq = ieee80211_channel_to_frequency(notif_bss_info->channel);
+ channel = ieee80211_get_channel(wiphy, freq);
+
+ WL_DBG(("SSID : \"%s\", rssi %d, channel %d, capability : 0x04%x, bssid %pM\n",
+ bi->SSID,
+ notif_bss_info->rssi, notif_bss_info->channel,
+ mgmt->u.beacon.capab_info, &bi->BSSID));
+
+ signal = notif_bss_info->rssi * 100;
+ if (unlikely(!cfg80211_inform_bss_frame(wiphy, channel, mgmt,
+ le16_to_cpu
+ (notif_bss_info->frame_len),
+ signal, GFP_KERNEL))) {
+ WL_ERR(("cfg80211_inform_bss_frame error\n"));
+ kfree(notif_bss_info);
+ return -EINVAL;
+ }
+ kfree(notif_bss_info);
+
+ return err;
+}
+
+static bool wl_is_linkup(struct wl_priv *wl, const wl_event_msg_t *e)
+{
+ u32 event = ntoh32(e->event_type);
+ u16 flags = ntoh16(e->flags);
+
+ if (event == WLC_E_LINK) {
+ if (flags & WLC_EVENT_MSG_LINK) {
+ if (wl_is_ibssmode(wl)) {
+ if (wl_is_ibssstarter(wl)) {
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static bool wl_is_linkdown(struct wl_priv *wl, const wl_event_msg_t *e)
+{
+ u32 event = ntoh32(e->event_type);
+ u16 flags = ntoh16(e->flags);
+
+ if (event == WLC_E_DEAUTH_IND || event == WLC_E_DISASSOC_IND) {
+ return true;
+ } else if (event == WLC_E_LINK) {
+ if (!(flags & WLC_EVENT_MSG_LINK))
+ return true;
+ }
+
+ return false;
+}
+
+static bool wl_is_nonetwork(struct wl_priv *wl, const wl_event_msg_t *e)
+{
+ u32 event = ntoh32(e->event_type);
+ u32 status = ntoh32(e->status);
+
+ if (event == WLC_E_SET_SSID || event == WLC_E_LINK) {
+ if (status == WLC_E_STATUS_NO_NETWORKS)
+ return true;
+ }
+
+ return false;
+}
+
+static s32
+wl_notify_connect_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data)
+{
+ bool act;
+ s32 err = 0;
+
+ if (wl_is_linkup(wl, e)) {
+ wl_link_up(wl);
+ if (wl_is_ibssmode(wl)) {
+ cfg80211_ibss_joined(ndev, (s8 *)&e->addr,
+ GFP_KERNEL);
+ WL_DBG(("joined in IBSS network\n"));
+ } else {
+ wl_bss_connect_done(wl, ndev, e, data, true);
+ WL_DBG(("joined in BSS network \"%s\"\n",
+ ((struct wlc_ssid *)
+ wl_read_prof(wl, WL_PROF_SSID))->SSID));
+ }
+ act = true;
+ wl_update_prof(wl, e, &act, WL_PROF_ACT);
+ } else if (wl_is_linkdown(wl, e)) {
+ cfg80211_disconnected(ndev, 0, NULL, 0, GFP_KERNEL);
+ clear_bit(WL_STATUS_CONNECTED, &wl->status);
+ wl_link_down(wl);
+ wl_init_prof(wl->profile);
+ } else if (wl_is_nonetwork(wl, e)) {
+ wl_bss_connect_done(wl, ndev, e, data, false);
+ }
+
+ return err;
+}
+
+static s32
+wl_notify_roaming_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data)
+{
+ bool act;
+ s32 err = 0;
+
+ wl_bss_roaming_done(wl, ndev, e, data);
+ act = true;
+ wl_update_prof(wl, e, &act, WL_PROF_ACT);
+
+ return err;
+}
+
+static __used s32
+wl_dev_bufvar_set(struct net_device *dev, s8 *name, s8 *buf, s32 len)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ u32 buflen;
+
+ buflen = bcm_mkiovar(name, buf, len, wl->ioctl_buf, WL_IOCTL_LEN_MAX);
+ BUG_ON(unlikely(!buflen));
+
+ return wl_dev_ioctl(dev, WLC_SET_VAR, wl->ioctl_buf, buflen);
+}
+
+static s32
+wl_dev_bufvar_get(struct net_device *dev, s8 *name, s8 *buf,
+ s32 buf_len)
+{
+ struct wl_priv *wl = ndev_to_wl(dev);
+ u32 len;
+ s32 err = 0;
+
+ len = bcm_mkiovar(name, NULL, 0, wl->ioctl_buf, WL_IOCTL_LEN_MAX);
+ BUG_ON(unlikely(!len));
+ err = wl_dev_ioctl(dev, WLC_GET_VAR, (void *)wl->ioctl_buf,
+ WL_IOCTL_LEN_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ memcpy(buf, wl->ioctl_buf, buf_len);
+
+ return err;
+}
+
+static s32 wl_get_assoc_ies(struct wl_priv *wl)
+{
+ struct net_device *ndev = wl_to_ndev(wl);
+ struct wl_assoc_ielen *assoc_info;
+ struct wl_connect_info *conn_info = wl_to_conn(wl);
+ u32 req_len;
+ u32 resp_len;
+ s32 err = 0;
+
+ err = wl_dev_bufvar_get(ndev, "assoc_info", wl->extra_buf,
+ WL_ASSOC_INFO_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("could not get assoc info (%d)\n", err));
+ return err;
+ }
+ assoc_info = (struct wl_assoc_ielen *)wl->extra_buf;
+ req_len = assoc_info->req_len;
+ resp_len = assoc_info->resp_len;
+ if (req_len) {
+ err = wl_dev_bufvar_get(ndev, "assoc_req_ies", wl->extra_buf,
+ WL_ASSOC_INFO_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("could not get assoc req (%d)\n", err));
+ return err;
+ }
+ conn_info->req_ie_len = req_len;
+ conn_info->req_ie =
+ kmemdup(wl->extra_buf, conn_info->req_ie_len, GFP_KERNEL);
+ } else {
+ conn_info->req_ie_len = 0;
+ conn_info->req_ie = NULL;
+ }
+ if (resp_len) {
+ err = wl_dev_bufvar_get(ndev, "assoc_resp_ies", wl->extra_buf,
+ WL_ASSOC_INFO_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("could not get assoc resp (%d)\n", err));
+ return err;
+ }
+ conn_info->resp_ie_len = resp_len;
+ conn_info->resp_ie =
+ kmemdup(wl->extra_buf, conn_info->resp_ie_len, GFP_KERNEL);
+ } else {
+ conn_info->resp_ie_len = 0;
+ conn_info->resp_ie = NULL;
+ }
+ WL_DBG(("req len (%d) resp len (%d)\n", conn_info->req_ie_len,
+ conn_info->resp_ie_len));
+
+ return err;
+}
+
+static void wl_ch_to_chanspec(int ch, struct wl_join_params *join_params,
+ size_t *join_params_size)
+{
+ chanspec_t chanspec = 0;
+
+ if (ch != 0) {
+ join_params->params.chanspec_num = 1;
+ join_params->params.chanspec_list[0] = ch;
+
+ if (join_params->params.chanspec_list[0])
+ chanspec |= WL_CHANSPEC_BAND_2G;
+ else
+ chanspec |= WL_CHANSPEC_BAND_5G;
+
+ chanspec |= WL_CHANSPEC_BW_20;
+ chanspec |= WL_CHANSPEC_CTL_SB_NONE;
+
+ *join_params_size += WL_ASSOC_PARAMS_FIXED_SIZE +
+ join_params->params.chanspec_num * sizeof(chanspec_t);
+
+ join_params->params.chanspec_list[0] &= WL_CHANSPEC_CHAN_MASK;
+ join_params->params.chanspec_list[0] |= chanspec;
+ join_params->params.chanspec_list[0] =
+ htodchanspec(join_params->params.chanspec_list[0]);
+
+ join_params->params.chanspec_num =
+ htod32(join_params->params.chanspec_num);
+
+ WL_DBG(("join_params->params.chanspec_list[0]= %#X, channel %d, chanspec %#X\n",
+ join_params->params.chanspec_list[0], ch, chanspec));
+ }
+}
+
+static s32 wl_update_bss_info(struct wl_priv *wl)
+{
+ struct cfg80211_bss *bss;
+ struct wl_bss_info *bi;
+ struct wlc_ssid *ssid;
+ struct bcm_tlv *tim;
+ u16 beacon_interval;
+ u8 dtim_period;
+ size_t ie_len;
+ u8 *ie;
+ s32 err = 0;
+
+ if (wl_is_ibssmode(wl))
+ return err;
+
+ ssid = (struct wlc_ssid *)wl_read_prof(wl, WL_PROF_SSID);
+ bss =
+ cfg80211_get_bss(wl_to_wiphy(wl), NULL, (s8 *)&wl->bssid,
+ ssid->SSID, ssid->SSID_len, WLAN_CAPABILITY_ESS,
+ WLAN_CAPABILITY_ESS);
+
+ rtnl_lock();
+ if (unlikely(!bss)) {
+ WL_DBG(("Could not find the AP\n"));
+ *(u32 *) wl->extra_buf = htod32(WL_EXTRA_BUF_MAX);
+ err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_BSS_INFO,
+ wl->extra_buf, WL_EXTRA_BUF_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("Could not get bss info %d\n", err));
+ goto update_bss_info_out;
+ }
+ bi = (struct wl_bss_info *)(wl->extra_buf + 4);
+ if (unlikely(memcmp(&bi->BSSID, &wl->bssid, ETHER_ADDR_LEN))) {
+ err = -EIO;
+ goto update_bss_info_out;
+ }
+ err = wl_inform_single_bss(wl, bi);
+ if (unlikely(err))
+ goto update_bss_info_out;
+
+ ie = ((u8 *)bi) + bi->ie_offset;
+ ie_len = bi->ie_length;
+ beacon_interval = cpu_to_le16(bi->beacon_period);
+ } else {
+ WL_DBG(("Found the AP in the list - BSSID %pM\n", bss->bssid));
+ ie = bss->information_elements;
+ ie_len = bss->len_information_elements;
+ beacon_interval = bss->beacon_interval;
+ cfg80211_put_bss(bss);
+ }
+
+ tim = bcm_parse_tlvs(ie, ie_len, WLAN_EID_TIM);
+ if (tim) {
+ dtim_period = tim->data[1];
+ } else {
+ /*
+ * active scan was done so we could not get dtim
+ * information out of probe response.
+ * so we speficially query dtim information to dongle.
+ */
+ err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_DTIMPRD,
+ &dtim_period, sizeof(dtim_period));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_GET_DTIMPRD error (%d)\n", err));
+ goto update_bss_info_out;
+ }
+ }
+
+ wl_update_prof(wl, NULL, &beacon_interval, WL_PROF_BEACONINT);
+ wl_update_prof(wl, NULL, &dtim_period, WL_PROF_DTIMPERIOD);
+
+update_bss_info_out:
+ rtnl_unlock();
+ return err;
+}
+
+static s32
+wl_bss_roaming_done(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data)
+{
+ struct wl_connect_info *conn_info = wl_to_conn(wl);
+ s32 err = 0;
+
+ wl_get_assoc_ies(wl);
+ memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN);
+ wl_update_bss_info(wl);
+ cfg80211_roamed(ndev,
+ (u8 *)&wl->bssid,
+ conn_info->req_ie, conn_info->req_ie_len,
+ conn_info->resp_ie, conn_info->resp_ie_len, GFP_KERNEL);
+ WL_DBG(("Report roaming result\n"));
+
+ set_bit(WL_STATUS_CONNECTED, &wl->status);
+
+ return err;
+}
+
+static s32
+wl_bss_connect_done(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data, bool completed)
+{
+ struct wl_connect_info *conn_info = wl_to_conn(wl);
+ s32 err = 0;
+
+ wl_get_assoc_ies(wl);
+ memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN);
+ wl_update_bss_info(wl);
+ if (test_and_clear_bit(WL_STATUS_CONNECTING, &wl->status)) {
+ cfg80211_connect_result(ndev,
+ (u8 *)&wl->bssid,
+ conn_info->req_ie,
+ conn_info->req_ie_len,
+ conn_info->resp_ie,
+ conn_info->resp_ie_len,
+ completed ? WLAN_STATUS_SUCCESS : WLAN_STATUS_AUTH_TIMEOUT,
+ GFP_KERNEL);
+ WL_DBG(("Report connect result - connection %s\n",
+ completed ? "succeeded" : "failed"));
+ } else {
+ cfg80211_roamed(ndev,
+ (u8 *)&wl->bssid,
+ conn_info->req_ie, conn_info->req_ie_len,
+ conn_info->resp_ie, conn_info->resp_ie_len,
+ GFP_KERNEL);
+ WL_DBG(("Report roaming result\n"));
+ }
+ set_bit(WL_STATUS_CONNECTED, &wl->status);
+
+ return err;
+}
+
+static s32
+wl_notify_mic_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data)
+{
+ u16 flags = ntoh16(e->flags);
+ enum nl80211_key_type key_type;
+
+ rtnl_lock();
+ if (flags & WLC_EVENT_MSG_GROUP)
+ key_type = NL80211_KEYTYPE_GROUP;
+ else
+ key_type = NL80211_KEYTYPE_PAIRWISE;
+
+ cfg80211_michael_mic_failure(ndev, (u8 *)&e->addr, key_type, -1,
+ NULL, GFP_KERNEL);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static s32
+wl_notify_scan_status(struct wl_priv *wl, struct net_device *ndev,
+ const wl_event_msg_t *e, void *data)
+{
+ struct channel_info channel_inform;
+ struct wl_scan_results *bss_list;
+ u32 len = WL_SCAN_BUF_MAX;
+ s32 err = 0;
+
+ if (wl->iscan_on && wl->iscan_kickstart)
+ return wl_wakeup_iscan(wl_to_iscan(wl));
+
+ if (unlikely(!test_and_clear_bit(WL_STATUS_SCANNING, &wl->status))) {
+ WL_ERR(("Scan complete while device not scanning\n"));
+ return -EINVAL;
+ }
+ if (unlikely(!wl->scan_request)) {
+ }
+ rtnl_lock();
+ err = wl_dev_ioctl(ndev, WLC_GET_CHANNEL, &channel_inform,
+ sizeof(channel_inform));
+ if (unlikely(err)) {
+ WL_ERR(("scan busy (%d)\n", err));
+ goto scan_done_out;
+ }
+ channel_inform.scan_channel = dtoh32(channel_inform.scan_channel);
+ if (unlikely(channel_inform.scan_channel)) {
+
+ WL_DBG(("channel_inform.scan_channel (%d)\n",
+ channel_inform.scan_channel));
+ }
+ wl->bss_list = wl->scan_results;
+ bss_list = wl->bss_list;
+ memset(bss_list, 0, len);
+ bss_list->buflen = htod32(len);
+ err = wl_dev_ioctl(ndev, WLC_SCAN_RESULTS, bss_list, len);
+ if (unlikely(err)) {
+ WL_ERR(("%s Scan_results error (%d)\n", ndev->name, err));
+ err = -EINVAL;
+ goto scan_done_out;
+ }
+ bss_list->buflen = dtoh32(bss_list->buflen);
+ bss_list->version = dtoh32(bss_list->version);
+ bss_list->count = dtoh32(bss_list->count);
+
+ err = wl_inform_bss(wl);
+ if (err)
+ goto scan_done_out;
+
+scan_done_out:
+ if (wl->scan_request) {
+ cfg80211_scan_done(wl->scan_request, false);
+ wl_set_mpc(ndev, 1);
+ wl->scan_request = NULL;
+ }
+ rtnl_unlock();
+ return err;
+}
+
+static void wl_init_conf(struct wl_conf *conf)
+{
+ conf->mode = (u32)-1;
+ conf->frag_threshold = (u32)-1;
+ conf->rts_threshold = (u32)-1;
+ conf->retry_short = (u32)-1;
+ conf->retry_long = (u32)-1;
+ conf->tx_power = -1;
+}
+
+static void wl_init_prof(struct wl_profile *prof)
+{
+ memset(prof, 0, sizeof(*prof));
+}
+
+static void wl_init_eloop_handler(struct wl_event_loop *el)
+{
+ memset(el, 0, sizeof(*el));
+ el->handler[WLC_E_SCAN_COMPLETE] = wl_notify_scan_status;
+ el->handler[WLC_E_JOIN] = wl_notify_connect_status;
+ el->handler[WLC_E_LINK] = wl_notify_connect_status;
+ el->handler[WLC_E_DEAUTH_IND] = wl_notify_connect_status;
+ el->handler[WLC_E_DISASSOC_IND] = wl_notify_connect_status;
+ el->handler[WLC_E_ASSOC_IND] = wl_notify_connect_status;
+ el->handler[WLC_E_REASSOC_IND] = wl_notify_connect_status;
+ el->handler[WLC_E_ROAM] = wl_notify_roaming_status;
+ el->handler[WLC_E_MIC_ERROR] = wl_notify_mic_status;
+ el->handler[WLC_E_SET_SSID] = wl_notify_connect_status;
+}
+
+static s32 wl_init_priv_mem(struct wl_priv *wl)
+{
+ wl->scan_results = (void *)kzalloc(WL_SCAN_BUF_MAX, GFP_KERNEL);
+ if (unlikely(!wl->scan_results)) {
+ WL_ERR(("Scan results alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->conf = (void *)kzalloc(sizeof(*wl->conf), GFP_KERNEL);
+ if (unlikely(!wl->conf)) {
+ WL_ERR(("wl_conf alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->profile = (void *)kzalloc(sizeof(*wl->profile), GFP_KERNEL);
+ if (unlikely(!wl->profile)) {
+ WL_ERR(("wl_profile alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->bss_info = (void *)kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL);
+ if (unlikely(!wl->bss_info)) {
+ WL_ERR(("Bss information alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->scan_req_int =
+ (void *)kzalloc(sizeof(*wl->scan_req_int), GFP_KERNEL);
+ if (unlikely(!wl->scan_req_int)) {
+ WL_ERR(("Scan req alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->ioctl_buf = (void *)kzalloc(WL_IOCTL_LEN_MAX, GFP_KERNEL);
+ if (unlikely(!wl->ioctl_buf)) {
+ WL_ERR(("Ioctl buf alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->extra_buf = (void *)kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
+ if (unlikely(!wl->extra_buf)) {
+ WL_ERR(("Extra buf alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->iscan = (void *)kzalloc(sizeof(*wl->iscan), GFP_KERNEL);
+ if (unlikely(!wl->iscan)) {
+ WL_ERR(("Iscan buf alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->fw = (void *)kzalloc(sizeof(*wl->fw), GFP_KERNEL);
+ if (unlikely(!wl->fw)) {
+ WL_ERR(("fw object alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+ wl->pmk_list = (void *)kzalloc(sizeof(*wl->pmk_list), GFP_KERNEL);
+ if (unlikely(!wl->pmk_list)) {
+ WL_ERR(("pmk list alloc failed\n"));
+ goto init_priv_mem_out;
+ }
+
+ return 0;
+
+init_priv_mem_out:
+ wl_deinit_priv_mem(wl);
+
+ return -ENOMEM;
+}
+
+static void wl_deinit_priv_mem(struct wl_priv *wl)
+{
+ kfree(wl->scan_results);
+ wl->scan_results = NULL;
+ kfree(wl->bss_info);
+ wl->bss_info = NULL;
+ kfree(wl->conf);
+ wl->conf = NULL;
+ kfree(wl->profile);
+ wl->profile = NULL;
+ kfree(wl->scan_req_int);
+ wl->scan_req_int = NULL;
+ kfree(wl->ioctl_buf);
+ wl->ioctl_buf = NULL;
+ kfree(wl->extra_buf);
+ wl->extra_buf = NULL;
+ kfree(wl->iscan);
+ wl->iscan = NULL;
+ kfree(wl->fw);
+ wl->fw = NULL;
+ kfree(wl->pmk_list);
+ wl->pmk_list = NULL;
+}
+
+static s32 wl_create_event_handler(struct wl_priv *wl)
+{
+ sema_init(&wl->event_sync, 0);
+ wl->event_tsk = kthread_run(wl_event_handler, wl, "wl_event_handler");
+ if (IS_ERR(wl->event_tsk)) {
+ wl->event_tsk = NULL;
+ WL_ERR(("failed to create event thread\n"));
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void wl_destroy_event_handler(struct wl_priv *wl)
+{
+ if (wl->event_tsk) {
+ send_sig(SIGTERM, wl->event_tsk, 1);
+ kthread_stop(wl->event_tsk);
+ wl->event_tsk = NULL;
+ }
+}
+
+static void wl_term_iscan(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl_to_iscan(wl);
+
+ if (wl->iscan_on && iscan->tsk) {
+ iscan->state = WL_ISCAN_STATE_IDLE;
+ send_sig(SIGTERM, iscan->tsk, 1);
+ kthread_stop(iscan->tsk);
+ iscan->tsk = NULL;
+ }
+}
+
+static void wl_notify_iscan_complete(struct wl_iscan_ctrl *iscan, bool aborted)
+{
+ struct wl_priv *wl = iscan_to_wl(iscan);
+ struct net_device *ndev = wl_to_ndev(wl);
+
+ if (unlikely(!test_and_clear_bit(WL_STATUS_SCANNING, &wl->status))) {
+ WL_ERR(("Scan complete while device not scanning\n"));
+ return;
+ }
+ if (likely(wl->scan_request)) {
+ cfg80211_scan_done(wl->scan_request, aborted);
+ wl_set_mpc(ndev, 1);
+ wl->scan_request = NULL;
+ }
+ wl->iscan_kickstart = false;
+}
+
+static s32 wl_wakeup_iscan(struct wl_iscan_ctrl *iscan)
+{
+ if (likely(iscan->state != WL_ISCAN_STATE_IDLE)) {
+ WL_DBG(("wake up iscan\n"));
+ up(&iscan->sync);
+ return 0;
+ }
+
+ return -EIO;
+}
+
+static s32
+wl_get_iscan_results(struct wl_iscan_ctrl *iscan, u32 *status,
+ struct wl_scan_results **bss_list)
+{
+ struct wl_iscan_results list;
+ struct wl_scan_results *results;
+ struct wl_iscan_results *list_buf;
+ s32 err = 0;
+
+ memset(iscan->scan_buf, 0, WL_ISCAN_BUF_MAX);
+ list_buf = (struct wl_iscan_results *)iscan->scan_buf;
+ results = &list_buf->results;
+ results->buflen = WL_ISCAN_RESULTS_FIXED_SIZE;
+ results->version = 0;
+ results->count = 0;
+
+ memset(&list, 0, sizeof(list));
+ list.results.buflen = htod32(WL_ISCAN_BUF_MAX);
+ err = wl_dev_iovar_getbuf(iscan->dev, "iscanresults", &list,
+ WL_ISCAN_RESULTS_FIXED_SIZE, iscan->scan_buf,
+ WL_ISCAN_BUF_MAX);
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+ results->buflen = dtoh32(results->buflen);
+ results->version = dtoh32(results->version);
+ results->count = dtoh32(results->count);
+ WL_DBG(("results->count = %d\n", results->count));
+ WL_DBG(("results->buflen = %d\n", results->buflen));
+ *status = dtoh32(list_buf->status);
+ *bss_list = results;
+
+ return err;
+}
+
+static s32 wl_iscan_done(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl->iscan;
+ s32 err = 0;
+
+ iscan->state = WL_ISCAN_STATE_IDLE;
+ rtnl_lock();
+ wl_inform_bss(wl);
+ wl_notify_iscan_complete(iscan, false);
+ rtnl_unlock();
+
+ return err;
+}
+
+static s32 wl_iscan_pending(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl->iscan;
+ s32 err = 0;
+
+ /* Reschedule the timer */
+ mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000);
+ iscan->timer_on = 1;
+
+ return err;
+}
+
+static s32 wl_iscan_inprogress(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl->iscan;
+ s32 err = 0;
+
+ rtnl_lock();
+ wl_inform_bss(wl);
+ wl_run_iscan(iscan, NULL, WL_SCAN_ACTION_CONTINUE);
+ rtnl_unlock();
+ /* Reschedule the timer */
+ mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000);
+ iscan->timer_on = 1;
+
+ return err;
+}
+
+static s32 wl_iscan_aborted(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl->iscan;
+ s32 err = 0;
+
+ iscan->state = WL_ISCAN_STATE_IDLE;
+ rtnl_lock();
+ wl_notify_iscan_complete(iscan, true);
+ rtnl_unlock();
+
+ return err;
+}
+
+static s32 wl_iscan_thread(void *data)
+{
+ struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1 };
+ struct wl_iscan_ctrl *iscan = (struct wl_iscan_ctrl *)data;
+ struct wl_priv *wl = iscan_to_wl(iscan);
+ struct wl_iscan_eloop *el = &iscan->el;
+ u32 status;
+ int err = 0;
+
+ sched_setscheduler(current, SCHED_FIFO, &param);
+ allow_signal(SIGTERM);
+ status = WL_SCAN_RESULTS_PARTIAL;
+ while (likely(!down_interruptible(&iscan->sync))) {
+ if (kthread_should_stop())
+ break;
+ if (iscan->timer_on) {
+ del_timer_sync(&iscan->timer);
+ iscan->timer_on = 0;
+ }
+ rtnl_lock();
+ err = wl_get_iscan_results(iscan, &status, &wl->bss_list);
+ if (unlikely(err)) {
+ status = WL_SCAN_RESULTS_ABORTED;
+ WL_ERR(("Abort iscan\n"));
+ }
+ rtnl_unlock();
+ el->handler[status] (wl);
+ }
+ if (iscan->timer_on) {
+ del_timer_sync(&iscan->timer);
+ iscan->timer_on = 0;
+ }
+ WL_DBG(("%s was terminated\n", __func__));
+
+ return 0;
+}
+
+static void wl_iscan_timer(unsigned long data)
+{
+ struct wl_iscan_ctrl *iscan = (struct wl_iscan_ctrl *)data;
+
+ if (iscan) {
+ iscan->timer_on = 0;
+ WL_DBG(("timer expired\n"));
+ wl_wakeup_iscan(iscan);
+ }
+}
+
+static s32 wl_invoke_iscan(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl_to_iscan(wl);
+ int err = 0;
+
+ if (wl->iscan_on && !iscan->tsk) {
+ iscan->state = WL_ISCAN_STATE_IDLE;
+ sema_init(&iscan->sync, 0);
+ iscan->tsk = kthread_run(wl_iscan_thread, iscan, "wl_iscan");
+ if (IS_ERR(iscan->tsk)) {
+ WL_ERR(("Could not create iscan thread\n"));
+ iscan->tsk = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ return err;
+}
+
+static void wl_init_iscan_eloop(struct wl_iscan_eloop *el)
+{
+ memset(el, 0, sizeof(*el));
+ el->handler[WL_SCAN_RESULTS_SUCCESS] = wl_iscan_done;
+ el->handler[WL_SCAN_RESULTS_PARTIAL] = wl_iscan_inprogress;
+ el->handler[WL_SCAN_RESULTS_PENDING] = wl_iscan_pending;
+ el->handler[WL_SCAN_RESULTS_ABORTED] = wl_iscan_aborted;
+ el->handler[WL_SCAN_RESULTS_NO_MEM] = wl_iscan_aborted;
+}
+
+static s32 wl_init_iscan(struct wl_priv *wl)
+{
+ struct wl_iscan_ctrl *iscan = wl_to_iscan(wl);
+ int err = 0;
+
+ if (wl->iscan_on) {
+ iscan->dev = wl_to_ndev(wl);
+ iscan->state = WL_ISCAN_STATE_IDLE;
+ wl_init_iscan_eloop(&iscan->el);
+ iscan->timer_ms = WL_ISCAN_TIMER_INTERVAL_MS;
+ init_timer(&iscan->timer);
+ iscan->timer.data = (unsigned long) iscan;
+ iscan->timer.function = wl_iscan_timer;
+ sema_init(&iscan->sync, 0);
+ iscan->tsk = kthread_run(wl_iscan_thread, iscan, "wl_iscan");
+ if (IS_ERR(iscan->tsk)) {
+ WL_ERR(("Could not create iscan thread\n"));
+ iscan->tsk = NULL;
+ return -ENOMEM;
+ }
+ iscan->data = wl;
+ }
+
+ return err;
+}
+
+static void wl_init_fw(struct wl_fw_ctrl *fw)
+{
+ fw->status = 0; /* init fw loading status.
+ 0 means nothing was loaded yet */
+}
+
+static s32 wl_init_priv(struct wl_priv *wl)
+{
+ struct wiphy *wiphy = wl_to_wiphy(wl);
+ s32 err = 0;
+
+ wl->scan_request = NULL;
+ wl->pwr_save = !!(wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT);
+ wl->iscan_on = true; /* iscan on & off switch.
+ we enable iscan per default */
+ wl->roam_on = false; /* roam on & off switch.
+ we enable roam per default */
+
+ wl->iscan_kickstart = false;
+ wl->active_scan = true; /* we do active scan for
+ specific scan per default */
+ wl->dongle_up = false; /* dongle is not up yet */
+ wl_init_eq(wl);
+ err = wl_init_priv_mem(wl);
+ if (unlikely(err))
+ return err;
+ if (unlikely(wl_create_event_handler(wl)))
+ return -ENOMEM;
+ wl_init_eloop_handler(&wl->el);
+ mutex_init(&wl->usr_sync);
+ err = wl_init_iscan(wl);
+ if (unlikely(err))
+ return err;
+ wl_init_fw(wl->fw);
+ wl_init_conf(wl->conf);
+ wl_init_prof(wl->profile);
+ wl_link_down(wl);
+
+ return err;
+}
+
+static void wl_deinit_priv(struct wl_priv *wl)
+{
+ wl_destroy_event_handler(wl);
+ wl->dongle_up = false; /* dongle down */
+ wl_flush_eq(wl);
+ wl_link_down(wl);
+ wl_term_iscan(wl);
+ wl_deinit_priv_mem(wl);
+}
+
+s32 wl_cfg80211_attach(struct net_device *ndev, void *data)
+{
+ struct wireless_dev *wdev;
+ struct wl_priv *wl;
+ struct wl_iface *ci;
+ s32 err = 0;
+
+ if (unlikely(!ndev)) {
+ WL_ERR(("ndev is invaild\n"));
+ return -ENODEV;
+ }
+ wl_cfg80211_dev = kzalloc(sizeof(struct wl_dev), GFP_KERNEL);
+ if (unlikely(!wl_cfg80211_dev)) {
+ WL_ERR(("wl_cfg80211_dev is invalid\n"));
+ return -ENOMEM;
+ }
+ WL_DBG(("func %p\n", wl_cfg80211_get_sdio_func()));
+ wdev = wl_alloc_wdev(sizeof(struct wl_iface), &wl_cfg80211_get_sdio_func()->dev);
+ if (unlikely(IS_ERR(wdev)))
+ return -ENOMEM;
+
+ wdev->iftype = wl_mode_to_nl80211_iftype(WL_MODE_BSS);
+ wl = wdev_to_wl(wdev);
+ wl->wdev = wdev;
+ wl->pub = data;
+ ci = (struct wl_iface *)wl_to_ci(wl);
+ ci->wl = wl;
+ ndev->ieee80211_ptr = wdev;
+ SET_NETDEV_DEV(ndev, wiphy_dev(wdev->wiphy));
+ wdev->netdev = ndev;
+ err = wl_init_priv(wl);
+ if (unlikely(err)) {
+ WL_ERR(("Failed to init iwm_priv (%d)\n", err));
+ goto cfg80211_attach_out;
+ }
+ wl_set_drvdata(wl_cfg80211_dev, ci);
+ set_bit(WL_STATUS_READY, &wl->status);
+
+ return err;
+
+cfg80211_attach_out:
+ wl_free_wdev(wl);
+ return err;
+}
+
+void wl_cfg80211_detach(void)
+{
+ struct wl_priv *wl;
+
+ wl = WL_PRIV_GET();
+
+ wl_deinit_priv(wl);
+ wl_free_wdev(wl);
+ wl_set_drvdata(wl_cfg80211_dev, NULL);
+ kfree(wl_cfg80211_dev);
+ wl_cfg80211_dev = NULL;
+ wl_clear_sdio_func();
+}
+
+static void wl_wakeup_event(struct wl_priv *wl)
+{
+ up(&wl->event_sync);
+}
+
+static s32 wl_event_handler(void *data)
+{
+ struct wl_priv *wl = (struct wl_priv *)data;
+ struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1 };
+ struct wl_event_q *e;
+
+ sched_setscheduler(current, SCHED_FIFO, &param);
+ allow_signal(SIGTERM);
+ while (likely(!down_interruptible(&wl->event_sync))) {
+ if (kthread_should_stop())
+ break;
+ e = wl_deq_event(wl);
+ if (unlikely(!e)) {
+ WL_ERR(("eqeue empty..\n"));
+ BUG();
+ }
+ WL_DBG(("event type (%d)\n", e->etype));
+ if (wl->el.handler[e->etype]) {
+ wl->el.handler[e->etype] (wl, wl_to_ndev(wl), &e->emsg,
+ e->edata);
+ } else {
+ WL_DBG(("Unknown Event (%d): ignoring\n", e->etype));
+ }
+ wl_put_event(e);
+ }
+ WL_DBG(("%s was terminated\n", __func__));
+ return 0;
+}
+
+void
+wl_cfg80211_event(struct net_device *ndev, const wl_event_msg_t * e, void *data)
+{
+ u32 event_type = ntoh32(e->event_type);
+ struct wl_priv *wl = ndev_to_wl(ndev);
+#if (WL_DBG_LEVEL > 0)
+ s8 *estr = (event_type <= sizeof(wl_dbg_estr) / WL_DBG_ESTR_MAX - 1) ?
+ wl_dbg_estr[event_type] : (s8 *) "Unknown";
+#endif /* (WL_DBG_LEVEL > 0) */
+ WL_DBG(("event_type (%d):" "WLC_E_" "%s\n", event_type, estr));
+ if (likely(!wl_enq_event(wl, event_type, e, data)))
+ wl_wakeup_event(wl);
+}
+
+static void wl_init_eq(struct wl_priv *wl)
+{
+ wl_init_eq_lock(wl);
+ INIT_LIST_HEAD(&wl->eq_list);
+}
+
+static void wl_flush_eq(struct wl_priv *wl)
+{
+ struct wl_event_q *e;
+
+ wl_lock_eq(wl);
+ while (!list_empty(&wl->eq_list)) {
+ e = list_first_entry(&wl->eq_list, struct wl_event_q, eq_list);
+ list_del(&e->eq_list);
+ kfree(e);
+ }
+ wl_unlock_eq(wl);
+}
+
+/*
+* retrieve first queued event from head
+*/
+
+static struct wl_event_q *wl_deq_event(struct wl_priv *wl)
+{
+ struct wl_event_q *e = NULL;
+
+ wl_lock_eq(wl);
+ if (likely(!list_empty(&wl->eq_list))) {
+ e = list_first_entry(&wl->eq_list, struct wl_event_q, eq_list);
+ list_del(&e->eq_list);
+ }
+ wl_unlock_eq(wl);
+
+ return e;
+}
+
+/*
+** push event to tail of the queue
+*/
+
+static s32
+wl_enq_event(struct wl_priv *wl, u32 event, const wl_event_msg_t *msg,
+ void *data)
+{
+ struct wl_event_q *e;
+ s32 err = 0;
+
+ e = kzalloc(sizeof(struct wl_event_q), GFP_KERNEL);
+ if (unlikely(!e)) {
+ WL_ERR(("event alloc failed\n"));
+ return -ENOMEM;
+ }
+
+ e->etype = event;
+ memcpy(&e->emsg, msg, sizeof(wl_event_msg_t));
+ if (data) {
+ }
+ wl_lock_eq(wl);
+ list_add_tail(&e->eq_list, &wl->eq_list);
+ wl_unlock_eq(wl);
+
+ return err;
+}
+
+static void wl_put_event(struct wl_event_q *e)
+{
+ kfree(e);
+}
+
+void wl_cfg80211_sdio_func(void *func)
+{
+ cfg80211_sdio_func = (struct sdio_func *)func;
+}
+
+static void wl_clear_sdio_func(void)
+{
+ cfg80211_sdio_func = NULL;
+}
+
+struct sdio_func *wl_cfg80211_get_sdio_func(void)
+{
+ return cfg80211_sdio_func;
+}
+
+static s32 wl_dongle_mode(struct net_device *ndev, s32 iftype)
+{
+ s32 infra = 0;
+ s32 ap = 0;
+ s32 err = 0;
+
+ switch (iftype) {
+ case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_WDS:
+ WL_ERR(("type (%d) : currently we do not support this mode\n",
+ iftype));
+ err = -EINVAL;
+ return err;
+ case NL80211_IFTYPE_ADHOC:
+ break;
+ case NL80211_IFTYPE_STATION:
+ infra = 1;
+ break;
+ default:
+ err = -EINVAL;
+ WL_ERR(("invalid type (%d)\n", iftype));
+ return err;
+ }
+ infra = htod32(infra);
+ ap = htod32(ap);
+ WL_DBG(("%s ap (%d), infra (%d)\n", ndev->name, ap, infra));
+ err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_INFRA error (%d)\n", err));
+ return err;
+ }
+ err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_AP error (%d)\n", err));
+ return err;
+ }
+
+ return -EINPROGRESS;
+}
+
+#ifndef EMBEDDED_PLATFORM
+static s32 wl_dongle_country(struct net_device *ndev, u8 ccode)
+{
+
+ s32 err = 0;
+
+ return err;
+}
+
+static s32 wl_dongle_up(struct net_device *ndev, u32 up)
+{
+ s32 err = 0;
+
+ err = wl_dev_ioctl(ndev, WLC_UP, &up, sizeof(up));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_UP error (%d)\n", err));
+ }
+ return err;
+}
+
+static s32 wl_dongle_power(struct net_device *ndev, u32 power_mode)
+{
+ s32 err = 0;
+
+ err = wl_dev_ioctl(ndev, WLC_SET_PM, &power_mode, sizeof(power_mode));
+ if (unlikely(err)) {
+ WL_ERR(("WLC_SET_PM error (%d)\n", err));
+ }
+ return err;
+}
+
+static s32
+wl_dongle_glom(struct net_device *ndev, u32 glom, u32 dongle_align)
+{
+ s8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+ s32 err = 0;
+
+ /* Match Host and Dongle rx alignment */
+ bcm_mkiovar("bus:txglomalign", (char *)&dongle_align, 4, iovbuf,
+ sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("txglomalign error (%d)\n", err));
+ goto dongle_glom_out;
+ }
+ /* disable glom option per default */
+ bcm_mkiovar("bus:txglom", (char *)&glom, 4, iovbuf, sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("txglom error (%d)\n", err));
+ goto dongle_glom_out;
+ }
+dongle_glom_out:
+ return err;
+}
+
+static s32
+wl_dongle_roam(struct net_device *ndev, u32 roamvar, u32 bcn_timeout)
+{
+ s8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+ s32 err = 0;
+
+ /* Setup timeout if Beacons are lost and roam is
+ off to report link down */
+ if (roamvar) {
+ bcm_mkiovar("bcn_timeout", (char *)&bcn_timeout, 4, iovbuf,
+ sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("bcn_timeout error (%d)\n", err));
+ goto dongle_rom_out;
+ }
+ }
+ /* Enable/Disable built-in roaming to allow supplicant
+ to take care of roaming */
+ bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf, sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("roam_off error (%d)\n", err));
+ goto dongle_rom_out;
+ }
+dongle_rom_out:
+ return err;
+}
+
+static s32 wl_dongle_eventmsg(struct net_device *ndev)
+{
+
+ s8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+ s8 eventmask[WL_EVENTING_MASK_LEN];
+ s32 err = 0;
+
+ /* Setup event_msgs */
+ bcm_mkiovar("event_msgs", eventmask, WL_EVENTING_MASK_LEN, iovbuf,
+ sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_GET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("Get event_msgs error (%d)\n", err));
+ goto dongle_eventmsg_out;
+ }
+ memcpy(eventmask, iovbuf, WL_EVENTING_MASK_LEN);
+
+ setbit(eventmask, WLC_E_SET_SSID);
+ setbit(eventmask, WLC_E_PRUNE);
+ setbit(eventmask, WLC_E_AUTH);
+ setbit(eventmask, WLC_E_REASSOC);
+ setbit(eventmask, WLC_E_REASSOC_IND);
+ setbit(eventmask, WLC_E_DEAUTH_IND);
+ setbit(eventmask, WLC_E_DISASSOC_IND);
+ setbit(eventmask, WLC_E_DISASSOC);
+ setbit(eventmask, WLC_E_JOIN);
+ setbit(eventmask, WLC_E_ASSOC_IND);
+ setbit(eventmask, WLC_E_PSK_SUP);
+ setbit(eventmask, WLC_E_LINK);
+ setbit(eventmask, WLC_E_NDIS_LINK);
+ setbit(eventmask, WLC_E_MIC_ERROR);
+ setbit(eventmask, WLC_E_PMKID_CACHE);
+ setbit(eventmask, WLC_E_TXFAIL);
+ setbit(eventmask, WLC_E_JOIN_START);
+ setbit(eventmask, WLC_E_SCAN_COMPLETE);
+
+ bcm_mkiovar("event_msgs", eventmask, WL_EVENTING_MASK_LEN, iovbuf,
+ sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (unlikely(err)) {
+ WL_ERR(("Set event_msgs error (%d)\n", err));
+ goto dongle_eventmsg_out;
+ }
+
+dongle_eventmsg_out:
+ return err;
+}
+
+static s32
+wl_dongle_scantime(struct net_device *ndev, s32 scan_assoc_time,
+ s32 scan_unassoc_time)
+{
+ s32 err = 0;
+
+ err = wl_dev_ioctl(ndev, WLC_SET_SCAN_CHANNEL_TIME, &scan_assoc_time,
+ sizeof(scan_assoc_time));
+ if (err) {
+ if (err == -EOPNOTSUPP) {
+ WL_INFO(("Scan assoc time is not supported\n"));
+ } else {
+ WL_ERR(("Scan assoc time error (%d)\n", err));
+ }
+ goto dongle_scantime_out;
+ }
+ err = wl_dev_ioctl(ndev, WLC_SET_SCAN_UNASSOC_TIME, &scan_unassoc_time,
+ sizeof(scan_unassoc_time));
+ if (err) {
+ if (err == -EOPNOTSUPP) {
+ WL_INFO(("Scan unassoc time is not supported\n"));
+ } else {
+ WL_ERR(("Scan unassoc time error (%d)\n", err));
+ }
+ goto dongle_scantime_out;
+ }
+
+dongle_scantime_out:
+ return err;
+}
+
+static s32
+wl_dongle_offload(struct net_device *ndev, s32 arpoe, s32 arp_ol)
+{
+ s8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+ s32 err = 0;
+
+ /* Set ARP offload */
+ bcm_mkiovar("arpoe", (char *)&arpoe, 4, iovbuf, sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (err) {
+ if (err == -EOPNOTSUPP)
+ WL_INFO(("arpoe is not supported\n"));
+ else
+ WL_ERR(("arpoe error (%d)\n", err));
+
+ goto dongle_offload_out;
+ }
+ bcm_mkiovar("arp_ol", (char *)&arp_ol, 4, iovbuf, sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (err) {
+ if (err == -EOPNOTSUPP)
+ WL_INFO(("arp_ol is not supported\n"));
+ else
+ WL_ERR(("arp_ol error (%d)\n", err));
+
+ goto dongle_offload_out;
+ }
+
+dongle_offload_out:
+ return err;
+}
+
+static s32 wl_pattern_atoh(s8 *src, s8 *dst)
+{
+ int i;
+ if (strncmp(src, "0x", 2) != 0 && strncmp(src, "0X", 2) != 0) {
+ WL_ERR(("Mask invalid format. Needs to start with 0x\n"));
+ return -1;
+ }
+ src = src + 2; /* Skip past 0x */
+ if (strlen(src) % 2 != 0) {
+ WL_ERR(("Mask invalid format. Needs to be of even length\n"));
+ return -1;
+ }
+ for (i = 0; *src != '\0'; i++) {
+ char num[3];
+ strncpy(num, src, 2);
+ num[2] = '\0';
+ dst[i] = (u8) simple_strtoul(num, NULL, 16);
+ src += 2;
+ }
+ return i;
+}
+
+static s32 wl_dongle_filter(struct net_device *ndev, u32 filter_mode)
+{
+ s8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" +
+ '\0' + bitvec */
+ const s8 *str;
+ struct wl_pkt_filter pkt_filter;
+ struct wl_pkt_filter *pkt_filterp;
+ s32 buf_len;
+ s32 str_len;
+ u32 mask_size;
+ u32 pattern_size;
+ s8 buf[256];
+ s32 err = 0;
+
+/* add a default packet filter pattern */
+ str = "pkt_filter_add";
+ str_len = strlen(str);
+ strncpy(buf, str, str_len);
+ buf[str_len] = '\0';
+ buf_len = str_len + 1;
+
+ pkt_filterp = (struct wl_pkt_filter *)(buf + str_len + 1);
+
+ /* Parse packet filter id. */
+ pkt_filter.id = htod32(100);
+
+ /* Parse filter polarity. */
+ pkt_filter.negate_match = htod32(0);
+
+ /* Parse filter type. */
+ pkt_filter.type = htod32(0);
+
+ /* Parse pattern filter offset. */
+ pkt_filter.u.pattern.offset = htod32(0);
+
+ /* Parse pattern filter mask. */
+ mask_size = htod32(wl_pattern_atoh("0xff",
+ (char *)pkt_filterp->u.pattern.
+ mask_and_pattern));
+
+ /* Parse pattern filter pattern. */
+ pattern_size = htod32(wl_pattern_atoh("0x00",
+ (char *)&pkt_filterp->u.pattern.
+ mask_and_pattern[mask_size]));
+
+ if (mask_size != pattern_size) {
+ WL_ERR(("Mask and pattern not the same size\n"));
+ err = -EINVAL;
+ goto dongle_filter_out;
+ }
+
+ pkt_filter.u.pattern.size_bytes = mask_size;
+ buf_len += WL_PKT_FILTER_FIXED_LEN;
+ buf_len += (WL_PKT_FILTER_PATTERN_FIXED_LEN + 2 * mask_size);
+
+ /* Keep-alive attributes are set in local
+ * variable (keep_alive_pkt), and
+ * then memcpy'ed into buffer (keep_alive_pktp) since there is no
+ * guarantee that the buffer is properly aligned.
+ */
+ memcpy((char *)pkt_filterp, &pkt_filter,
+ WL_PKT_FILTER_FIXED_LEN + WL_PKT_FILTER_PATTERN_FIXED_LEN);
+
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, buf, buf_len);
+ if (err) {
+ if (err == -EOPNOTSUPP) {
+ WL_INFO(("filter not supported\n"));
+ } else {
+ WL_ERR(("filter (%d)\n", err));
+ }
+ goto dongle_filter_out;
+ }
+
+ /* set mode to allow pattern */
+ bcm_mkiovar("pkt_filter_mode", (char *)&filter_mode, 4, iovbuf,
+ sizeof(iovbuf));
+ err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf));
+ if (err) {
+ if (err == -EOPNOTSUPP) {
+ WL_INFO(("filter_mode not supported\n"));
+ } else {
+ WL_ERR(("filter_mode (%d)\n", err));
+ }
+ goto dongle_filter_out;
+ }
+
+dongle_filter_out:
+ return err;
+}
+#endif /* !EMBEDDED_PLATFORM */
+
+s32 wl_config_dongle(struct wl_priv *wl, bool need_lock)
+{
+#ifndef DHD_SDALIGN
+#define DHD_SDALIGN 32
+#endif
+ struct net_device *ndev;
+ struct wireless_dev *wdev;
+ s32 err = 0;
+
+ if (wl->dongle_up)
+ return err;
+
+ ndev = wl_to_ndev(wl);
+ wdev = ndev->ieee80211_ptr;
+ if (need_lock)
+ rtnl_lock();
+
+#ifndef EMBEDDED_PLATFORM
+ err = wl_dongle_up(ndev, 0);
+ if (unlikely(err))
+ goto default_conf_out;
+ err = wl_dongle_country(ndev, 0);
+ if (unlikely(err))
+ goto default_conf_out;
+ err = wl_dongle_power(ndev, PM_FAST);
+ if (unlikely(err))
+ goto default_conf_out;
+ err = wl_dongle_glom(ndev, 0, DHD_SDALIGN);
+ if (unlikely(err))
+ goto default_conf_out;
+ err = wl_dongle_roam(ndev, (wl->roam_on ? 0 : 1), 3);
+ if (unlikely(err))
+ goto default_conf_out;
+ err = wl_dongle_eventmsg(ndev);
+ if (unlikely(err))
+ goto default_conf_out;
+
+ wl_dongle_scantime(ndev, 40, 80);
+ wl_dongle_offload(ndev, 1, 0xf);
+ wl_dongle_filter(ndev, 1);
+#endif /* !EMBEDDED_PLATFORM */
+
+ err = wl_dongle_mode(ndev, wdev->iftype);
+ if (unlikely(err && err != -EINPROGRESS))
+ goto default_conf_out;
+ err = wl_dongle_probecap(wl);
+ if (unlikely(err))
+ goto default_conf_out;
+
+ /* -EINPROGRESS: Call commit handler */
+
+default_conf_out:
+ if (need_lock)
+ rtnl_unlock();
+
+ wl->dongle_up = true;
+
+ return err;
+
+}
+
+static s32 wl_update_wiphybands(struct wl_priv *wl)
+{
+ struct wiphy *wiphy;
+ s32 phy_list;
+ s8 phy;
+ s32 err = 0;
+
+ err = wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_PHYLIST, &phy_list,
+ sizeof(phy_list));
+ if (unlikely(err)) {
+ WL_ERR(("error (%d)\n", err));
+ return err;
+ }
+
+ phy = ((char *)&phy_list)[1];
+ WL_DBG(("%c phy\n", phy));
+ if (phy == 'n' || phy == 'a') {
+ wiphy = wl_to_wiphy(wl);
+ wiphy->bands[IEEE80211_BAND_5GHZ] = &__wl_band_5ghz_n;
+ }
+
+ return err;
+}
+
+static s32 __wl_cfg80211_up(struct wl_priv *wl)
+{
+ s32 err = 0;
+
+ wl_debugfs_add_netdev_params(wl);
+
+ err = wl_config_dongle(wl, false);
+ if (unlikely(err))
+ return err;
+
+ wl_invoke_iscan(wl);
+ set_bit(WL_STATUS_READY, &wl->status);
+ return err;
+}
+
+static s32 __wl_cfg80211_down(struct wl_priv *wl)
+{
+ s32 err = 0;
+
+ /* Check if cfg80211 interface is already down */
+ if (!test_bit(WL_STATUS_READY, &wl->status))
+ return err; /* it is even not ready */
+
+ set_bit(WL_STATUS_SCAN_ABORTING, &wl->status);
+ wl_term_iscan(wl);
+ if (wl->scan_request) {
+ cfg80211_scan_done(wl->scan_request, true); /* true
+ means abort */
+ /* wl_set_mpc(wl_to_ndev(wl), 1); */ /* BUG
+ * this operation cannot help
+ * but here because sdio
+ * is already down through
+ * rmmod process.
+ * Need to figure out how to
+ * address this issue
+ */
+ wl->scan_request = NULL;
+ }
+ clear_bit(WL_STATUS_READY, &wl->status);
+ clear_bit(WL_STATUS_SCANNING, &wl->status);
+ clear_bit(WL_STATUS_SCAN_ABORTING, &wl->status);
+ clear_bit(WL_STATUS_CONNECTED, &wl->status);
+
+ wl_debugfs_remove_netdev(wl);
+
+ return err;
+}
+
+s32 wl_cfg80211_up(void)
+{
+ struct wl_priv *wl;
+ s32 err = 0;
+
+ wl = WL_PRIV_GET();
+ mutex_lock(&wl->usr_sync);
+ err = __wl_cfg80211_up(wl);
+ mutex_unlock(&wl->usr_sync);
+
+ return err;
+}
+
+s32 wl_cfg80211_down(void)
+{
+ struct wl_priv *wl;
+ s32 err = 0;
+
+ wl = WL_PRIV_GET();
+ mutex_lock(&wl->usr_sync);
+ err = __wl_cfg80211_down(wl);
+ mutex_unlock(&wl->usr_sync);
+
+ return err;
+}
+
+static s32 wl_dongle_probecap(struct wl_priv *wl)
+{
+ s32 err = 0;
+
+ err = wl_update_wiphybands(wl);
+ if (unlikely(err))
+ return err;
+
+ return err;
+}
+
+static void *wl_read_prof(struct wl_priv *wl, s32 item)
+{
+ switch (item) {
+ case WL_PROF_SEC:
+ return &wl->profile->sec;
+ case WL_PROF_ACT:
+ return &wl->profile->active;
+ case WL_PROF_BSSID:
+ return &wl->profile->bssid;
+ case WL_PROF_SSID:
+ return &wl->profile->ssid;
+ }
+ WL_ERR(("invalid item (%d)\n", item));
+ return NULL;
+}
+
+static s32
+wl_update_prof(struct wl_priv *wl, const wl_event_msg_t *e, void *data,
+ s32 item)
+{
+ s32 err = 0;
+ struct wlc_ssid *ssid;
+
+ switch (item) {
+ case WL_PROF_SSID:
+ ssid = (wlc_ssid_t *) data;
+ memset(wl->profile->ssid.SSID, 0,
+ sizeof(wl->profile->ssid.SSID));
+ memcpy(wl->profile->ssid.SSID, ssid->SSID, ssid->SSID_len);
+ wl->profile->ssid.SSID_len = ssid->SSID_len;
+ break;
+ case WL_PROF_BSSID:
+ if (data)
+ memcpy(wl->profile->bssid, data, ETHER_ADDR_LEN);
+ else
+ memset(wl->profile->bssid, 0, ETHER_ADDR_LEN);
+ break;
+ case WL_PROF_SEC:
+ memcpy(&wl->profile->sec, data, sizeof(wl->profile->sec));
+ break;
+ case WL_PROF_ACT:
+ wl->profile->active = *(bool *)data;
+ break;
+ case WL_PROF_BEACONINT:
+ wl->profile->beacon_interval = *(u16 *)data;
+ break;
+ case WL_PROF_DTIMPERIOD:
+ wl->profile->dtim_period = *(u8 *)data;
+ break;
+ default:
+ WL_ERR(("unsupported item (%d)\n", item));
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+void wl_cfg80211_dbg_level(u32 level)
+{
+ /*
+ * prohibit to change debug level
+ * by insmod parameter.
+ * eventually debug level will be configured
+ * in compile time by using CONFIG_XXX
+ */
+ /* wl_dbg_level = level; */
+}
+
+static bool wl_is_ibssmode(struct wl_priv *wl)
+{
+ return wl->conf->mode == WL_MODE_IBSS;
+}
+
+static bool wl_is_ibssstarter(struct wl_priv *wl)
+{
+ return wl->ibss_starter;
+}
+
+static void wl_rst_ie(struct wl_priv *wl)
+{
+ struct wl_ie *ie = wl_to_ie(wl);
+
+ ie->offset = 0;
+}
+
+static __used s32 wl_add_ie(struct wl_priv *wl, u8 t, u8 l, u8 *v)
+{
+ struct wl_ie *ie = wl_to_ie(wl);
+ s32 err = 0;
+
+ if (unlikely(ie->offset + l + 2 > WL_TLV_INFO_MAX)) {
+ WL_ERR(("ei crosses buffer boundary\n"));
+ return -ENOSPC;
+ }
+ ie->buf[ie->offset] = t;
+ ie->buf[ie->offset + 1] = l;
+ memcpy(&ie->buf[ie->offset + 2], v, l);
+ ie->offset += l + 2;
+
+ return err;
+}
+
+static s32 wl_mrg_ie(struct wl_priv *wl, u8 *ie_stream, u16 ie_size)
+{
+ struct wl_ie *ie = wl_to_ie(wl);
+ s32 err = 0;
+
+ if (unlikely(ie->offset + ie_size > WL_TLV_INFO_MAX)) {
+ WL_ERR(("ei_stream crosses buffer boundary\n"));
+ return -ENOSPC;
+ }
+ memcpy(&ie->buf[ie->offset], ie_stream, ie_size);
+ ie->offset += ie_size;
+
+ return err;
+}
+
+static s32 wl_cp_ie(struct wl_priv *wl, u8 *dst, u16 dst_size)
+{
+ struct wl_ie *ie = wl_to_ie(wl);
+ s32 err = 0;
+
+ if (unlikely(ie->offset > dst_size)) {
+ WL_ERR(("dst_size is not enough\n"));
+ return -ENOSPC;
+ }
+ memcpy(dst, &ie->buf[0], ie->offset);
+
+ return err;
+}
+
+static u32 wl_get_ielen(struct wl_priv *wl)
+{
+ struct wl_ie *ie = wl_to_ie(wl);
+
+ return ie->offset;
+}
+
+static void wl_link_up(struct wl_priv *wl)
+{
+ wl->link_up = true;
+}
+
+static void wl_link_down(struct wl_priv *wl)
+{
+ struct wl_connect_info *conn_info = wl_to_conn(wl);
+
+ wl->link_up = false;
+ kfree(conn_info->req_ie);
+ conn_info->req_ie = NULL;
+ conn_info->req_ie_len = 0;
+ kfree(conn_info->resp_ie);
+ conn_info->resp_ie = NULL;
+ conn_info->resp_ie_len = 0;
+}
+
+static void wl_lock_eq(struct wl_priv *wl)
+{
+ spin_lock_irq(&wl->eq_lock);
+}
+
+static void wl_unlock_eq(struct wl_priv *wl)
+{
+ spin_unlock_irq(&wl->eq_lock);
+}
+
+static void wl_init_eq_lock(struct wl_priv *wl)
+{
+ spin_lock_init(&wl->eq_lock);
+}
+
+static void wl_delay(u32 ms)
+{
+ if (ms < 1000 / HZ) {
+ cond_resched();
+ mdelay(ms);
+ } else {
+ msleep(ms);
+ }
+}
+
+static void wl_set_drvdata(struct wl_dev *dev, void *data)
+{
+ dev->driver_data = data;
+}
+
+static void *wl_get_drvdata(struct wl_dev *dev)
+{
+ return dev->driver_data;
+}
+
+s32 wl_cfg80211_read_fw(s8 *buf, u32 size)
+{
+ const struct firmware *fw_entry;
+ struct wl_priv *wl;
+
+ wl = WL_PRIV_GET();
+
+ fw_entry = wl->fw->fw_entry;
+
+ if (fw_entry->size < wl->fw->ptr + size)
+ size = fw_entry->size - wl->fw->ptr;
+
+ memcpy(buf, &fw_entry->data[wl->fw->ptr], size);
+ wl->fw->ptr += size;
+ return size;
+}
+
+void wl_cfg80211_release_fw(void)
+{
+ struct wl_priv *wl;
+
+ wl = WL_PRIV_GET();
+ release_firmware(wl->fw->fw_entry);
+ wl->fw->ptr = 0;
+}
+
+void *wl_cfg80211_request_fw(s8 *file_name)
+{
+ struct wl_priv *wl;
+ const struct firmware *fw_entry = NULL;
+ s32 err = 0;
+
+ WL_DBG(("file name : \"%s\"\n", file_name));
+ wl = WL_PRIV_GET();
+
+ if (!test_bit(WL_FW_LOADING_DONE, &wl->fw->status)) {
+ err = request_firmware(&wl->fw->fw_entry, file_name,
+ &wl_cfg80211_get_sdio_func()->dev);
+ if (unlikely(err)) {
+ WL_ERR(("Could not download fw (%d)\n", err));
+ goto req_fw_out;
+ }
+ set_bit(WL_FW_LOADING_DONE, &wl->fw->status);
+ fw_entry = wl->fw->fw_entry;
+ if (fw_entry) {
+ WL_DBG(("fw size (%zd), data (%p)\n", fw_entry->size,
+ fw_entry->data));
+ }
+ } else if (!test_bit(WL_NVRAM_LOADING_DONE, &wl->fw->status)) {
+ err = request_firmware(&wl->fw->fw_entry, file_name,
+ &wl_cfg80211_get_sdio_func()->dev);
+ if (unlikely(err)) {
+ WL_ERR(("Could not download nvram (%d)\n", err));
+ goto req_fw_out;
+ }
+ set_bit(WL_NVRAM_LOADING_DONE, &wl->fw->status);
+ fw_entry = wl->fw->fw_entry;
+ if (fw_entry) {
+ WL_DBG(("nvram size (%zd), data (%p)\n", fw_entry->size,
+ fw_entry->data));
+ }
+ } else {
+ WL_DBG(("Downloading already done. Nothing to do more\n"));
+ err = -EPERM;
+ }
+
+req_fw_out:
+ if (unlikely(err)) {
+ return NULL;
+ }
+ wl->fw->ptr = 0;
+ return (void *)fw_entry->data;
+}
+
+s8 *wl_cfg80211_get_fwname(void)
+{
+ struct wl_priv *wl;
+
+ wl = WL_PRIV_GET();
+ strcpy(wl->fw->fw_name, WL_4329_FW_FILE);
+ return wl->fw->fw_name;
+}
+
+s8 *wl_cfg80211_get_nvramname(void)
+{
+ struct wl_priv *wl;
+
+ wl = WL_PRIV_GET();
+ strcpy(wl->fw->nvram_name, WL_4329_NVRAM_FILE);
+ return wl->fw->nvram_name;
+}
+
+static void wl_set_mpc(struct net_device *ndev, int mpc)
+{
+ s32 err = 0;
+
+ err = wl_dev_intvar_set(ndev, "mpc", mpc);
+ if (unlikely(err)) {
+ WL_ERR(("fail to set mpc\n"));
+ return;
+ }
+ WL_DBG(("MPC : %d\n", mpc));
+}
+
+static int wl_debugfs_add_netdev_params(struct wl_priv *wl)
+{
+ char buf[10+IFNAMSIZ];
+ struct dentry *fd;
+ s32 err = 0;
+
+ sprintf(buf, "netdev:%s", wl_to_ndev(wl)->name);
+ wl->debugfsdir = debugfs_create_dir(buf, wl_to_wiphy(wl)->debugfsdir);
+
+ fd = debugfs_create_u16("beacon_int", S_IRUGO, wl->debugfsdir,
+ (u16 *)&wl->profile->beacon_interval);
+ if (!fd) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ fd = debugfs_create_u8("dtim_period", S_IRUGO, wl->debugfsdir,
+ (u8 *)&wl->profile->dtim_period);
+ if (!fd) {
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+err_out:
+ return err;
+}
+
+static void wl_debugfs_remove_netdev(struct wl_priv *wl)
+{
+ debugfs_remove_recursive(wl->debugfsdir);
+ wl->debugfsdir = NULL;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h
new file mode 100644
index 000000000000..770e63f0c8ef
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _wl_cfg80211_h_
+#define _wl_cfg80211_h_
+
+#include <linux/wireless.h>
+#include <linux/wireless.h>
+#include <net/cfg80211.h>
+#include <proto/ethernet.h>
+#include <wlioctl.h>
+
+struct wl_conf;
+struct wl_iface;
+struct wl_priv;
+struct wl_security;
+struct wl_ibss;
+
+#if defined(IL_BIGENDIAN)
+#include <bcmendian.h>
+#define htod32(i) (bcmswap32(i))
+#define htod16(i) (bcmswap16(i))
+#define dtoh32(i) (bcmswap32(i))
+#define dtoh16(i) (bcmswap16(i))
+#define htodchanspec(i) htod16(i)
+#define dtohchanspec(i) dtoh16(i)
+#else
+#define htod32(i) i
+#define htod16(i) i
+#define dtoh32(i) i
+#define dtoh16(i) i
+#define htodchanspec(i) i
+#define dtohchanspec(i) i
+#endif
+
+#define WL_DBG_NONE 0
+#define WL_DBG_DBG (1 << 2)
+#define WL_DBG_INFO (1 << 1)
+#define WL_DBG_ERR (1 << 0)
+#define WL_DBG_MASK ((WL_DBG_DBG | WL_DBG_INFO | WL_DBG_ERR) << 1)
+
+#define WL_DBG_LEVEL 1 /* 0 invalidates all debug messages.
+ default is 1 */
+#define WL_ERR(args) \
+do { \
+ if (wl_dbg_level & WL_DBG_ERR) { \
+ if (net_ratelimit()) { \
+ printk(KERN_ERR "ERROR @%s : ", __func__); \
+ printk args; \
+ } \
+ } \
+} while (0)
+#define WL_INFO(args) \
+do { \
+ if (wl_dbg_level & WL_DBG_INFO) { \
+ if (net_ratelimit()) { \
+ printk(KERN_ERR "INFO @%s : ", __func__); \
+ printk args; \
+ } \
+ } \
+} while (0)
+#if (WL_DBG_LEVEL > 0)
+#define WL_DBG(args) \
+do { \
+ if (wl_dbg_level & WL_DBG_DBG) { \
+ printk(KERN_ERR "DEBUG @%s :", __func__); \
+ printk args; \
+ } \
+} while (0)
+#else /* !(WL_DBG_LEVEL > 0) */
+#define WL_DBG(args)
+#endif /* (WL_DBG_LEVEL > 0) */
+
+#define WL_SCAN_RETRY_MAX 3 /* used for ibss scan */
+#define WL_NUM_SCAN_MAX 1
+#define WL_NUM_PMKIDS_MAX MAXPMKID /* will be used
+ * for 2.6.33 kernel
+ * or later
+ */
+#define WL_SCAN_BUF_MAX (1024 * 8)
+#define WL_TLV_INFO_MAX 1024
+#define WL_BSS_INFO_MAX 2048
+#define WL_ASSOC_INFO_MAX 512 /*
+ * needs to grab assoc info from dongle to
+ * report it to cfg80211 through "connect"
+ * event
+ */
+#define WL_IOCTL_LEN_MAX 1024
+#define WL_EXTRA_BUF_MAX 2048
+#define WL_ISCAN_BUF_MAX 2048 /*
+ * the buf lengh can be WLC_IOCTL_MAXLEN (8K)
+ * to reduce iteration
+ */
+#define WL_ISCAN_TIMER_INTERVAL_MS 3000
+#define WL_SCAN_ERSULTS_LAST (WL_SCAN_RESULTS_NO_MEM+1)
+#define WL_AP_MAX 256 /* virtually unlimitted as long
+ * as kernel memory allows
+ */
+#define WL_FILE_NAME_MAX 256
+
+/* dongle status */
+enum wl_status {
+ WL_STATUS_READY,
+ WL_STATUS_SCANNING,
+ WL_STATUS_SCAN_ABORTING,
+ WL_STATUS_CONNECTING,
+ WL_STATUS_CONNECTED
+};
+
+/* wi-fi mode */
+enum wl_mode {
+ WL_MODE_BSS,
+ WL_MODE_IBSS,
+ WL_MODE_AP
+};
+
+/* dongle profile list */
+enum wl_prof_list {
+ WL_PROF_MODE,
+ WL_PROF_SSID,
+ WL_PROF_SEC,
+ WL_PROF_IBSS,
+ WL_PROF_BAND,
+ WL_PROF_BSSID,
+ WL_PROF_ACT,
+ WL_PROF_BEACONINT,
+ WL_PROF_DTIMPERIOD
+};
+
+/* dongle iscan state */
+enum wl_iscan_state {
+ WL_ISCAN_STATE_IDLE,
+ WL_ISCAN_STATE_SCANING
+};
+
+/* fw downloading status */
+enum wl_fw_status {
+ WL_FW_LOADING_DONE,
+ WL_NVRAM_LOADING_DONE
+};
+
+/* beacon / probe_response */
+struct beacon_proberesp {
+ __le64 timestamp;
+ __le16 beacon_int;
+ __le16 capab_info;
+ u8 variable[0];
+} __attribute__ ((packed));
+
+/* dongle configuration */
+struct wl_conf {
+ u32 mode; /* adhoc , infrastructure or ap */
+ u32 frag_threshold;
+ u32 rts_threshold;
+ u32 retry_short;
+ u32 retry_long;
+ s32 tx_power;
+ struct ieee80211_channel channel;
+};
+
+/* cfg80211 main event loop */
+struct wl_event_loop {
+ s32(*handler[WLC_E_LAST]) (struct wl_priv *wl,
+ struct net_device *ndev,
+ const wl_event_msg_t *e, void *data);
+};
+
+/* representing interface of cfg80211 plane */
+struct wl_iface {
+ struct wl_priv *wl;
+};
+
+struct wl_dev {
+ void *driver_data; /* to store cfg80211 object information */
+};
+
+/* bss inform structure for cfg80211 interface */
+struct wl_cfg80211_bss_info {
+ u16 band;
+ u16 channel;
+ s16 rssi;
+ u16 frame_len;
+ u8 frame_buf[1];
+};
+
+/* basic structure of scan request */
+struct wl_scan_req {
+ struct wlc_ssid ssid;
+};
+
+/* basic structure of information element */
+struct wl_ie {
+ u16 offset;
+ u8 buf[WL_TLV_INFO_MAX];
+};
+
+/* event queue for cfg80211 main event */
+struct wl_event_q {
+ struct list_head eq_list;
+ u32 etype;
+ wl_event_msg_t emsg;
+ s8 edata[1];
+};
+
+/* security information with currently associated ap */
+struct wl_security {
+ u32 wpa_versions;
+ u32 auth_type;
+ u32 cipher_pairwise;
+ u32 cipher_group;
+ u32 wpa_auth;
+};
+
+/* ibss information for currently joined ibss network */
+struct wl_ibss {
+ u8 beacon_interval; /* in millisecond */
+ u8 atim; /* in millisecond */
+ s8 join_only;
+ u8 band;
+ u8 channel;
+};
+
+/* dongle profile */
+struct wl_profile {
+ u32 mode;
+ struct wlc_ssid ssid;
+ u8 bssid[ETHER_ADDR_LEN];
+ u16 beacon_interval;
+ u8 dtim_period;
+ struct wl_security sec;
+ struct wl_ibss ibss;
+ s32 band;
+ bool active;
+};
+
+/* dongle iscan event loop */
+struct wl_iscan_eloop {
+ s32(*handler[WL_SCAN_ERSULTS_LAST]) (struct wl_priv *wl);
+};
+
+/* dongle iscan controller */
+struct wl_iscan_ctrl {
+ struct net_device *dev;
+ struct timer_list timer;
+ u32 timer_ms;
+ u32 timer_on;
+ s32 state;
+ struct task_struct *tsk;
+ struct semaphore sync;
+ struct wl_iscan_eloop el;
+ void *data;
+ s8 ioctl_buf[WLC_IOCTL_SMLEN];
+ s8 scan_buf[WL_ISCAN_BUF_MAX];
+};
+
+/* association inform */
+struct wl_connect_info {
+ u8 *req_ie;
+ s32 req_ie_len;
+ u8 *resp_ie;
+ s32 resp_ie_len;
+};
+
+/* firmware /nvram downloading controller */
+struct wl_fw_ctrl {
+ const struct firmware *fw_entry;
+ unsigned long status;
+ u32 ptr;
+ s8 fw_name[WL_FILE_NAME_MAX];
+ s8 nvram_name[WL_FILE_NAME_MAX];
+};
+
+/* assoc ie length */
+struct wl_assoc_ielen {
+ u32 req_len;
+ u32 resp_len;
+};
+
+/* wpa2 pmk list */
+struct wl_pmk_list {
+ pmkid_list_t pmkids;
+ pmkid_t foo[MAXPMKID - 1];
+};
+
+/* dongle private data of cfg80211 interface */
+struct wl_priv {
+ struct wireless_dev *wdev; /* representing wl cfg80211 device */
+ struct wl_conf *conf; /* dongle configuration */
+ struct cfg80211_scan_request *scan_request; /* scan request
+ object */
+ struct wl_event_loop el; /* main event loop */
+ struct list_head eq_list; /* used for event queue */
+ spinlock_t eq_lock; /* for event queue synchronization */
+ struct mutex usr_sync; /* maily for dongle up/down synchronization */
+ struct wl_scan_results *bss_list; /* bss_list holding scanned
+ ap information */
+ struct wl_scan_results *scan_results;
+ struct wl_scan_req *scan_req_int; /* scan request object for
+ internal purpose */
+ struct wl_cfg80211_bss_info *bss_info; /* bss information for
+ cfg80211 layer */
+ struct wl_ie ie; /* information element object for
+ internal purpose */
+ struct ether_addr bssid; /* bssid of currently engaged network */
+ struct semaphore event_sync; /* for synchronization of main event
+ thread */
+ struct wl_profile *profile; /* holding dongle profile */
+ struct wl_iscan_ctrl *iscan; /* iscan controller */
+ struct wl_connect_info conn_info; /* association information
+ container */
+ struct wl_fw_ctrl *fw; /* control firwmare / nvram paramter
+ downloading */
+ struct wl_pmk_list *pmk_list; /* wpa2 pmk list */
+ struct task_struct *event_tsk; /* task of main event handler thread */
+ unsigned long status; /* current dongle status */
+ void *pub;
+ u32 channel; /* current channel */
+ bool iscan_on; /* iscan on/off switch */
+ bool iscan_kickstart; /* indicate iscan already started */
+ bool active_scan; /* current scan mode */
+ bool ibss_starter; /* indicates this sta is ibss starter */
+ bool link_up; /* link/connection up flag */
+ bool pwr_save; /* indicate whether dongle to support
+ power save mode */
+ bool dongle_up; /* indicate whether dongle up or not */
+ bool roam_on; /* on/off switch for dongle self-roaming */
+ bool scan_tried; /* indicates if first scan attempted */
+ u8 *ioctl_buf; /* ioctl buffer */
+ u8 *extra_buf; /* maily to grab assoc information */
+ struct dentry *debugfsdir;
+ u8 ci[0] __attribute__ ((__aligned__(NETDEV_ALIGN)));
+};
+
+#define wl_to_dev(w) (wiphy_dev(wl->wdev->wiphy))
+#define wl_to_wiphy(w) (w->wdev->wiphy)
+#define wiphy_to_wl(w) ((struct wl_priv *)(wiphy_priv(w)))
+#define wl_to_wdev(w) (w->wdev)
+#define wdev_to_wl(w) ((struct wl_priv *)(wdev_priv(w)))
+#define wl_to_ndev(w) (w->wdev->netdev)
+#define ndev_to_wl(n) (wdev_to_wl(n->ieee80211_ptr))
+#define ci_to_wl(c) (ci->wl)
+#define wl_to_ci(w) (&w->ci)
+#define wl_to_sr(w) (w->scan_req_int)
+#define wl_to_ie(w) (&w->ie)
+#define iscan_to_wl(i) ((struct wl_priv *)(i->data))
+#define wl_to_iscan(w) (w->iscan)
+#define wl_to_conn(w) (&w->conn_info)
+
+static inline struct wl_bss_info *next_bss(struct wl_scan_results *list,
+ struct wl_bss_info *bss)
+{
+ return bss = bss ?
+ (struct wl_bss_info *)((unsigned long)bss +
+ dtoh32(bss->length)) : list->bss_info;
+}
+
+#define for_each_bss(list, bss, __i) \
+ for (__i = 0; __i < list->count && __i < WL_AP_MAX; __i++, bss = next_bss(list, bss))
+
+extern s32 wl_cfg80211_attach(struct net_device *ndev, void *data);
+extern void wl_cfg80211_detach(void);
+/* event handler from dongle */
+extern void wl_cfg80211_event(struct net_device *ndev, const wl_event_msg_t *e,
+ void *data);
+extern void wl_cfg80211_sdio_func(void *func); /* set sdio function info */
+extern struct sdio_func *wl_cfg80211_get_sdio_func(void); /* set sdio function info */
+extern s32 wl_cfg80211_up(void); /* dongle up */
+extern s32 wl_cfg80211_down(void); /* dongle down */
+extern void wl_cfg80211_dbg_level(u32 level); /* set dongle
+ debugging level */
+extern void *wl_cfg80211_request_fw(s8 *file_name); /* request fw /nvram
+ downloading */
+extern s32 wl_cfg80211_read_fw(s8 *buf, u32 size); /* read fw
+ image */
+extern void wl_cfg80211_release_fw(void); /* release fw */
+extern s8 *wl_cfg80211_get_fwname(void); /* get firmware name for
+ the dongle */
+extern s8 *wl_cfg80211_get_nvramname(void); /* get nvram name for
+ the dongle */
+
+#endif /* _wl_cfg80211_h_ */
diff --git a/drivers/staging/brcm80211/brcmfmac/wl_iw.c b/drivers/staging/brcm80211/brcmfmac/wl_iw.c
new file mode 100644
index 000000000000..979a494fda59
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/wl_iw.c
@@ -0,0 +1,3767 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/kthread.h>
+#include <bcmdefs.h>
+#include <linuxver.h>
+#include <osl.h>
+#include <wlioctl.h>
+
+#include <bcmutils.h>
+#include <bcmendian.h>
+#include <proto/ethernet.h>
+
+#include <linux/if_arp.h>
+#include <asm/uaccess.h>
+
+#include <dngl_stats.h>
+#include <dhd.h>
+#include <dhdioctl.h>
+
+typedef void wlc_info_t;
+typedef void wl_info_t;
+typedef const struct si_pub si_t;
+#include <wlioctl.h>
+
+#include <proto/ethernet.h>
+#include <dngl_stats.h>
+#include <dhd.h>
+#define WL_ERROR(x) printf x
+#define WL_TRACE(x)
+#define WL_ASSOC(x)
+#define WL_INFORM(x)
+#define WL_WSEC(x)
+#define WL_SCAN(x)
+
+#include <wl_iw.h>
+
+#define IW_WSEC_ENABLED(wsec) ((wsec) & (WEP_ENABLED | \
+ TKIP_ENABLED | AES_ENABLED))
+
+#include <linux/rtnetlink.h>
+
+#define WL_IW_USE_ISCAN 1
+#define ENABLE_ACTIVE_PASSIVE_SCAN_SUPPRESS 1
+
+bool g_set_essid_before_scan = true;
+
+#define WL_IW_IOCTL_CALL(func_call) \
+ do { \
+ func_call; \
+ } while (0)
+
+static int g_onoff = G_WLAN_SET_ON;
+wl_iw_extra_params_t g_wl_iw_params;
+
+extern bool wl_iw_conn_status_str(u32 event_type, u32 status,
+ u32 reason, char *stringBuf, uint buflen);
+
+uint wl_msg_level = WL_ERROR_VAL;
+
+#define MAX_WLIW_IOCTL_LEN 1024
+
+#if defined(IL_BIGENDIAN)
+#include <bcmendian.h>
+#define htod32(i) (bcmswap32(i))
+#define htod16(i) (bcmswap16(i))
+#define dtoh32(i) (bcmswap32(i))
+#define dtoh16(i) (bcmswap16(i))
+#define htodchanspec(i) htod16(i)
+#define dtohchanspec(i) dtoh16(i)
+#else
+#define htod32(i) i
+#define htod16(i) i
+#define dtoh32(i) i
+#define dtoh16(i) i
+#define htodchanspec(i) i
+#define dtohchanspec(i) i
+#endif
+
+#ifdef CONFIG_WIRELESS_EXT
+
+extern struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev);
+extern int dhd_wait_pend8021x(struct net_device *dev);
+#endif
+
+#if WIRELESS_EXT < 19
+#define IW_IOCTL_IDX(cmd) ((cmd) - SIOCIWFIRST)
+#define IW_EVENT_IDX(cmd) ((cmd) - IWEVFIRST)
+#endif
+
+static void *g_scan;
+static volatile uint g_scan_specified_ssid;
+static wlc_ssid_t g_specific_ssid;
+
+static wlc_ssid_t g_ssid;
+
+#if defined(WL_IW_USE_ISCAN)
+#define ISCAN_STATE_IDLE 0
+#define ISCAN_STATE_SCANING 1
+
+#define WLC_IW_ISCAN_MAXLEN 2048
+typedef struct iscan_buf {
+ struct iscan_buf *next;
+ char iscan_buf[WLC_IW_ISCAN_MAXLEN];
+} iscan_buf_t;
+
+typedef struct iscan_info {
+ struct net_device *dev;
+ struct timer_list timer;
+ u32 timer_ms;
+ u32 timer_on;
+ int iscan_state;
+ iscan_buf_t *list_hdr;
+ iscan_buf_t *list_cur;
+
+ struct task_struct *sysioc_tsk;
+ struct semaphore sysioc_sem;
+
+#if defined CSCAN
+ char ioctlbuf[WLC_IOCTL_MEDLEN];
+#else
+ char ioctlbuf[WLC_IOCTL_SMLEN];
+#endif
+ wl_iscan_params_t *iscan_ex_params_p;
+ int iscan_ex_param_size;
+} iscan_info_t;
+iscan_info_t *g_iscan;
+static void wl_iw_timerfunc(unsigned long data);
+static void wl_iw_set_event_mask(struct net_device *dev);
+static int wl_iw_iscan(iscan_info_t *iscan, wlc_ssid_t *ssid, u16 action);
+#endif /* defined(WL_IW_USE_ISCAN) */
+
+static int
+wl_iw_set_scan(struct net_device *dev,
+ struct iw_request_info *info,
+ union iwreq_data *wrqu, char *extra);
+
+static int
+wl_iw_get_scan(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra);
+
+static uint
+wl_iw_get_scan_prep(wl_scan_results_t *list,
+ struct iw_request_info *info, char *extra, short max_size);
+
+static void swap_key_from_BE(wl_wsec_key_t *key)
+{
+ key->index = htod32(key->index);
+ key->len = htod32(key->len);
+ key->algo = htod32(key->algo);
+ key->flags = htod32(key->flags);
+ key->rxiv.hi = htod32(key->rxiv.hi);
+ key->rxiv.lo = htod16(key->rxiv.lo);
+ key->iv_initialized = htod32(key->iv_initialized);
+}
+
+static void swap_key_to_BE(wl_wsec_key_t *key)
+{
+ key->index = dtoh32(key->index);
+ key->len = dtoh32(key->len);
+ key->algo = dtoh32(key->algo);
+ key->flags = dtoh32(key->flags);
+ key->rxiv.hi = dtoh32(key->rxiv.hi);
+ key->rxiv.lo = dtoh16(key->rxiv.lo);
+ key->iv_initialized = dtoh32(key->iv_initialized);
+}
+
+static int dev_wlc_ioctl(struct net_device *dev, int cmd, void *arg, int len)
+{
+ struct ifreq ifr;
+ wl_ioctl_t ioc;
+ mm_segment_t fs;
+ int ret = -EINVAL;
+
+ if (!dev) {
+ WL_ERROR(("%s: dev is null\n", __func__));
+ return ret;
+ }
+
+ WL_INFORM(("\n%s, PID:%x: send Local IOCTL -> dhd: cmd:0x%x, buf:%p, "
+ "len:%d ,\n", __func__, current->pid, cmd, arg, len));
+
+ if (g_onoff == G_WLAN_SET_ON) {
+ memset(&ioc, 0, sizeof(ioc));
+ ioc.cmd = cmd;
+ ioc.buf = arg;
+ ioc.len = len;
+
+ strcpy(ifr.ifr_name, dev->name);
+ ifr.ifr_data = (caddr_t)&ioc;
+
+ ret = dev_open(dev);
+ if (ret) {
+ WL_ERROR(("%s: Error dev_open: %d\n", __func__, ret));
+ return ret;
+ }
+
+ fs = get_fs();
+ set_fs(get_ds());
+ ret = dev->netdev_ops->ndo_do_ioctl(dev, &ifr, SIOCDEVPRIVATE);
+ set_fs(fs);
+ } else {
+ WL_TRACE(("%s: call after driver stop : ignored\n", __func__));
+ }
+ return ret;
+}
+
+static int dev_wlc_intvar_set(struct net_device *dev, char *name, int val)
+{
+ char buf[WLC_IOCTL_SMLEN];
+ uint len;
+
+ val = htod32(val);
+ len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf));
+ ASSERT(len);
+
+ return dev_wlc_ioctl(dev, WLC_SET_VAR, buf, len);
+}
+
+#if defined(WL_IW_USE_ISCAN)
+static int
+dev_iw_iovar_setbuf(struct net_device *dev,
+ char *iovar,
+ void *param, int paramlen, void *bufptr, int buflen)
+{
+ int iolen;
+
+ iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen);
+ ASSERT(iolen);
+
+ if (iolen == 0)
+ return 0;
+
+ return dev_wlc_ioctl(dev, WLC_SET_VAR, bufptr, iolen);
+}
+
+static int
+dev_iw_iovar_getbuf(struct net_device *dev,
+ char *iovar,
+ void *param, int paramlen, void *bufptr, int buflen)
+{
+ int iolen;
+
+ iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen);
+ ASSERT(iolen);
+
+ return dev_wlc_ioctl(dev, WLC_GET_VAR, bufptr, buflen);
+}
+#endif /* defined(WL_IW_USE_ISCAN) */
+
+#if WIRELESS_EXT > 17
+static int
+dev_wlc_bufvar_set(struct net_device *dev, char *name, char *buf, int len)
+{
+ static char ioctlbuf[MAX_WLIW_IOCTL_LEN];
+ uint buflen;
+
+ buflen = bcm_mkiovar(name, buf, len, ioctlbuf, sizeof(ioctlbuf));
+ ASSERT(buflen);
+
+ return dev_wlc_ioctl(dev, WLC_SET_VAR, ioctlbuf, buflen);
+}
+#endif /* WIRELESS_EXT > 17 */
+
+static int
+dev_wlc_bufvar_get(struct net_device *dev, char *name, char *buf, int buflen)
+{
+ static char ioctlbuf[MAX_WLIW_IOCTL_LEN];
+ int error;
+ uint len;
+
+ len = bcm_mkiovar(name, NULL, 0, ioctlbuf, sizeof(ioctlbuf));
+ ASSERT(len);
+ error =
+ dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)ioctlbuf,
+ MAX_WLIW_IOCTL_LEN);
+ if (!error)
+ bcopy(ioctlbuf, buf, buflen);
+
+ return error;
+}
+
+static int dev_wlc_intvar_get(struct net_device *dev, char *name, int *retval)
+{
+ union {
+ char buf[WLC_IOCTL_SMLEN];
+ int val;
+ } var;
+ int error;
+
+ uint len;
+ uint data_null;
+
+ len =
+ bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var),
+ sizeof(var.buf));
+ ASSERT(len);
+ error = dev_wlc_ioctl(dev, WLC_GET_VAR, (void *)&var, len);
+
+ *retval = dtoh32(var.val);
+
+ return error;
+}
+
+#if WIRELESS_EXT < 13
+struct iw_request_info {
+ __u16 cmd;
+ __u16 flags;
+};
+
+typedef int (*iw_handler) (struct net_device *dev,
+ struct iw_request_info *info,
+ void *wrqu, char *extra);
+#endif
+
+static int
+wl_iw_config_commit(struct net_device *dev,
+ struct iw_request_info *info, void *zwrq, char *extra)
+{
+ wlc_ssid_t ssid;
+ int error;
+ struct sockaddr bssid;
+
+ WL_TRACE(("%s: SIOCSIWCOMMIT\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid));
+ if (error)
+ return error;
+
+ ssid.SSID_len = dtoh32(ssid.SSID_len);
+
+ if (!ssid.SSID_len)
+ return 0;
+
+ bzero(&bssid, sizeof(struct sockaddr));
+ error = dev_wlc_ioctl(dev, WLC_REASSOC, &bssid, ETHER_ADDR_LEN);
+ if (error) {
+ WL_ERROR(("%s: WLC_REASSOC to %s failed \n", __func__,
+ ssid.SSID));
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+wl_iw_get_name(struct net_device *dev,
+ struct iw_request_info *info, char *cwrq, char *extra)
+{
+ WL_TRACE(("%s: SIOCGIWNAME\n", dev->name));
+
+ strcpy(cwrq, "IEEE 802.11-DS");
+
+ return 0;
+}
+
+static int
+wl_iw_set_freq(struct net_device *dev,
+ struct iw_request_info *info, struct iw_freq *fwrq, char *extra)
+{
+ int error, chan;
+ uint sf = 0;
+
+ WL_TRACE(("\n %s %s: SIOCSIWFREQ\n", __func__, dev->name));
+
+ if (fwrq->e == 0 && fwrq->m < MAXCHANNEL) {
+ chan = fwrq->m;
+ } else {
+ if (fwrq->e >= 6) {
+ fwrq->e -= 6;
+ while (fwrq->e--)
+ fwrq->m *= 10;
+ } else if (fwrq->e < 6) {
+ while (fwrq->e++ < 6)
+ fwrq->m /= 10;
+ }
+ if (fwrq->m > 4000 && fwrq->m < 5000)
+ sf = WF_CHAN_FACTOR_4_G;
+
+ chan = wf_mhz2channel(fwrq->m, sf);
+ }
+ chan = htod32(chan);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_CHANNEL, &chan, sizeof(chan));
+ if (error)
+ return error;
+
+ g_wl_iw_params.target_channel = chan;
+ return -EINPROGRESS;
+}
+
+static int
+wl_iw_get_freq(struct net_device *dev,
+ struct iw_request_info *info, struct iw_freq *fwrq, char *extra)
+{
+ channel_info_t ci;
+ int error;
+
+ WL_TRACE(("%s: SIOCGIWFREQ\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci));
+ if (error)
+ return error;
+
+ fwrq->m = dtoh32(ci.hw_channel);
+ fwrq->e = dtoh32(0);
+ return 0;
+}
+
+static int
+wl_iw_set_mode(struct net_device *dev,
+ struct iw_request_info *info, __u32 *uwrq, char *extra)
+{
+ int infra = 0, ap = 0, error = 0;
+
+ WL_TRACE(("%s: SIOCSIWMODE\n", dev->name));
+
+ switch (*uwrq) {
+ case IW_MODE_MASTER:
+ infra = ap = 1;
+ break;
+ case IW_MODE_ADHOC:
+ case IW_MODE_AUTO:
+ break;
+ case IW_MODE_INFRA:
+ infra = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ infra = htod32(infra);
+ ap = htod32(ap);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_INFRA, &infra, sizeof(infra));
+ if (error)
+ return error;
+
+ error = dev_wlc_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap));
+ if (error)
+ return error;
+
+ return -EINPROGRESS;
+}
+
+static int
+wl_iw_get_mode(struct net_device *dev,
+ struct iw_request_info *info, __u32 *uwrq, char *extra)
+{
+ int error, infra = 0, ap = 0;
+
+ WL_TRACE(("%s: SIOCGIWMODE\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra));
+ if (error)
+ return error;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap));
+ if (error)
+ return error;
+
+ infra = dtoh32(infra);
+ ap = dtoh32(ap);
+ *uwrq = infra ? ap ? IW_MODE_MASTER : IW_MODE_INFRA : IW_MODE_ADHOC;
+
+ return 0;
+}
+
+static int
+wl_iw_get_range(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ struct iw_range *range = (struct iw_range *)extra;
+ wl_u32_list_t *list;
+ wl_rateset_t rateset;
+ s8 *channels;
+ int error, i, k;
+ uint sf, ch;
+
+ int phytype;
+ int bw_cap = 0, sgi_tx = 0, nmode = 0;
+ channel_info_t ci;
+ u8 nrate_list2copy = 0;
+ u16 nrate_list[4][8] = { {13, 26, 39, 52, 78, 104, 117, 130},
+ {14, 29, 43, 58, 87, 116, 130, 144},
+ {27, 54, 81, 108, 162, 216, 243, 270},
+ {30, 60, 90, 120, 180, 240, 270, 300}
+ };
+
+ WL_TRACE(("%s: SIOCGIWRANGE\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ channels = kmalloc((MAXCHANNEL + 1) * 4, GFP_KERNEL);
+ if (!channels) {
+ WL_ERROR(("Could not alloc channels\n"));
+ return -ENOMEM;
+ }
+ list = (wl_u32_list_t *) channels;
+
+ dwrq->length = sizeof(struct iw_range);
+ memset(range, 0, sizeof(range));
+
+ range->min_nwid = range->max_nwid = 0;
+
+ list->count = htod32(MAXCHANNEL);
+ error = dev_wlc_ioctl(dev, WLC_GET_VALID_CHANNELS, channels,
+ (MAXCHANNEL + 1) * 4);
+ if (error) {
+ kfree(channels);
+ return error;
+ }
+ for (i = 0; i < dtoh32(list->count) && i < IW_MAX_FREQUENCIES; i++) {
+ range->freq[i].i = dtoh32(list->element[i]);
+
+ ch = dtoh32(list->element[i]);
+ if (ch <= CH_MAX_2G_CHANNEL)
+ sf = WF_CHAN_FACTOR_2_4_G;
+ else
+ sf = WF_CHAN_FACTOR_5_G;
+
+ range->freq[i].m = wf_channel2mhz(ch, sf);
+ range->freq[i].e = 6;
+ }
+ range->num_frequency = range->num_channels = i;
+
+ range->max_qual.qual = 5;
+ range->max_qual.level = 0x100 - 200;
+ range->max_qual.noise = 0x100 - 200;
+ range->sensitivity = 65535;
+
+#if WIRELESS_EXT > 11
+ range->avg_qual.qual = 3;
+ range->avg_qual.level = 0x100 + WL_IW_RSSI_GOOD;
+ range->avg_qual.noise = 0x100 - 75;
+#endif
+
+ error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset,
+ sizeof(rateset));
+ if (error) {
+ kfree(channels);
+ return error;
+ }
+ rateset.count = dtoh32(rateset.count);
+ range->num_bitrates = rateset.count;
+ for (i = 0; i < rateset.count && i < IW_MAX_BITRATES; i++)
+ range->bitrate[i] = (rateset.rates[i] & 0x7f) * 500000;
+ dev_wlc_intvar_get(dev, "nmode", &nmode);
+ dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &phytype, sizeof(phytype));
+
+ if (nmode == 1 && phytype == WLC_PHY_TYPE_SSN) {
+ dev_wlc_intvar_get(dev, "mimo_bw_cap", &bw_cap);
+ dev_wlc_intvar_get(dev, "sgi_tx", &sgi_tx);
+ dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci,
+ sizeof(channel_info_t));
+ ci.hw_channel = dtoh32(ci.hw_channel);
+
+ if (bw_cap == 0 || (bw_cap == 2 && ci.hw_channel <= 14)) {
+ if (sgi_tx == 0)
+ nrate_list2copy = 0;
+ else
+ nrate_list2copy = 1;
+ }
+ if (bw_cap == 1 || (bw_cap == 2 && ci.hw_channel >= 36)) {
+ if (sgi_tx == 0)
+ nrate_list2copy = 2;
+ else
+ nrate_list2copy = 3;
+ }
+ range->num_bitrates += 8;
+ for (k = 0; i < range->num_bitrates; k++, i++) {
+ range->bitrate[i] =
+ (nrate_list[nrate_list2copy][k]) * 500000;
+ }
+ }
+
+ error = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &i, sizeof(i));
+ if (error) {
+ kfree(channels);
+ return error;
+ }
+ i = dtoh32(i);
+ if (i == WLC_PHY_TYPE_A)
+ range->throughput = 24000000;
+ else
+ range->throughput = 1500000;
+
+ range->min_rts = 0;
+ range->max_rts = 2347;
+ range->min_frag = 256;
+ range->max_frag = 2346;
+
+ range->max_encoding_tokens = DOT11_MAX_DEFAULT_KEYS;
+ range->num_encoding_sizes = 4;
+ range->encoding_size[0] = WEP1_KEY_SIZE;
+ range->encoding_size[1] = WEP128_KEY_SIZE;
+#if WIRELESS_EXT > 17
+ range->encoding_size[2] = TKIP_KEY_SIZE;
+#else
+ range->encoding_size[2] = 0;
+#endif
+ range->encoding_size[3] = AES_KEY_SIZE;
+
+ range->min_pmp = 0;
+ range->max_pmp = 0;
+ range->min_pmt = 0;
+ range->max_pmt = 0;
+ range->pmp_flags = 0;
+ range->pm_capa = 0;
+
+ range->num_txpower = 2;
+ range->txpower[0] = 1;
+ range->txpower[1] = 255;
+ range->txpower_capa = IW_TXPOW_MWATT;
+
+#if WIRELESS_EXT > 10
+ range->we_version_compiled = WIRELESS_EXT;
+ range->we_version_source = 19;
+
+ range->retry_capa = IW_RETRY_LIMIT;
+ range->retry_flags = IW_RETRY_LIMIT;
+ range->r_time_flags = 0;
+ range->min_retry = 1;
+ range->max_retry = 255;
+ range->min_r_time = 0;
+ range->max_r_time = 0;
+#endif
+
+#if WIRELESS_EXT > 17
+ range->enc_capa = IW_ENC_CAPA_WPA;
+ range->enc_capa |= IW_ENC_CAPA_CIPHER_TKIP;
+ range->enc_capa |= IW_ENC_CAPA_CIPHER_CCMP;
+ range->enc_capa |= IW_ENC_CAPA_WPA2;
+
+ IW_EVENT_CAPA_SET_KERNEL(range->event_capa);
+ IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP);
+ IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN);
+ IW_EVENT_CAPA_SET(range->event_capa, IWEVTXDROP);
+ IW_EVENT_CAPA_SET(range->event_capa, IWEVMICHAELMICFAILURE);
+ IW_EVENT_CAPA_SET(range->event_capa, IWEVPMKIDCAND);
+#endif /* WIRELESS_EXT > 17 */
+
+ kfree(channels);
+
+ return 0;
+}
+
+static int rssi_to_qual(int rssi)
+{
+ if (rssi <= WL_IW_RSSI_NO_SIGNAL)
+ return 0;
+ else if (rssi <= WL_IW_RSSI_VERY_LOW)
+ return 1;
+ else if (rssi <= WL_IW_RSSI_LOW)
+ return 2;
+ else if (rssi <= WL_IW_RSSI_GOOD)
+ return 3;
+ else if (rssi <= WL_IW_RSSI_VERY_GOOD)
+ return 4;
+ else
+ return 5;
+}
+
+static int
+wl_iw_set_spy(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *dwrq, char *extra)
+{
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+ struct sockaddr *addr = (struct sockaddr *)extra;
+ int i;
+
+ WL_TRACE(("%s: SIOCSIWSPY\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ iw->spy_num = min_t(int, ARRAY_SIZE(iw->spy_addr), dwrq->length);
+ for (i = 0; i < iw->spy_num; i++)
+ memcpy(&iw->spy_addr[i], addr[i].sa_data, ETHER_ADDR_LEN);
+ memset(iw->spy_qual, 0, sizeof(iw->spy_qual));
+
+ return 0;
+}
+
+static int
+wl_iw_get_spy(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *dwrq, char *extra)
+{
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+ struct sockaddr *addr = (struct sockaddr *)extra;
+ struct iw_quality *qual = (struct iw_quality *)&addr[iw->spy_num];
+ int i;
+
+ WL_TRACE(("%s: SIOCGIWSPY\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ dwrq->length = iw->spy_num;
+ for (i = 0; i < iw->spy_num; i++) {
+ memcpy(addr[i].sa_data, &iw->spy_addr[i], ETHER_ADDR_LEN);
+ addr[i].sa_family = AF_UNIX;
+ memcpy(&qual[i], &iw->spy_qual[i], sizeof(struct iw_quality));
+ iw->spy_qual[i].updated = 0;
+ }
+
+ return 0;
+}
+
+static int
+wl_iw_ch_to_chanspec(int ch, wl_join_params_t *join_params,
+ int *join_params_size)
+{
+ chanspec_t chanspec = 0;
+
+ if (ch != 0) {
+ join_params->params.chanspec_num = 1;
+ join_params->params.chanspec_list[0] = ch;
+
+ if (join_params->params.chanspec_list[0])
+ chanspec |= WL_CHANSPEC_BAND_2G;
+ else
+ chanspec |= WL_CHANSPEC_BAND_5G;
+
+ chanspec |= WL_CHANSPEC_BW_20;
+ chanspec |= WL_CHANSPEC_CTL_SB_NONE;
+
+ *join_params_size += WL_ASSOC_PARAMS_FIXED_SIZE +
+ join_params->params.chanspec_num * sizeof(chanspec_t);
+
+ join_params->params.chanspec_list[0] &= WL_CHANSPEC_CHAN_MASK;
+ join_params->params.chanspec_list[0] |= chanspec;
+ join_params->params.chanspec_list[0] =
+ htodchanspec(join_params->params.chanspec_list[0]);
+
+ join_params->params.chanspec_num =
+ htod32(join_params->params.chanspec_num);
+
+ WL_TRACE(("%s join_params->params.chanspec_list[0]= %X\n",
+ __func__, join_params->params.chanspec_list[0]));
+ }
+ return 1;
+}
+
+static int
+wl_iw_set_wap(struct net_device *dev,
+ struct iw_request_info *info, struct sockaddr *awrq, char *extra)
+{
+ int error = -EINVAL;
+ wl_join_params_t join_params;
+ int join_params_size;
+
+ WL_TRACE(("%s: SIOCSIWAP\n", dev->name));
+
+ if (awrq->sa_family != ARPHRD_ETHER) {
+ WL_ERROR(("Invalid Header...sa_family\n"));
+ return -EINVAL;
+ }
+
+ if (ETHER_ISBCAST(awrq->sa_data) || ETHER_ISNULLADDR(awrq->sa_data)) {
+ scb_val_t scbval;
+ bzero(&scbval, sizeof(scb_val_t));
+ (void)dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval,
+ sizeof(scb_val_t));
+ return 0;
+ }
+
+ memset(&join_params, 0, sizeof(join_params));
+ join_params_size = sizeof(join_params.ssid);
+
+ memcpy(join_params.ssid.SSID, g_ssid.SSID, g_ssid.SSID_len);
+ join_params.ssid.SSID_len = htod32(g_ssid.SSID_len);
+ memcpy(&join_params.params.bssid, awrq->sa_data, ETHER_ADDR_LEN);
+
+ WL_TRACE(("%s target_channel=%d\n", __func__,
+ g_wl_iw_params.target_channel));
+ wl_iw_ch_to_chanspec(g_wl_iw_params.target_channel, &join_params,
+ &join_params_size);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params,
+ join_params_size);
+ if (error) {
+ WL_ERROR(("%s Invalid ioctl data=%d\n", __func__, error));
+ }
+
+ if (g_ssid.SSID_len) {
+ WL_TRACE(("%s: join SSID=%s BSSID=%pM ch=%d\n",
+ __func__, g_ssid.SSID, awrq->sa_data,
+ g_wl_iw_params.target_channel));
+ }
+
+ memset(&g_ssid, 0, sizeof(g_ssid));
+ return 0;
+}
+
+static int
+wl_iw_get_wap(struct net_device *dev,
+ struct iw_request_info *info, struct sockaddr *awrq, char *extra)
+{
+ WL_TRACE(("%s: SIOCGIWAP\n", dev->name));
+
+ awrq->sa_family = ARPHRD_ETHER;
+ memset(awrq->sa_data, 0, ETHER_ADDR_LEN);
+
+ (void)dev_wlc_ioctl(dev, WLC_GET_BSSID, awrq->sa_data, ETHER_ADDR_LEN);
+
+ return 0;
+}
+
+#if WIRELESS_EXT > 17
+static int
+wl_iw_mlme(struct net_device *dev,
+ struct iw_request_info *info, struct sockaddr *awrq, char *extra)
+{
+ struct iw_mlme *mlme;
+ scb_val_t scbval;
+ int error = -EINVAL;
+
+ WL_TRACE(("%s: SIOCSIWMLME DISASSOC/DEAUTH\n", dev->name));
+
+ mlme = (struct iw_mlme *)extra;
+ if (mlme == NULL) {
+ WL_ERROR(("Invalid ioctl data.\n"));
+ return error;
+ }
+
+ scbval.val = mlme->reason_code;
+ bcopy(&mlme->addr.sa_data, &scbval.ea, ETHER_ADDR_LEN);
+
+ if (mlme->cmd == IW_MLME_DISASSOC) {
+ scbval.val = htod32(scbval.val);
+ error =
+ dev_wlc_ioctl(dev, WLC_DISASSOC, &scbval,
+ sizeof(scb_val_t));
+ } else if (mlme->cmd == IW_MLME_DEAUTH) {
+ scbval.val = htod32(scbval.val);
+ error =
+ dev_wlc_ioctl(dev, WLC_SCB_DEAUTHENTICATE_FOR_REASON,
+ &scbval, sizeof(scb_val_t));
+ } else {
+ WL_ERROR(("Invalid ioctl data.\n"));
+ return error;
+ }
+
+ return error;
+}
+#endif /* WIRELESS_EXT > 17 */
+
+#ifndef WL_IW_USE_ISCAN
+static int
+wl_iw_get_aplist(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_scan_results_t *list;
+ struct sockaddr *addr = (struct sockaddr *)extra;
+ struct iw_quality qual[IW_MAX_AP];
+ wl_bss_info_t *bi = NULL;
+ int error, i;
+ uint buflen = dwrq->length;
+
+ WL_TRACE(("%s: SIOCGIWAPLIST\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ list = kmalloc(buflen, GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+ memset(list, 0, buflen);
+ list->buflen = htod32(buflen);
+ error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, buflen);
+ if (error) {
+ WL_ERROR(("%d: Scan results error %d\n", __LINE__, error));
+ kfree(list);
+ return error;
+ }
+ list->buflen = dtoh32(list->buflen);
+ list->version = dtoh32(list->version);
+ list->count = dtoh32(list->count);
+ if (list->version != WL_BSS_INFO_VERSION) {
+ WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n",
+ __func__, list->version));
+ kfree(list);
+ return -EINVAL;
+ }
+
+ for (i = 0, dwrq->length = 0;
+ i < list->count && dwrq->length < IW_MAX_AP; i++) {
+ bi = bi ? (wl_bss_info_t *) ((unsigned long)bi +
+ dtoh32(bi->length)) : list->
+ bss_info;
+ ASSERT(((unsigned long)bi + dtoh32(bi->length)) <=
+ ((unsigned long)list + buflen));
+
+ if (!(dtoh16(bi->capability) & DOT11_CAP_ESS))
+ continue;
+
+ memcpy(addr[dwrq->length].sa_data, &bi->BSSID, ETHER_ADDR_LEN);
+ addr[dwrq->length].sa_family = ARPHRD_ETHER;
+ qual[dwrq->length].qual = rssi_to_qual(dtoh16(bi->RSSI));
+ qual[dwrq->length].level = 0x100 + dtoh16(bi->RSSI);
+ qual[dwrq->length].noise = 0x100 + bi->phy_noise;
+
+#if WIRELESS_EXT > 18
+ qual[dwrq->length].updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM;
+#else
+ qual[dwrq->length].updated = 7;
+#endif
+ dwrq->length++;
+ }
+
+ kfree(list);
+
+ if (dwrq->length) {
+ memcpy(&addr[dwrq->length], qual,
+ sizeof(struct iw_quality) * dwrq->length);
+ dwrq->flags = 1;
+ }
+
+ return 0;
+}
+#endif /* WL_IW_USE_ISCAN */
+
+#ifdef WL_IW_USE_ISCAN
+static int
+wl_iw_iscan_get_aplist(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_scan_results_t *list;
+ iscan_buf_t *buf;
+ iscan_info_t *iscan = g_iscan;
+
+ struct sockaddr *addr = (struct sockaddr *)extra;
+ struct iw_quality qual[IW_MAX_AP];
+ wl_bss_info_t *bi = NULL;
+ int i;
+
+ WL_TRACE(("%s: SIOCGIWAPLIST\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ if ((!iscan) || (!iscan->sysioc_tsk)) {
+ WL_ERROR(("%s error\n", __func__));
+ return 0;
+ }
+
+ buf = iscan->list_hdr;
+ while (buf) {
+ list = &((wl_iscan_results_t *) buf->iscan_buf)->results;
+ if (list->version != WL_BSS_INFO_VERSION) {
+ WL_ERROR(("%s : list->version %d != "
+ "WL_BSS_INFO_VERSION\n",
+ __func__, list->version));
+ return -EINVAL;
+ }
+
+ bi = NULL;
+ for (i = 0, dwrq->length = 0;
+ i < list->count && dwrq->length < IW_MAX_AP; i++) {
+ bi = bi ? (wl_bss_info_t *) ((unsigned long)bi +
+ dtoh32(bi->length)) :
+ list->bss_info;
+ ASSERT(((unsigned long)bi + dtoh32(bi->length)) <=
+ ((unsigned long)list + WLC_IW_ISCAN_MAXLEN));
+
+ if (!(dtoh16(bi->capability) & DOT11_CAP_ESS))
+ continue;
+
+ memcpy(addr[dwrq->length].sa_data, &bi->BSSID,
+ ETHER_ADDR_LEN);
+ addr[dwrq->length].sa_family = ARPHRD_ETHER;
+ qual[dwrq->length].qual =
+ rssi_to_qual(dtoh16(bi->RSSI));
+ qual[dwrq->length].level = 0x100 + dtoh16(bi->RSSI);
+ qual[dwrq->length].noise = 0x100 + bi->phy_noise;
+
+#if WIRELESS_EXT > 18
+ qual[dwrq->length].updated =
+ IW_QUAL_ALL_UPDATED | IW_QUAL_DBM;
+#else
+ qual[dwrq->length].updated = 7;
+#endif
+
+ dwrq->length++;
+ }
+ buf = buf->next;
+ }
+ if (dwrq->length) {
+ memcpy(&addr[dwrq->length], qual,
+ sizeof(struct iw_quality) * dwrq->length);
+ dwrq->flags = 1;
+ }
+
+ return 0;
+}
+
+static int wl_iw_iscan_prep(wl_scan_params_t *params, wlc_ssid_t *ssid)
+{
+ int err = 0;
+
+ memcpy(&params->bssid, &ether_bcast, ETHER_ADDR_LEN);
+ params->bss_type = DOT11_BSSTYPE_ANY;
+ params->scan_type = 0;
+ params->nprobes = -1;
+ params->active_time = -1;
+ params->passive_time = -1;
+ params->home_time = -1;
+ params->channel_num = 0;
+
+ params->nprobes = htod32(params->nprobes);
+ params->active_time = htod32(params->active_time);
+ params->passive_time = htod32(params->passive_time);
+ params->home_time = htod32(params->home_time);
+ if (ssid && ssid->SSID_len)
+ memcpy(&params->ssid, ssid, sizeof(wlc_ssid_t));
+
+ return err;
+}
+
+static int wl_iw_iscan(iscan_info_t *iscan, wlc_ssid_t *ssid, u16 action)
+{
+ int err = 0;
+
+ iscan->iscan_ex_params_p->version = htod32(ISCAN_REQ_VERSION);
+ iscan->iscan_ex_params_p->action = htod16(action);
+ iscan->iscan_ex_params_p->scan_duration = htod16(0);
+
+ WL_SCAN(("%s : nprobes=%d\n", __func__,
+ iscan->iscan_ex_params_p->params.nprobes));
+ WL_SCAN(("active_time=%d\n",
+ iscan->iscan_ex_params_p->params.active_time));
+ WL_SCAN(("passive_time=%d\n",
+ iscan->iscan_ex_params_p->params.passive_time));
+ WL_SCAN(("home_time=%d\n", iscan->iscan_ex_params_p->params.home_time));
+ WL_SCAN(("scan_type=%d\n", iscan->iscan_ex_params_p->params.scan_type));
+ WL_SCAN(("bss_type=%d\n", iscan->iscan_ex_params_p->params.bss_type));
+
+ (void)dev_iw_iovar_setbuf(iscan->dev, "iscan", iscan->iscan_ex_params_p,
+ iscan->iscan_ex_param_size, iscan->ioctlbuf,
+ sizeof(iscan->ioctlbuf));
+
+ return err;
+}
+
+static void wl_iw_timerfunc(unsigned long data)
+{
+ iscan_info_t *iscan = (iscan_info_t *) data;
+ if (iscan) {
+ iscan->timer_on = 0;
+ if (iscan->iscan_state != ISCAN_STATE_IDLE) {
+ WL_TRACE(("timer trigger\n"));
+ up(&iscan->sysioc_sem);
+ }
+ }
+}
+
+static void wl_iw_set_event_mask(struct net_device *dev)
+{
+ char eventmask[WL_EVENTING_MASK_LEN];
+ char iovbuf[WL_EVENTING_MASK_LEN + 12];
+
+ dev_iw_iovar_getbuf(dev, "event_msgs", "", 0, iovbuf, sizeof(iovbuf));
+ bcopy(iovbuf, eventmask, WL_EVENTING_MASK_LEN);
+ setbit(eventmask, WLC_E_SCAN_COMPLETE);
+ dev_iw_iovar_setbuf(dev, "event_msgs", eventmask, WL_EVENTING_MASK_LEN,
+ iovbuf, sizeof(iovbuf));
+}
+
+static u32 wl_iw_iscan_get(iscan_info_t *iscan)
+{
+ iscan_buf_t *buf;
+ iscan_buf_t *ptr;
+ wl_iscan_results_t *list_buf;
+ wl_iscan_results_t list;
+ wl_scan_results_t *results;
+ u32 status;
+ int res = 0;
+
+ MUTEX_LOCK_WL_SCAN_SET();
+ if (iscan->list_cur) {
+ buf = iscan->list_cur;
+ iscan->list_cur = buf->next;
+ } else {
+ buf = kmalloc(sizeof(iscan_buf_t), GFP_KERNEL);
+ if (!buf) {
+ WL_ERROR(("%s can't alloc iscan_buf_t : going to abort "
+ "currect iscan\n", __func__));
+ MUTEX_UNLOCK_WL_SCAN_SET();
+ return WL_SCAN_RESULTS_NO_MEM;
+ }
+ buf->next = NULL;
+ if (!iscan->list_hdr)
+ iscan->list_hdr = buf;
+ else {
+ ptr = iscan->list_hdr;
+ while (ptr->next) {
+ ptr = ptr->next;
+ }
+ ptr->next = buf;
+ }
+ }
+ memset(buf->iscan_buf, 0, WLC_IW_ISCAN_MAXLEN);
+ list_buf = (wl_iscan_results_t *) buf->iscan_buf;
+ results = &list_buf->results;
+ results->buflen = WL_ISCAN_RESULTS_FIXED_SIZE;
+ results->version = 0;
+ results->count = 0;
+
+ memset(&list, 0, sizeof(list));
+ list.results.buflen = htod32(WLC_IW_ISCAN_MAXLEN);
+ res = dev_iw_iovar_getbuf(iscan->dev,
+ "iscanresults",
+ &list,
+ WL_ISCAN_RESULTS_FIXED_SIZE,
+ buf->iscan_buf, WLC_IW_ISCAN_MAXLEN);
+ if (res == 0) {
+ results->buflen = dtoh32(results->buflen);
+ results->version = dtoh32(results->version);
+ results->count = dtoh32(results->count);
+ WL_TRACE(("results->count = %d\n", results->count));
+ WL_TRACE(("results->buflen = %d\n", results->buflen));
+ status = dtoh32(list_buf->status);
+ } else {
+ WL_ERROR(("%s returns error %d\n", __func__, res));
+ status = WL_SCAN_RESULTS_NO_MEM;
+ }
+ MUTEX_UNLOCK_WL_SCAN_SET();
+ return status;
+}
+
+static void wl_iw_force_specific_scan(iscan_info_t *iscan)
+{
+ WL_TRACE(("%s force Specific SCAN for %s\n", __func__,
+ g_specific_ssid.SSID));
+ rtnl_lock();
+
+ (void)dev_wlc_ioctl(iscan->dev, WLC_SCAN, &g_specific_ssid,
+ sizeof(g_specific_ssid));
+
+ rtnl_unlock();
+}
+
+static void wl_iw_send_scan_complete(iscan_info_t *iscan)
+{
+#ifndef SANDGATE2G
+ union iwreq_data wrqu;
+
+ memset(&wrqu, 0, sizeof(wrqu));
+
+ wireless_send_event(iscan->dev, SIOCGIWSCAN, &wrqu, NULL);
+ WL_TRACE(("Send Event ISCAN complete\n"));
+#endif
+}
+
+static int _iscan_sysioc_thread(void *data)
+{
+ u32 status;
+ iscan_info_t *iscan = (iscan_info_t *) data;
+ static bool iscan_pass_abort = false;
+
+ allow_signal(SIGTERM);
+ status = WL_SCAN_RESULTS_PARTIAL;
+ while (down_interruptible(&iscan->sysioc_sem) == 0) {
+ if (kthread_should_stop())
+ break;
+
+ if (iscan->timer_on) {
+ del_timer_sync(&iscan->timer);
+ iscan->timer_on = 0;
+ }
+ rtnl_lock();
+ status = wl_iw_iscan_get(iscan);
+ rtnl_unlock();
+ if (g_scan_specified_ssid && (iscan_pass_abort == true)) {
+ WL_TRACE(("%s Get results from specific scan "
+ "status = %d\n", __func__, status));
+ wl_iw_send_scan_complete(iscan);
+ iscan_pass_abort = false;
+ status = -1;
+ }
+
+ switch (status) {
+ case WL_SCAN_RESULTS_PARTIAL:
+ WL_TRACE(("iscanresults incomplete\n"));
+ rtnl_lock();
+ wl_iw_iscan(iscan, NULL, WL_SCAN_ACTION_CONTINUE);
+ rtnl_unlock();
+ mod_timer(&iscan->timer,
+ jiffies + iscan->timer_ms * HZ / 1000);
+ iscan->timer_on = 1;
+ break;
+ case WL_SCAN_RESULTS_SUCCESS:
+ WL_TRACE(("iscanresults complete\n"));
+ iscan->iscan_state = ISCAN_STATE_IDLE;
+ wl_iw_send_scan_complete(iscan);
+ break;
+ case WL_SCAN_RESULTS_PENDING:
+ WL_TRACE(("iscanresults pending\n"));
+ mod_timer(&iscan->timer,
+ jiffies + iscan->timer_ms * HZ / 1000);
+ iscan->timer_on = 1;
+ break;
+ case WL_SCAN_RESULTS_ABORTED:
+ WL_TRACE(("iscanresults aborted\n"));
+ iscan->iscan_state = ISCAN_STATE_IDLE;
+ if (g_scan_specified_ssid == 0)
+ wl_iw_send_scan_complete(iscan);
+ else {
+ iscan_pass_abort = true;
+ wl_iw_force_specific_scan(iscan);
+ }
+ break;
+ case WL_SCAN_RESULTS_NO_MEM:
+ WL_TRACE(("iscanresults can't alloc memory: skip\n"));
+ iscan->iscan_state = ISCAN_STATE_IDLE;
+ break;
+ default:
+ WL_TRACE(("iscanresults returned unknown status %d\n",
+ status));
+ break;
+ }
+ }
+
+ if (iscan->timer_on) {
+ del_timer_sync(&iscan->timer);
+ iscan->timer_on = 0;
+ }
+ return 0;
+}
+#endif /* WL_IW_USE_ISCAN */
+
+static int
+wl_iw_set_scan(struct net_device *dev,
+ struct iw_request_info *info,
+ union iwreq_data *wrqu, char *extra)
+{
+ int error;
+ WL_TRACE(("\n:%s dev:%s: SIOCSIWSCAN : SCAN\n", __func__, dev->name));
+
+ g_set_essid_before_scan = false;
+#if defined(CSCAN)
+ WL_ERROR(("%s: Scan from SIOCGIWSCAN not supported\n", __func__));
+ return -EINVAL;
+#endif
+
+ if (g_onoff == G_WLAN_SET_OFF)
+ return 0;
+
+ memset(&g_specific_ssid, 0, sizeof(g_specific_ssid));
+#ifndef WL_IW_USE_ISCAN
+ g_scan_specified_ssid = 0;
+#endif
+
+#if WIRELESS_EXT > 17
+ if (wrqu->data.length == sizeof(struct iw_scan_req)) {
+ if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
+ struct iw_scan_req *req = (struct iw_scan_req *)extra;
+ if (g_scan_specified_ssid) {
+ WL_TRACE(("%s Specific SCAN is not done ignore "
+ "scan for = %s\n",
+ __func__, req->essid));
+ return -EBUSY;
+ } else {
+ g_specific_ssid.SSID_len = min_t(size_t,
+ sizeof(g_specific_ssid.SSID),
+ req->essid_len);
+ memcpy(g_specific_ssid.SSID, req->essid,
+ g_specific_ssid.SSID_len);
+ g_specific_ssid.SSID_len =
+ htod32(g_specific_ssid.SSID_len);
+ g_scan_specified_ssid = 1;
+ WL_TRACE(("### Specific scan ssid=%s len=%d\n",
+ g_specific_ssid.SSID,
+ g_specific_ssid.SSID_len));
+ }
+ }
+ }
+#endif /* WIRELESS_EXT > 17 */
+ error = dev_wlc_ioctl(dev, WLC_SCAN, &g_specific_ssid,
+ sizeof(g_specific_ssid));
+ if (error) {
+ WL_TRACE(("#### Set SCAN for %s failed with %d\n",
+ g_specific_ssid.SSID, error));
+ g_scan_specified_ssid = 0;
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#ifdef WL_IW_USE_ISCAN
+int wl_iw_iscan_set_scan_broadcast_prep(struct net_device *dev, uint flag)
+{
+ wlc_ssid_t ssid;
+ iscan_info_t *iscan = g_iscan;
+
+ if (flag)
+ rtnl_lock();
+
+ wl_iw_set_event_mask(dev);
+
+ WL_TRACE(("+++: Set Broadcast ISCAN\n"));
+ memset(&ssid, 0, sizeof(ssid));
+
+ iscan->list_cur = iscan->list_hdr;
+ iscan->iscan_state = ISCAN_STATE_SCANING;
+
+ memset(&iscan->iscan_ex_params_p->params, 0,
+ iscan->iscan_ex_param_size);
+ wl_iw_iscan_prep(&iscan->iscan_ex_params_p->params, &ssid);
+ wl_iw_iscan(iscan, &ssid, WL_SCAN_ACTION_START);
+
+ if (flag)
+ rtnl_unlock();
+
+ mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000);
+
+ iscan->timer_on = 1;
+
+ return 0;
+}
+
+static int
+wl_iw_iscan_set_scan(struct net_device *dev,
+ struct iw_request_info *info,
+ union iwreq_data *wrqu, char *extra)
+{
+ wlc_ssid_t ssid;
+ iscan_info_t *iscan = g_iscan;
+
+ WL_TRACE(("%s: SIOCSIWSCAN : ISCAN\n", dev->name));
+
+#if defined(CSCAN)
+ WL_ERROR(("%s: Scan from SIOCGIWSCAN not supported\n", __func__));
+ return -EINVAL;
+#endif
+
+ if (g_onoff == G_WLAN_SET_OFF) {
+ WL_TRACE(("%s: driver is not up yet after START\n", __func__));
+ return 0;
+ }
+#ifdef PNO_SUPPORT
+ if (dhd_dev_get_pno_status(dev)) {
+ WL_ERROR(("%s: Scan called when PNO is active\n", __func__));
+ }
+#endif
+
+ if ((!iscan) || (!iscan->sysioc_tsk))
+ return wl_iw_set_scan(dev, info, wrqu, extra);
+
+ if (g_scan_specified_ssid) {
+ WL_TRACE(("%s Specific SCAN already running ignoring BC scan\n",
+ __func__));
+ return EBUSY;
+ }
+
+ memset(&ssid, 0, sizeof(ssid));
+
+#if WIRELESS_EXT > 17
+ if (wrqu->data.length == sizeof(struct iw_scan_req)) {
+ if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
+ struct iw_scan_req *req = (struct iw_scan_req *)extra;
+ ssid.SSID_len = min_t(size_t, sizeof(ssid.SSID),
+ req->essid_len);
+ memcpy(ssid.SSID, req->essid, ssid.SSID_len);
+ ssid.SSID_len = htod32(ssid.SSID_len);
+ } else {
+ g_scan_specified_ssid = 0;
+
+ if (iscan->iscan_state == ISCAN_STATE_SCANING) {
+ WL_TRACE(("%s ISCAN already in progress \n",
+ __func__));
+ return 0;
+ }
+ }
+ }
+#endif /* WIRELESS_EXT > 17 */
+ wl_iw_iscan_set_scan_broadcast_prep(dev, 0);
+
+ return 0;
+}
+#endif /* WL_IW_USE_ISCAN */
+
+#if WIRELESS_EXT > 17
+static bool ie_is_wpa_ie(u8 **wpaie, u8 **tlvs, int *tlvs_len)
+{
+
+ u8 *ie = *wpaie;
+
+ if ((ie[1] >= 6) &&
+ !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x01"), 4)) {
+ return true;
+ }
+
+ ie += ie[1] + 2;
+ *tlvs_len -= (int)(ie - *tlvs);
+ *tlvs = ie;
+ return false;
+}
+
+static bool ie_is_wps_ie(u8 **wpsie, u8 **tlvs, int *tlvs_len)
+{
+
+ u8 *ie = *wpsie;
+
+ if ((ie[1] >= 4) &&
+ !bcmp((const void *)&ie[2], (const void *)(WPA_OUI "\x04"), 4)) {
+ return true;
+ }
+
+ ie += ie[1] + 2;
+ *tlvs_len -= (int)(ie - *tlvs);
+ *tlvs = ie;
+ return false;
+}
+#endif /* WIRELESS_EXT > 17 */
+
+static int
+wl_iw_handle_scanresults_ies(char **event_p, char *end,
+ struct iw_request_info *info, wl_bss_info_t *bi)
+{
+#if WIRELESS_EXT > 17
+ struct iw_event iwe;
+ char *event;
+
+ event = *event_p;
+ if (bi->ie_length) {
+ bcm_tlv_t *ie;
+ u8 *ptr = ((u8 *) bi) + sizeof(wl_bss_info_t);
+ int ptr_len = bi->ie_length;
+
+ ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_RSN_ID);
+ if (ie) {
+ iwe.cmd = IWEVGENIE;
+ iwe.u.data.length = ie->len + 2;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe,
+ (char *)ie);
+ }
+ ptr = ((u8 *) bi) + sizeof(wl_bss_info_t);
+
+ while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) {
+ if (ie_is_wps_ie(((u8 **)&ie), &ptr, &ptr_len)) {
+ iwe.cmd = IWEVGENIE;
+ iwe.u.data.length = ie->len + 2;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe,
+ (char *)ie);
+ break;
+ }
+ }
+
+ ptr = ((u8 *) bi) + sizeof(wl_bss_info_t);
+ ptr_len = bi->ie_length;
+ while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) {
+ if (ie_is_wpa_ie(((u8 **)&ie), &ptr, &ptr_len)) {
+ iwe.cmd = IWEVGENIE;
+ iwe.u.data.length = ie->len + 2;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe,
+ (char *)ie);
+ break;
+ }
+ }
+
+ *event_p = event;
+ }
+#endif /* WIRELESS_EXT > 17 */
+ return 0;
+}
+
+static uint
+wl_iw_get_scan_prep(wl_scan_results_t *list,
+ struct iw_request_info *info, char *extra, short max_size)
+{
+ int i, j;
+ struct iw_event iwe;
+ wl_bss_info_t *bi = NULL;
+ char *event = extra, *end = extra + max_size - WE_ADD_EVENT_FIX, *value;
+ int ret = 0;
+
+ ASSERT(list);
+
+ for (i = 0; i < list->count && i < IW_MAX_AP; i++) {
+ if (list->version != WL_BSS_INFO_VERSION) {
+ WL_ERROR(("%s : list->version %d != "
+ "WL_BSS_INFO_VERSION\n",
+ __func__, list->version));
+ return ret;
+ }
+
+ bi = bi ? (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32(bi->length)) : list->
+ bss_info;
+
+ WL_TRACE(("%s : %s\n", __func__, bi->SSID));
+
+ iwe.cmd = SIOCGIWAP;
+ iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
+ memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID, ETHER_ADDR_LEN);
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_ADDR_LEN);
+ iwe.u.data.length = dtoh32(bi->SSID_len);
+ iwe.cmd = SIOCGIWESSID;
+ iwe.u.data.flags = 1;
+ event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, bi->SSID);
+
+ if (dtoh16(bi->capability) & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) {
+ iwe.cmd = SIOCGIWMODE;
+ if (dtoh16(bi->capability) & DOT11_CAP_ESS)
+ iwe.u.mode = IW_MODE_INFRA;
+ else
+ iwe.u.mode = IW_MODE_ADHOC;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_UINT_LEN);
+ }
+
+ iwe.cmd = SIOCGIWFREQ;
+ iwe.u.freq.m = wf_channel2mhz(CHSPEC_CHANNEL(bi->chanspec),
+ CHSPEC_CHANNEL(bi->chanspec) <=
+ CH_MAX_2G_CHANNEL ?
+ WF_CHAN_FACTOR_2_4_G :
+ WF_CHAN_FACTOR_5_G);
+ iwe.u.freq.e = 6;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_FREQ_LEN);
+
+ iwe.cmd = IWEVQUAL;
+ iwe.u.qual.qual = rssi_to_qual(dtoh16(bi->RSSI));
+ iwe.u.qual.level = 0x100 + dtoh16(bi->RSSI);
+ iwe.u.qual.noise = 0x100 + bi->phy_noise;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_QUAL_LEN);
+
+ wl_iw_handle_scanresults_ies(&event, end, info, bi);
+
+ iwe.cmd = SIOCGIWENCODE;
+ if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY)
+ iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+ else
+ iwe.u.data.flags = IW_ENCODE_DISABLED;
+ iwe.u.data.length = 0;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)event);
+
+ if (bi->rateset.count) {
+ if (((event - extra) +
+ IW_EV_LCP_LEN) <= (unsigned long)end) {
+ value = event + IW_EV_LCP_LEN;
+ iwe.cmd = SIOCGIWRATE;
+ iwe.u.bitrate.fixed = iwe.u.bitrate.disabled =
+ 0;
+ for (j = 0;
+ j < bi->rateset.count
+ && j < IW_MAX_BITRATES; j++) {
+ iwe.u.bitrate.value =
+ (bi->rateset.rates[j] & 0x7f) *
+ 500000;
+ value =
+ IWE_STREAM_ADD_VALUE(info, event,
+ value, end, &iwe,
+ IW_EV_PARAM_LEN);
+ }
+ event = value;
+ }
+ }
+ }
+
+ ret = event - extra;
+ if (ret < 0) {
+ WL_ERROR(("==> Wrong size\n"));
+ ret = 0;
+ }
+ WL_TRACE(("%s: size=%d bytes prepared\n", __func__,
+ (unsigned int)(event - extra)));
+ return (uint)ret;
+}
+
+static int
+wl_iw_get_scan(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *dwrq, char *extra)
+{
+ channel_info_t ci;
+ wl_scan_results_t *list_merge;
+ wl_scan_results_t *list = (wl_scan_results_t *) g_scan;
+ int error;
+ uint buflen_from_user = dwrq->length;
+ uint len = G_SCAN_RESULTS;
+ __u16 len_ret = 0;
+#if defined(WL_IW_USE_ISCAN)
+ iscan_info_t *iscan = g_iscan;
+ iscan_buf_t *p_buf;
+#endif
+
+ WL_TRACE(("%s: buflen_from_user %d: \n", dev->name, buflen_from_user));
+
+ if (!extra) {
+ WL_TRACE(("%s: wl_iw_get_scan return -EINVAL\n", dev->name));
+ return -EINVAL;
+ }
+
+ error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci));
+ if (error)
+ return error;
+ ci.scan_channel = dtoh32(ci.scan_channel);
+ if (ci.scan_channel)
+ return -EAGAIN;
+
+ if (g_scan_specified_ssid) {
+ list = kmalloc(len, GFP_KERNEL);
+ if (!list) {
+ WL_TRACE(("%s: wl_iw_get_scan return -ENOMEM\n",
+ dev->name));
+ g_scan_specified_ssid = 0;
+ return -ENOMEM;
+ }
+ }
+
+ memset(list, 0, len);
+ list->buflen = htod32(len);
+ error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, len);
+ if (error) {
+ WL_ERROR(("%s: %s : Scan_results ERROR %d\n", dev->name,
+ __func__, error));
+ dwrq->length = len;
+ if (g_scan_specified_ssid) {
+ g_scan_specified_ssid = 0;
+ kfree(list);
+ }
+ return 0;
+ }
+ list->buflen = dtoh32(list->buflen);
+ list->version = dtoh32(list->version);
+ list->count = dtoh32(list->count);
+
+ if (list->version != WL_BSS_INFO_VERSION) {
+ WL_ERROR(("%s : list->version %d != WL_BSS_INFO_VERSION\n",
+ __func__, list->version));
+ if (g_scan_specified_ssid) {
+ g_scan_specified_ssid = 0;
+ kfree(list);
+ }
+ return -EINVAL;
+ }
+
+ if (g_scan_specified_ssid) {
+ WL_TRACE(("%s: Specified scan APs in the list =%d\n",
+ __func__, list->count));
+ len_ret =
+ (__u16) wl_iw_get_scan_prep(list, info, extra,
+ buflen_from_user);
+ kfree(list);
+
+#if defined(WL_IW_USE_ISCAN)
+ p_buf = iscan->list_hdr;
+ while (p_buf != iscan->list_cur) {
+ list_merge =
+ &((wl_iscan_results_t *) p_buf->iscan_buf)->results;
+ WL_TRACE(("%s: Bcast APs list=%d\n", __func__,
+ list_merge->count));
+ if (list_merge->count > 0)
+ len_ret +=
+ (__u16) wl_iw_get_scan_prep(list_merge,
+ info, extra + len_ret,
+ buflen_from_user - len_ret);
+ p_buf = p_buf->next;
+ }
+#else
+ list_merge = (wl_scan_results_t *) g_scan;
+ WL_TRACE(("%s: Bcast APs list=%d\n", __func__,
+ list_merge->count));
+ if (list_merge->count > 0)
+ len_ret +=
+ (__u16) wl_iw_get_scan_prep(list_merge, info,
+ extra + len_ret,
+ buflen_from_user -
+ len_ret);
+#endif /* defined(WL_IW_USE_ISCAN) */
+ } else {
+ list = (wl_scan_results_t *) g_scan;
+ len_ret =
+ (__u16) wl_iw_get_scan_prep(list, info, extra,
+ buflen_from_user);
+ }
+
+#if defined(WL_IW_USE_ISCAN)
+ g_scan_specified_ssid = 0;
+#endif
+ if ((len_ret + WE_ADD_EVENT_FIX) < buflen_from_user)
+ len = len_ret;
+
+ dwrq->length = len;
+ dwrq->flags = 0;
+
+ WL_TRACE(("%s return to WE %d bytes APs=%d\n", __func__,
+ dwrq->length, list->count));
+ return 0;
+}
+
+#if defined(WL_IW_USE_ISCAN)
+static int
+wl_iw_iscan_get_scan(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_scan_results_t *list;
+ struct iw_event iwe;
+ wl_bss_info_t *bi = NULL;
+ int ii, j;
+ int apcnt;
+ char *event = extra, *end = extra + dwrq->length, *value;
+ iscan_info_t *iscan = g_iscan;
+ iscan_buf_t *p_buf;
+ u32 counter = 0;
+ u8 channel;
+
+ WL_TRACE(("%s %s buflen_from_user %d:\n", dev->name, __func__,
+ dwrq->length));
+
+ if (!extra) {
+ WL_TRACE(("%s: INVALID SIOCGIWSCAN GET bad parameter\n",
+ dev->name));
+ return -EINVAL;
+ }
+
+ if ((!iscan) || (!iscan->sysioc_tsk)) {
+ WL_ERROR(("%ssysioc_tsk\n", __func__));
+ return wl_iw_get_scan(dev, info, dwrq, extra);
+ }
+
+ if (iscan->iscan_state == ISCAN_STATE_SCANING) {
+ WL_TRACE(("%s: SIOCGIWSCAN GET still scanning\n", dev->name));
+ return -EAGAIN;
+ }
+
+ WL_TRACE(("%s: SIOCGIWSCAN GET broadcast results\n", dev->name));
+ apcnt = 0;
+ p_buf = iscan->list_hdr;
+ while (p_buf != iscan->list_cur) {
+ list = &((wl_iscan_results_t *) p_buf->iscan_buf)->results;
+
+ counter += list->count;
+
+ if (list->version != WL_BSS_INFO_VERSION) {
+ WL_ERROR(("%s : list->version %d != "
+ "WL_BSS_INFO_VERSION\n",
+ __func__, list->version));
+ return -EINVAL;
+ }
+
+ bi = NULL;
+ for (ii = 0; ii < list->count && apcnt < IW_MAX_AP;
+ apcnt++, ii++) {
+ bi = bi ? (wl_bss_info_t *)((unsigned long)bi +
+ dtoh32(bi->length)) :
+ list->bss_info;
+ ASSERT(((unsigned long)bi + dtoh32(bi->length)) <=
+ ((unsigned long)list + WLC_IW_ISCAN_MAXLEN));
+
+ if (event + ETHER_ADDR_LEN + bi->SSID_len +
+ IW_EV_UINT_LEN + IW_EV_FREQ_LEN + IW_EV_QUAL_LEN >=
+ end)
+ return -E2BIG;
+ iwe.cmd = SIOCGIWAP;
+ iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
+ memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID,
+ ETHER_ADDR_LEN);
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_ADDR_LEN);
+
+ iwe.u.data.length = dtoh32(bi->SSID_len);
+ iwe.cmd = SIOCGIWESSID;
+ iwe.u.data.flags = 1;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe,
+ bi->SSID);
+
+ if (dtoh16(bi->capability) &
+ (DOT11_CAP_ESS | DOT11_CAP_IBSS)) {
+ iwe.cmd = SIOCGIWMODE;
+ if (dtoh16(bi->capability) & DOT11_CAP_ESS)
+ iwe.u.mode = IW_MODE_INFRA;
+ else
+ iwe.u.mode = IW_MODE_ADHOC;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_UINT_LEN);
+ }
+
+ iwe.cmd = SIOCGIWFREQ;
+ channel =
+ (bi->ctl_ch ==
+ 0) ? CHSPEC_CHANNEL(bi->chanspec) : bi->ctl_ch;
+ iwe.u.freq.m =
+ wf_channel2mhz(channel,
+ channel <=
+ CH_MAX_2G_CHANNEL ?
+ WF_CHAN_FACTOR_2_4_G :
+ WF_CHAN_FACTOR_5_G);
+ iwe.u.freq.e = 6;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_FREQ_LEN);
+
+ iwe.cmd = IWEVQUAL;
+ iwe.u.qual.qual = rssi_to_qual(dtoh16(bi->RSSI));
+ iwe.u.qual.level = 0x100 + dtoh16(bi->RSSI);
+ iwe.u.qual.noise = 0x100 + bi->phy_noise;
+ event =
+ IWE_STREAM_ADD_EVENT(info, event, end, &iwe,
+ IW_EV_QUAL_LEN);
+
+ wl_iw_handle_scanresults_ies(&event, end, info, bi);
+
+ iwe.cmd = SIOCGIWENCODE;
+ if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY)
+ iwe.u.data.flags =
+ IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
+ else
+ iwe.u.data.flags = IW_ENCODE_DISABLED;
+ iwe.u.data.length = 0;
+ event =
+ IWE_STREAM_ADD_POINT(info, event, end, &iwe,
+ (char *)event);
+
+ if (bi->rateset.count) {
+ if (event + IW_MAX_BITRATES * IW_EV_PARAM_LEN >=
+ end)
+ return -E2BIG;
+
+ value = event + IW_EV_LCP_LEN;
+ iwe.cmd = SIOCGIWRATE;
+ iwe.u.bitrate.fixed = iwe.u.bitrate.disabled =
+ 0;
+ for (j = 0;
+ j < bi->rateset.count
+ && j < IW_MAX_BITRATES; j++) {
+ iwe.u.bitrate.value =
+ (bi->rateset.rates[j] & 0x7f) *
+ 500000;
+ value =
+ IWE_STREAM_ADD_VALUE(info, event,
+ value, end,
+ &iwe,
+ IW_EV_PARAM_LEN);
+ }
+ event = value;
+ }
+ }
+ p_buf = p_buf->next;
+ }
+
+ dwrq->length = event - extra;
+ dwrq->flags = 0;
+
+ WL_TRACE(("%s return to WE %d bytes APs=%d\n", __func__,
+ dwrq->length, counter));
+
+ if (!dwrq->length)
+ return -EAGAIN;
+
+ return 0;
+}
+#endif /* defined(WL_IW_USE_ISCAN) */
+
+static int
+wl_iw_set_essid(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ int error;
+ wl_join_params_t join_params;
+ int join_params_size;
+
+ WL_TRACE(("%s: SIOCSIWESSID\n", dev->name));
+
+ if (g_set_essid_before_scan)
+ return -EAGAIN;
+
+ memset(&g_ssid, 0, sizeof(g_ssid));
+
+ CHECK_EXTRA_FOR_NULL(extra);
+
+ if (dwrq->length && extra) {
+#if WIRELESS_EXT > 20
+ g_ssid.SSID_len = min_t(size_t, sizeof(g_ssid.SSID),
+ dwrq->length);
+#else
+ g_ssid.SSID_len = min_t(size_t, sizeof(g_ssid.SSID),
+ dwrq->length - 1);
+#endif
+ memcpy(g_ssid.SSID, extra, g_ssid.SSID_len);
+ } else {
+ g_ssid.SSID_len = 0;
+ }
+ g_ssid.SSID_len = htod32(g_ssid.SSID_len);
+
+ memset(&join_params, 0, sizeof(join_params));
+ join_params_size = sizeof(join_params.ssid);
+
+ memcpy(&join_params.ssid.SSID, g_ssid.SSID, g_ssid.SSID_len);
+ join_params.ssid.SSID_len = htod32(g_ssid.SSID_len);
+ memcpy(&join_params.params.bssid, &ether_bcast, ETHER_ADDR_LEN);
+
+ wl_iw_ch_to_chanspec(g_wl_iw_params.target_channel, &join_params,
+ &join_params_size);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params,
+ join_params_size);
+ if (error)
+ WL_ERROR(("Invalid ioctl data=%d\n", error));
+
+ if (g_ssid.SSID_len) {
+ WL_TRACE(("%s: join SSID=%s ch=%d\n", __func__,
+ g_ssid.SSID, g_wl_iw_params.target_channel));
+ }
+ return 0;
+}
+
+static int
+wl_iw_get_essid(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wlc_ssid_t ssid;
+ int error;
+
+ WL_TRACE(("%s: SIOCGIWESSID\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid));
+ if (error) {
+ WL_ERROR(("Error getting the SSID\n"));
+ return error;
+ }
+
+ ssid.SSID_len = dtoh32(ssid.SSID_len);
+
+ memcpy(extra, ssid.SSID, ssid.SSID_len);
+
+ dwrq->length = ssid.SSID_len;
+
+ dwrq->flags = 1;
+
+ return 0;
+}
+
+static int
+wl_iw_set_nick(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *dwrq, char *extra)
+{
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+
+ WL_TRACE(("%s: SIOCSIWNICKN\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ if (dwrq->length > sizeof(iw->nickname))
+ return -E2BIG;
+
+ memcpy(iw->nickname, extra, dwrq->length);
+ iw->nickname[dwrq->length - 1] = '\0';
+
+ return 0;
+}
+
+static int
+wl_iw_get_nick(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *dwrq, char *extra)
+{
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+
+ WL_TRACE(("%s: SIOCGIWNICKN\n", dev->name));
+
+ if (!extra)
+ return -EINVAL;
+
+ strcpy(extra, iw->nickname);
+ dwrq->length = strlen(extra) + 1;
+
+ return 0;
+}
+
+static int
+wl_iw_set_rate(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ wl_rateset_t rateset;
+ int error, rate, i, error_bg, error_a;
+
+ WL_TRACE(("%s: SIOCSIWRATE\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset,
+ sizeof(rateset));
+ if (error)
+ return error;
+
+ rateset.count = dtoh32(rateset.count);
+
+ if (vwrq->value < 0)
+ rate = rateset.rates[rateset.count - 1] & 0x7f;
+ else if (vwrq->value < rateset.count)
+ rate = rateset.rates[vwrq->value] & 0x7f;
+ else
+ rate = vwrq->value / 500000;
+
+ if (vwrq->fixed) {
+ error_bg = dev_wlc_intvar_set(dev, "bg_rate", rate);
+ error_a = dev_wlc_intvar_set(dev, "a_rate", rate);
+
+ if (error_bg && error_a)
+ return error_bg | error_a;
+ } else {
+ error_bg = dev_wlc_intvar_set(dev, "bg_rate", 0);
+ error_a = dev_wlc_intvar_set(dev, "a_rate", 0);
+
+ if (error_bg && error_a)
+ return error_bg | error_a;
+
+ for (i = 0; i < rateset.count; i++)
+ if ((rateset.rates[i] & 0x7f) > rate)
+ break;
+ rateset.count = htod32(i);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_RATESET, &rateset,
+ sizeof(rateset));
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+static int
+wl_iw_get_rate(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ int error, rate;
+
+ WL_TRACE(("%s: SIOCGIWRATE\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate));
+ if (error)
+ return error;
+ rate = dtoh32(rate);
+ vwrq->value = rate * 500000;
+
+ return 0;
+}
+
+static int
+wl_iw_set_rts(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ int error, rts;
+
+ WL_TRACE(("%s: SIOCSIWRTS\n", dev->name));
+
+ if (vwrq->disabled)
+ rts = DOT11_DEFAULT_RTS_LEN;
+ else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_RTS_LEN)
+ return -EINVAL;
+ else
+ rts = vwrq->value;
+
+ error = dev_wlc_intvar_set(dev, "rtsthresh", rts);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int
+wl_iw_get_rts(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ int error, rts;
+
+ WL_TRACE(("%s: SIOCGIWRTS\n", dev->name));
+
+ error = dev_wlc_intvar_get(dev, "rtsthresh", &rts);
+ if (error)
+ return error;
+
+ vwrq->value = rts;
+ vwrq->disabled = (rts >= DOT11_DEFAULT_RTS_LEN);
+ vwrq->fixed = 1;
+
+ return 0;
+}
+
+static int
+wl_iw_set_frag(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ int error, frag;
+
+ WL_TRACE(("%s: SIOCSIWFRAG\n", dev->name));
+
+ if (vwrq->disabled)
+ frag = DOT11_DEFAULT_FRAG_LEN;
+ else if (vwrq->value < 0 || vwrq->value > DOT11_DEFAULT_FRAG_LEN)
+ return -EINVAL;
+ else
+ frag = vwrq->value;
+
+ error = dev_wlc_intvar_set(dev, "fragthresh", frag);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int
+wl_iw_get_frag(struct net_device *dev,
+ struct iw_request_info *info, struct iw_param *vwrq, char *extra)
+{
+ int error, fragthreshold;
+
+ WL_TRACE(("%s: SIOCGIWFRAG\n", dev->name));
+
+ error = dev_wlc_intvar_get(dev, "fragthresh", &fragthreshold);
+ if (error)
+ return error;
+
+ vwrq->value = fragthreshold;
+ vwrq->disabled = (fragthreshold >= DOT11_DEFAULT_FRAG_LEN);
+ vwrq->fixed = 1;
+
+ return 0;
+}
+
+static int
+wl_iw_set_txpow(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, disable;
+ u16 txpwrmw;
+ WL_TRACE(("%s: SIOCSIWTXPOW\n", dev->name));
+
+ disable = vwrq->disabled ? WL_RADIO_SW_DISABLE : 0;
+ disable += WL_RADIO_SW_DISABLE << 16;
+
+ disable = htod32(disable);
+ error = dev_wlc_ioctl(dev, WLC_SET_RADIO, &disable, sizeof(disable));
+ if (error)
+ return error;
+
+ if (disable & WL_RADIO_SW_DISABLE)
+ return 0;
+
+ if (!(vwrq->flags & IW_TXPOW_MWATT))
+ return -EINVAL;
+
+ if (vwrq->value < 0)
+ return 0;
+
+ if (vwrq->value > 0xffff)
+ txpwrmw = 0xffff;
+ else
+ txpwrmw = (u16) vwrq->value;
+
+ error =
+ dev_wlc_intvar_set(dev, "qtxpower", (int)(bcm_mw_to_qdbm(txpwrmw)));
+ return error;
+}
+
+static int
+wl_iw_get_txpow(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, disable, txpwrdbm;
+ u8 result;
+
+ WL_TRACE(("%s: SIOCGIWTXPOW\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_RADIO, &disable, sizeof(disable));
+ if (error)
+ return error;
+
+ error = dev_wlc_intvar_get(dev, "qtxpower", &txpwrdbm);
+ if (error)
+ return error;
+
+ disable = dtoh32(disable);
+ result = (u8) (txpwrdbm & ~WL_TXPWR_OVERRIDE);
+ vwrq->value = (s32) bcm_qdbm_to_mw(result);
+ vwrq->fixed = 0;
+ vwrq->disabled =
+ (disable & (WL_RADIO_SW_DISABLE | WL_RADIO_HW_DISABLE)) ? 1 : 0;
+ vwrq->flags = IW_TXPOW_MWATT;
+
+ return 0;
+}
+
+#if WIRELESS_EXT > 10
+static int
+wl_iw_set_retry(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, lrl, srl;
+
+ WL_TRACE(("%s: SIOCSIWRETRY\n", dev->name));
+
+ if (vwrq->disabled || (vwrq->flags & IW_RETRY_LIFETIME))
+ return -EINVAL;
+
+ if (vwrq->flags & IW_RETRY_LIMIT) {
+
+#if WIRELESS_EXT > 20
+ if ((vwrq->flags & IW_RETRY_LONG)
+ || (vwrq->flags & IW_RETRY_MAX)
+ || !((vwrq->flags & IW_RETRY_SHORT)
+ || (vwrq->flags & IW_RETRY_MIN))) {
+#else
+ if ((vwrq->flags & IW_RETRY_MAX)
+ || !(vwrq->flags & IW_RETRY_MIN)) {
+#endif
+ lrl = htod32(vwrq->value);
+ error = dev_wlc_ioctl(dev, WLC_SET_LRL, &lrl,
+ sizeof(lrl));
+ if (error)
+ return error;
+ }
+#if WIRELESS_EXT > 20
+ if ((vwrq->flags & IW_RETRY_SHORT)
+ || (vwrq->flags & IW_RETRY_MIN)
+ || !((vwrq->flags & IW_RETRY_LONG)
+ || (vwrq->flags & IW_RETRY_MAX))) {
+#else
+ if ((vwrq->flags & IW_RETRY_MIN)
+ || !(vwrq->flags & IW_RETRY_MAX)) {
+#endif
+ srl = htod32(vwrq->value);
+ error = dev_wlc_ioctl(dev, WLC_SET_SRL, &srl,
+ sizeof(srl));
+ if (error)
+ return error;
+ }
+ }
+ return 0;
+}
+
+static int
+wl_iw_get_retry(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, lrl, srl;
+
+ WL_TRACE(("%s: SIOCGIWRETRY\n", dev->name));
+
+ vwrq->disabled = 0;
+
+ if ((vwrq->flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME)
+ return -EINVAL;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_LRL, &lrl, sizeof(lrl));
+ if (error)
+ return error;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_SRL, &srl, sizeof(srl));
+ if (error)
+ return error;
+
+ lrl = dtoh32(lrl);
+ srl = dtoh32(srl);
+
+ if (vwrq->flags & IW_RETRY_MAX) {
+ vwrq->flags = IW_RETRY_LIMIT | IW_RETRY_MAX;
+ vwrq->value = lrl;
+ } else {
+ vwrq->flags = IW_RETRY_LIMIT;
+ vwrq->value = srl;
+ if (srl != lrl)
+ vwrq->flags |= IW_RETRY_MIN;
+ }
+
+ return 0;
+}
+#endif /* WIRELESS_EXT > 10 */
+
+static int
+wl_iw_set_encode(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_wsec_key_t key;
+ int error, val, wsec;
+
+ WL_TRACE(("%s: SIOCSIWENCODE\n", dev->name));
+
+ memset(&key, 0, sizeof(key));
+
+ if ((dwrq->flags & IW_ENCODE_INDEX) == 0) {
+ for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS;
+ key.index++) {
+ val = htod32(key.index);
+ error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val,
+ sizeof(val));
+ if (error)
+ return error;
+ val = dtoh32(val);
+ if (val)
+ break;
+ }
+ if (key.index == DOT11_MAX_DEFAULT_KEYS)
+ key.index = 0;
+ } else {
+ key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
+ if (key.index >= DOT11_MAX_DEFAULT_KEYS)
+ return -EINVAL;
+ }
+
+ if (!extra || !dwrq->length || (dwrq->flags & IW_ENCODE_NOKEY)) {
+ val = htod32(key.index);
+ error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, &val,
+ sizeof(val));
+ if (error)
+ return error;
+ } else {
+ key.len = dwrq->length;
+
+ if (dwrq->length > sizeof(key.data))
+ return -EINVAL;
+
+ memcpy(key.data, extra, dwrq->length);
+
+ key.flags = WL_PRIMARY_KEY;
+ switch (key.len) {
+ case WEP1_KEY_SIZE:
+ key.algo = CRYPTO_ALGO_WEP1;
+ break;
+ case WEP128_KEY_SIZE:
+ key.algo = CRYPTO_ALGO_WEP128;
+ break;
+ case TKIP_KEY_SIZE:
+ key.algo = CRYPTO_ALGO_TKIP;
+ break;
+ case AES_KEY_SIZE:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ swap_key_from_BE(&key);
+ error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (error)
+ return error;
+ }
+
+ val = (dwrq->flags & IW_ENCODE_DISABLED) ? 0 : WEP_ENABLED;
+
+ error = dev_wlc_intvar_get(dev, "wsec", &wsec);
+ if (error)
+ return error;
+
+ wsec &= ~(WEP_ENABLED);
+ wsec |= val;
+
+ error = dev_wlc_intvar_set(dev, "wsec", wsec);
+ if (error)
+ return error;
+
+ val = (dwrq->flags & IW_ENCODE_RESTRICTED) ? 1 : 0;
+ val = htod32(val);
+ error = dev_wlc_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val));
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int
+wl_iw_get_encode(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_wsec_key_t key;
+ int error, val, wsec, auth;
+
+ WL_TRACE(("%s: SIOCGIWENCODE\n", dev->name));
+
+ bzero(&key, sizeof(wl_wsec_key_t));
+
+ if ((dwrq->flags & IW_ENCODE_INDEX) == 0) {
+ for (key.index = 0; key.index < DOT11_MAX_DEFAULT_KEYS;
+ key.index++) {
+ val = key.index;
+ error = dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val,
+ sizeof(val));
+ if (error)
+ return error;
+ val = dtoh32(val);
+ if (val)
+ break;
+ }
+ } else
+ key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
+
+ if (key.index >= DOT11_MAX_DEFAULT_KEYS)
+ key.index = 0;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec));
+ if (error)
+ return error;
+
+ error = dev_wlc_ioctl(dev, WLC_GET_AUTH, &auth, sizeof(auth));
+ if (error)
+ return error;
+
+ swap_key_to_BE(&key);
+
+ wsec = dtoh32(wsec);
+ auth = dtoh32(auth);
+ dwrq->length = min_t(u16, DOT11_MAX_KEY_SIZE, key.len);
+
+ dwrq->flags = key.index + 1;
+ if (!(wsec & (WEP_ENABLED | TKIP_ENABLED | AES_ENABLED)))
+ dwrq->flags |= IW_ENCODE_DISABLED;
+
+ if (auth)
+ dwrq->flags |= IW_ENCODE_RESTRICTED;
+
+ if (dwrq->length && extra)
+ memcpy(extra, key.data, dwrq->length);
+
+ return 0;
+}
+
+static int
+wl_iw_set_power(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, pm;
+
+ WL_TRACE(("%s: SIOCSIWPOWER\n", dev->name));
+
+ pm = vwrq->disabled ? PM_OFF : PM_MAX;
+
+ pm = htod32(pm);
+ error = dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm));
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int
+wl_iw_get_power(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error, pm;
+
+ WL_TRACE(("%s: SIOCGIWPOWER\n", dev->name));
+
+ error = dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm));
+ if (error)
+ return error;
+
+ pm = dtoh32(pm);
+ vwrq->disabled = pm ? 0 : 1;
+ vwrq->flags = IW_POWER_ALL_R;
+
+ return 0;
+}
+
+#if WIRELESS_EXT > 17
+static int
+wl_iw_set_wpaie(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *iwp, char *extra)
+{
+
+ WL_TRACE(("%s: SIOCSIWGENIE\n", dev->name));
+
+ CHECK_EXTRA_FOR_NULL(extra);
+
+ dev_wlc_bufvar_set(dev, "wpaie", extra, iwp->length);
+
+ return 0;
+}
+
+static int
+wl_iw_get_wpaie(struct net_device *dev,
+ struct iw_request_info *info, struct iw_point *iwp, char *extra)
+{
+ WL_TRACE(("%s: SIOCGIWGENIE\n", dev->name));
+ iwp->length = 64;
+ dev_wlc_bufvar_get(dev, "wpaie", extra, iwp->length);
+ return 0;
+}
+
+static int
+wl_iw_set_encodeext(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_point *dwrq, char *extra)
+{
+ wl_wsec_key_t key;
+ int error;
+ struct iw_encode_ext *iwe;
+
+ WL_TRACE(("%s: SIOCSIWENCODEEXT\n", dev->name));
+
+ CHECK_EXTRA_FOR_NULL(extra);
+
+ memset(&key, 0, sizeof(key));
+ iwe = (struct iw_encode_ext *)extra;
+
+ if (dwrq->flags & IW_ENCODE_DISABLED) {
+
+ }
+
+ key.index = 0;
+ if (dwrq->flags & IW_ENCODE_INDEX)
+ key.index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
+
+ key.len = iwe->key_len;
+
+ if (!ETHER_ISMULTI(iwe->addr.sa_data))
+ bcopy((void *)&iwe->addr.sa_data, (char *)&key.ea,
+ ETHER_ADDR_LEN);
+
+ if (key.len == 0) {
+ if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) {
+ WL_WSEC(("Changing the the primary Key to %d\n",
+ key.index));
+ key.index = htod32(key.index);
+ error = dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY,
+ &key.index, sizeof(key.index));
+ if (error)
+ return error;
+ } else {
+ swap_key_from_BE(&key);
+ dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ }
+ } else {
+ if (iwe->key_len > sizeof(key.data))
+ return -EINVAL;
+
+ WL_WSEC(("Setting the key index %d\n", key.index));
+ if (iwe->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) {
+ WL_WSEC(("key is a Primary Key\n"));
+ key.flags = WL_PRIMARY_KEY;
+ }
+
+ bcopy((void *)iwe->key, key.data, iwe->key_len);
+
+ if (iwe->alg == IW_ENCODE_ALG_TKIP) {
+ u8 keybuf[8];
+ bcopy(&key.data[24], keybuf, sizeof(keybuf));
+ bcopy(&key.data[16], &key.data[24], sizeof(keybuf));
+ bcopy(keybuf, &key.data[16], sizeof(keybuf));
+ }
+
+ if (iwe->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) {
+ unsigned char *ivptr;
+ ivptr = (unsigned char *) iwe->rx_seq;
+ key.rxiv.hi = (ivptr[5] << 24) | (ivptr[4] << 16) |
+ (ivptr[3] << 8) | ivptr[2];
+ key.rxiv.lo = (ivptr[1] << 8) | ivptr[0];
+ key.iv_initialized = true;
+ }
+
+ switch (iwe->alg) {
+ case IW_ENCODE_ALG_NONE:
+ key.algo = CRYPTO_ALGO_OFF;
+ break;
+ case IW_ENCODE_ALG_WEP:
+ if (iwe->key_len == WEP1_KEY_SIZE)
+ key.algo = CRYPTO_ALGO_WEP1;
+ else
+ key.algo = CRYPTO_ALGO_WEP128;
+ break;
+ case IW_ENCODE_ALG_TKIP:
+ key.algo = CRYPTO_ALGO_TKIP;
+ break;
+ case IW_ENCODE_ALG_CCMP:
+ key.algo = CRYPTO_ALGO_AES_CCM;
+ break;
+ default:
+ break;
+ }
+ swap_key_from_BE(&key);
+
+ dhd_wait_pend8021x(dev);
+
+ error = dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key));
+ if (error)
+ return error;
+ }
+ return 0;
+}
+
+#if WIRELESS_EXT > 17
+struct {
+ pmkid_list_t pmkids;
+ pmkid_t foo[MAXPMKID - 1];
+} pmkid_list;
+
+static int
+wl_iw_set_pmksa(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ struct iw_pmksa *iwpmksa;
+ uint i;
+ int ret = 0;
+
+ WL_WSEC(("%s: SIOCSIWPMKSA\n", dev->name));
+
+ CHECK_EXTRA_FOR_NULL(extra);
+
+ iwpmksa = (struct iw_pmksa *)extra;
+
+ if (iwpmksa->cmd == IW_PMKSA_FLUSH) {
+ WL_WSEC(("wl_iw_set_pmksa - IW_PMKSA_FLUSH\n"));
+ bzero((char *)&pmkid_list, sizeof(pmkid_list));
+ }
+
+ else if (iwpmksa->cmd == IW_PMKSA_REMOVE) {
+ {
+ pmkid_list_t pmkid, *pmkidptr;
+ uint j;
+ pmkidptr = &pmkid;
+
+ bcopy(&iwpmksa->bssid.sa_data[0],
+ &pmkidptr->pmkid[0].BSSID, ETHER_ADDR_LEN);
+ bcopy(&iwpmksa->pmkid[0], &pmkidptr->pmkid[0].PMKID,
+ WPA2_PMKID_LEN);
+
+ WL_WSEC(("wl_iw_set_pmksa:IW_PMKSA_REMOVE:PMKID: "
+ "%pM = ", &pmkidptr->pmkid[0].BSSID));
+ for (j = 0; j < WPA2_PMKID_LEN; j++)
+ WL_WSEC(("%02x ", pmkidptr->pmkid[0].PMKID[j]));
+ WL_WSEC(("\n"));
+ }
+
+ for (i = 0; i < pmkid_list.pmkids.npmkid; i++)
+ if (!bcmp
+ (&iwpmksa->bssid.sa_data[0],
+ &pmkid_list.pmkids.pmkid[i].BSSID, ETHER_ADDR_LEN))
+ break;
+
+ if ((pmkid_list.pmkids.npmkid > 0)
+ && (i < pmkid_list.pmkids.npmkid)) {
+ bzero(&pmkid_list.pmkids.pmkid[i], sizeof(pmkid_t));
+ for (; i < (pmkid_list.pmkids.npmkid - 1); i++) {
+ bcopy(&pmkid_list.pmkids.pmkid[i + 1].BSSID,
+ &pmkid_list.pmkids.pmkid[i].BSSID,
+ ETHER_ADDR_LEN);
+ bcopy(&pmkid_list.pmkids.pmkid[i + 1].PMKID,
+ &pmkid_list.pmkids.pmkid[i].PMKID,
+ WPA2_PMKID_LEN);
+ }
+ pmkid_list.pmkids.npmkid--;
+ } else
+ ret = -EINVAL;
+ }
+
+ else if (iwpmksa->cmd == IW_PMKSA_ADD) {
+ for (i = 0; i < pmkid_list.pmkids.npmkid; i++)
+ if (!bcmp
+ (&iwpmksa->bssid.sa_data[0],
+ &pmkid_list.pmkids.pmkid[i].BSSID, ETHER_ADDR_LEN))
+ break;
+ if (i < MAXPMKID) {
+ bcopy(&iwpmksa->bssid.sa_data[0],
+ &pmkid_list.pmkids.pmkid[i].BSSID,
+ ETHER_ADDR_LEN);
+ bcopy(&iwpmksa->pmkid[0],
+ &pmkid_list.pmkids.pmkid[i].PMKID,
+ WPA2_PMKID_LEN);
+ if (i == pmkid_list.pmkids.npmkid)
+ pmkid_list.pmkids.npmkid++;
+ } else
+ ret = -EINVAL;
+ {
+ uint j;
+ uint k;
+ k = pmkid_list.pmkids.npmkid;
+ WL_WSEC(("wl_iw_set_pmksa,IW_PMKSA_ADD - PMKID: %pM = ",
+ &pmkid_list.pmkids.pmkid[k].BSSID));
+ for (j = 0; j < WPA2_PMKID_LEN; j++)
+ WL_WSEC(("%02x ",
+ pmkid_list.pmkids.pmkid[k].PMKID[j]));
+ WL_WSEC(("\n"));
+ }
+ }
+ WL_WSEC(("PRINTING pmkid LIST - No of elements %d\n",
+ pmkid_list.pmkids.npmkid));
+ for (i = 0; i < pmkid_list.pmkids.npmkid; i++) {
+ uint j;
+ WL_WSEC(("PMKID[%d]: %pM = ", i,
+ &pmkid_list.pmkids.pmkid[i].BSSID));
+ for (j = 0; j < WPA2_PMKID_LEN; j++)
+ WL_WSEC(("%02x ", pmkid_list.pmkids.pmkid[i].PMKID[j]));
+ WL_WSEC(("\n"));
+ }
+ WL_WSEC(("\n"));
+
+ if (!ret)
+ ret = dev_wlc_bufvar_set(dev, "pmkid_info", (char *)&pmkid_list,
+ sizeof(pmkid_list));
+ return ret;
+}
+#endif /* WIRELESS_EXT > 17 */
+
+static int
+wl_iw_get_encodeext(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ WL_TRACE(("%s: SIOCGIWENCODEEXT\n", dev->name));
+ return 0;
+}
+
+static int
+wl_iw_set_wpaauth(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error = 0;
+ int paramid;
+ int paramval;
+ int val = 0;
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+
+ WL_TRACE(("%s: SIOCSIWAUTH\n", dev->name));
+
+ paramid = vwrq->flags & IW_AUTH_INDEX;
+ paramval = vwrq->value;
+
+ WL_TRACE(("%s: SIOCSIWAUTH, paramid = 0x%0x, paramval = 0x%0x\n",
+ dev->name, paramid, paramval));
+
+ switch (paramid) {
+ case IW_AUTH_WPA_VERSION:
+ if (paramval & IW_AUTH_WPA_VERSION_DISABLED)
+ val = WPA_AUTH_DISABLED;
+ else if (paramval & (IW_AUTH_WPA_VERSION_WPA))
+ val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED;
+ else if (paramval & IW_AUTH_WPA_VERSION_WPA2)
+ val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
+ WL_INFORM(("%s: %d: setting wpa_auth to 0x%0x\n", __func__,
+ __LINE__, val));
+ error = dev_wlc_intvar_set(dev, "wpa_auth", val);
+ if (error)
+ return error;
+ break;
+ case IW_AUTH_CIPHER_PAIRWISE:
+ case IW_AUTH_CIPHER_GROUP:
+ if (paramval & (IW_AUTH_CIPHER_WEP40 | IW_AUTH_CIPHER_WEP104))
+ val = WEP_ENABLED;
+ if (paramval & IW_AUTH_CIPHER_TKIP)
+ val = TKIP_ENABLED;
+ if (paramval & IW_AUTH_CIPHER_CCMP)
+ val = AES_ENABLED;
+
+ if (paramid == IW_AUTH_CIPHER_PAIRWISE) {
+ iw->pwsec = val;
+ val |= iw->gwsec;
+ } else {
+ iw->gwsec = val;
+ val |= iw->pwsec;
+ }
+
+ if (iw->privacy_invoked && !val) {
+ WL_WSEC(("%s: %s: 'Privacy invoked' true but clearing "
+ "wsec, assuming " "we're a WPS enrollee\n",
+ dev->name, __func__));
+ error = dev_wlc_intvar_set(dev, "is_WPS_enrollee",
+ true);
+ if (error) {
+ WL_WSEC(("Failed to set is_WPS_enrollee\n"));
+ return error;
+ }
+ } else if (val) {
+ error = dev_wlc_intvar_set(dev, "is_WPS_enrollee",
+ false);
+ if (error) {
+ WL_WSEC(("Failed to clear is_WPS_enrollee\n"));
+ return error;
+ }
+ }
+
+ error = dev_wlc_intvar_set(dev, "wsec", val);
+ if (error)
+ return error;
+
+ break;
+
+ case IW_AUTH_KEY_MGMT:
+ error = dev_wlc_intvar_get(dev, "wpa_auth", &val);
+ if (error)
+ return error;
+
+ if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED)) {
+ if (paramval & IW_AUTH_KEY_MGMT_PSK)
+ val = WPA_AUTH_PSK;
+ else
+ val = WPA_AUTH_UNSPECIFIED;
+ } else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) {
+ if (paramval & IW_AUTH_KEY_MGMT_PSK)
+ val = WPA2_AUTH_PSK;
+ else
+ val = WPA2_AUTH_UNSPECIFIED;
+ }
+ WL_INFORM(("%s: %d: setting wpa_auth to %d\n", __func__,
+ __LINE__, val));
+ error = dev_wlc_intvar_set(dev, "wpa_auth", val);
+ if (error)
+ return error;
+
+ break;
+ case IW_AUTH_TKIP_COUNTERMEASURES:
+ dev_wlc_bufvar_set(dev, "tkip_countermeasures",
+ (char *)&paramval, 1);
+ break;
+
+ case IW_AUTH_80211_AUTH_ALG:
+ WL_INFORM(("Setting the D11auth %d\n", paramval));
+ if (paramval == IW_AUTH_ALG_OPEN_SYSTEM)
+ val = 0;
+ else if (paramval == IW_AUTH_ALG_SHARED_KEY)
+ val = 1;
+ else if (paramval ==
+ (IW_AUTH_ALG_OPEN_SYSTEM | IW_AUTH_ALG_SHARED_KEY))
+ val = 2;
+ else
+ error = 1;
+ if (!error) {
+ error = dev_wlc_intvar_set(dev, "auth", val);
+ if (error)
+ return error;
+ }
+ break;
+
+ case IW_AUTH_WPA_ENABLED:
+ if (paramval == 0) {
+ iw->pwsec = 0;
+ iw->gwsec = 0;
+ error = dev_wlc_intvar_get(dev, "wsec", &val);
+ if (error)
+ return error;
+ if (val & (TKIP_ENABLED | AES_ENABLED)) {
+ val &= ~(TKIP_ENABLED | AES_ENABLED);
+ dev_wlc_intvar_set(dev, "wsec", val);
+ }
+ val = 0;
+ WL_INFORM(("%s: %d: setting wpa_auth to %d\n",
+ __func__, __LINE__, val));
+ dev_wlc_intvar_set(dev, "wpa_auth", 0);
+ return error;
+ }
+ break;
+
+ case IW_AUTH_DROP_UNENCRYPTED:
+ dev_wlc_bufvar_set(dev, "wsec_restrict", (char *)&paramval, 1);
+ break;
+
+ case IW_AUTH_RX_UNENCRYPTED_EAPOL:
+ dev_wlc_bufvar_set(dev, "rx_unencrypted_eapol",
+ (char *)&paramval, 1);
+ break;
+
+#if WIRELESS_EXT > 17
+ case IW_AUTH_ROAMING_CONTROL:
+ WL_INFORM(("%s: IW_AUTH_ROAMING_CONTROL\n", __func__));
+ break;
+ case IW_AUTH_PRIVACY_INVOKED:
+ {
+ int wsec;
+
+ if (paramval == 0) {
+ iw->privacy_invoked = false;
+ error = dev_wlc_intvar_set(dev,
+ "is_WPS_enrollee", false);
+ if (error) {
+ WL_WSEC(("Failed to clear iovar "
+ "is_WPS_enrollee\n"));
+ return error;
+ }
+ } else {
+ iw->privacy_invoked = true;
+ error = dev_wlc_intvar_get(dev, "wsec", &wsec);
+ if (error)
+ return error;
+
+ if (!(IW_WSEC_ENABLED(wsec))) {
+ error = dev_wlc_intvar_set(dev,
+ "is_WPS_enrollee",
+ true);
+ if (error) {
+ WL_WSEC(("Failed to set iovar "
+ "is_WPS_enrollee\n"));
+ return error;
+ }
+ } else {
+ error = dev_wlc_intvar_set(dev,
+ "is_WPS_enrollee",
+ false);
+ if (error) {
+ WL_WSEC(("Failed to clear "
+ "is_WPS_enrollee\n"));
+ return error;
+ }
+ }
+ }
+ break;
+ }
+#endif /* WIRELESS_EXT > 17 */
+ default:
+ break;
+ }
+ return 0;
+}
+
+#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK) || ((_val) & WPA2_AUTH_PSK))
+
+static int
+wl_iw_get_wpaauth(struct net_device *dev,
+ struct iw_request_info *info,
+ struct iw_param *vwrq, char *extra)
+{
+ int error;
+ int paramid;
+ int paramval = 0;
+ int val;
+ wl_iw_t *iw = *(wl_iw_t **) netdev_priv(dev);
+
+ WL_TRACE(("%s: SIOCGIWAUTH\n", dev->name));
+
+ paramid = vwrq->flags & IW_AUTH_INDEX;
+
+ switch (paramid) {
+ case IW_AUTH_WPA_VERSION:
+ error = dev_wlc_intvar_get(dev, "wpa_auth", &val);
+ if (error)
+ return error;
+ if (val & (WPA_AUTH_NONE | WPA_AUTH_DISABLED))
+ paramval = IW_AUTH_WPA_VERSION_DISABLED;
+ else if (val & (WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED))
+ paramval = IW_AUTH_WPA_VERSION_WPA;
+ else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED))
+ paramval = IW_AUTH_WPA_VERSION_WPA2;
+ break;
+ case IW_AUTH_CIPHER_PAIRWISE:
+ case IW_AUTH_CIPHER_GROUP:
+ if (paramid == IW_AUTH_CIPHER_PAIRWISE)
+ val = iw->pwsec;
+ else
+ val = iw->gwsec;
+
+ paramval = 0;
+ if (val) {
+ if (val & WEP_ENABLED)
+ paramval |=
+ (IW_AUTH_CIPHER_WEP40 |
+ IW_AUTH_CIPHER_WEP104);
+ if (val & TKIP_ENABLED)
+ paramval |= (IW_AUTH_CIPHER_TKIP);
+ if (val & AES_ENABLED)
+ paramval |= (IW_AUTH_CIPHER_CCMP);
+ } else
+ paramval = IW_AUTH_CIPHER_NONE;
+ break;
+ case IW_AUTH_KEY_MGMT:
+ error = dev_wlc_intvar_get(dev, "wpa_auth", &val);
+ if (error)
+ return error;
+ if (VAL_PSK(val))
+ paramval = IW_AUTH_KEY_MGMT_PSK;
+ else
+ paramval = IW_AUTH_KEY_MGMT_802_1X;
+
+ break;
+ case IW_AUTH_TKIP_COUNTERMEASURES:
+ dev_wlc_bufvar_get(dev, "tkip_countermeasures",
+ (char *)&paramval, 1);
+ break;
+
+ case IW_AUTH_DROP_UNENCRYPTED:
+ dev_wlc_bufvar_get(dev, "wsec_restrict", (char *)&paramval, 1);
+ break;
+
+ case IW_AUTH_RX_UNENCRYPTED_EAPOL:
+ dev_wlc_bufvar_get(dev, "rx_unencrypted_eapol",
+ (char *)&paramval, 1);
+ break;
+
+ case IW_AUTH_80211_AUTH_ALG:
+ error = dev_wlc_intvar_get(dev, "auth", &val);
+ if (error)
+ return error;
+ if (!val)
+ paramval = IW_AUTH_ALG_OPEN_SYSTEM;
+ else
+ paramval = IW_AUTH_ALG_SHARED_KEY;
+ break;
+ case IW_AUTH_WPA_ENABLED:
+ error = dev_wlc_intvar_get(dev, "wpa_auth", &val);
+ if (error)
+ return error;
+ if (val)
+ paramval = true;
+ else
+ paramval = false;
+ break;
+#if WIRELESS_EXT > 17
+ case IW_AUTH_ROAMING_CONTROL:
+ WL_ERROR(("%s: IW_AUTH_ROAMING_CONTROL\n", __func__));
+ break;
+ case IW_AUTH_PRIVACY_INVOKED:
+ paramval = iw->privacy_invoked;
+ break;
+
+#endif
+ }
+ vwrq->value = paramval;
+ return 0;
+}
+#endif /* WIRELESS_EXT > 17 */
+
+static const iw_handler wl_iw_handler[] = {
+ (iw_handler) wl_iw_config_commit,
+ (iw_handler) wl_iw_get_name,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_set_freq,
+ (iw_handler) wl_iw_get_freq,
+ (iw_handler) wl_iw_set_mode,
+ (iw_handler) wl_iw_get_mode,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_get_range,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_set_spy,
+ (iw_handler) wl_iw_get_spy,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_set_wap,
+ (iw_handler) wl_iw_get_wap,
+#if WIRELESS_EXT > 17
+ (iw_handler) wl_iw_mlme,
+#else
+ (iw_handler) NULL,
+#endif
+#if defined(WL_IW_USE_ISCAN)
+ (iw_handler) wl_iw_iscan_get_aplist,
+#else
+ (iw_handler) wl_iw_get_aplist,
+#endif
+#if WIRELESS_EXT > 13
+#if defined(WL_IW_USE_ISCAN)
+ (iw_handler) wl_iw_iscan_set_scan,
+ (iw_handler) wl_iw_iscan_get_scan,
+#else
+ (iw_handler) wl_iw_set_scan,
+ (iw_handler) wl_iw_get_scan,
+#endif
+#else
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+#endif /* WIRELESS_EXT > 13 */
+ (iw_handler) wl_iw_set_essid,
+ (iw_handler) wl_iw_get_essid,
+ (iw_handler) wl_iw_set_nick,
+ (iw_handler) wl_iw_get_nick,
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_set_rate,
+ (iw_handler) wl_iw_get_rate,
+ (iw_handler) wl_iw_set_rts,
+ (iw_handler) wl_iw_get_rts,
+ (iw_handler) wl_iw_set_frag,
+ (iw_handler) wl_iw_get_frag,
+ (iw_handler) wl_iw_set_txpow,
+ (iw_handler) wl_iw_get_txpow,
+#if WIRELESS_EXT > 10
+ (iw_handler) wl_iw_set_retry,
+ (iw_handler) wl_iw_get_retry,
+#endif
+ (iw_handler) wl_iw_set_encode,
+ (iw_handler) wl_iw_get_encode,
+ (iw_handler) wl_iw_set_power,
+ (iw_handler) wl_iw_get_power,
+#if WIRELESS_EXT > 17
+ (iw_handler) NULL,
+ (iw_handler) NULL,
+ (iw_handler) wl_iw_set_wpaie,
+ (iw_handler) wl_iw_get_wpaie,
+ (iw_handler) wl_iw_set_wpaauth,
+ (iw_handler) wl_iw_get_wpaauth,
+ (iw_handler) wl_iw_set_encodeext,
+ (iw_handler) wl_iw_get_encodeext,
+ (iw_handler) wl_iw_set_pmksa,
+#endif /* WIRELESS_EXT > 17 */
+};
+
+#if WIRELESS_EXT > 12
+
+const struct iw_handler_def wl_iw_handler_def = {
+ .num_standard = ARRAY_SIZE(wl_iw_handler),
+ .standard = (iw_handler *) wl_iw_handler,
+ .num_private = 0,
+ .num_private_args = 0,
+ .private = 0,
+ .private_args = 0,
+
+#if WIRELESS_EXT >= 19
+ .get_wireless_stats = dhd_get_wireless_stats,
+#endif
+};
+#endif /* WIRELESS_EXT > 12 */
+
+int wl_iw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ struct iwreq *wrq = (struct iwreq *)rq;
+ struct iw_request_info info;
+ iw_handler handler;
+ char *extra = NULL;
+ int token_size = 1, max_tokens = 0, ret = 0;
+
+ WL_TRACE(("\n%s, cmd:%x alled via dhd->do_ioctl()entry point\n",
+ __func__, cmd));
+ if (cmd < SIOCIWFIRST ||
+ IW_IOCTL_IDX(cmd) >= ARRAY_SIZE(wl_iw_handler)) {
+ WL_ERROR(("%s: error in cmd=%x : out of range\n", __func__,
+ cmd));
+ return -EOPNOTSUPP;
+ }
+
+ handler = wl_iw_handler[IW_IOCTL_IDX(cmd)];
+ if (!handler) {
+ WL_ERROR(("%s: error in cmd=%x : not supported\n",
+ __func__, cmd));
+ return -EOPNOTSUPP;
+ }
+
+ switch (cmd) {
+
+ case SIOCSIWESSID:
+ case SIOCGIWESSID:
+ case SIOCSIWNICKN:
+ case SIOCGIWNICKN:
+ max_tokens = IW_ESSID_MAX_SIZE + 1;
+ break;
+
+ case SIOCSIWENCODE:
+ case SIOCGIWENCODE:
+#if WIRELESS_EXT > 17
+ case SIOCSIWENCODEEXT:
+ case SIOCGIWENCODEEXT:
+#endif
+ max_tokens = wrq->u.data.length;
+ break;
+
+ case SIOCGIWRANGE:
+ max_tokens = sizeof(struct iw_range) + 500;
+ break;
+
+ case SIOCGIWAPLIST:
+ token_size =
+ sizeof(struct sockaddr) + sizeof(struct iw_quality);
+ max_tokens = IW_MAX_AP;
+ break;
+
+#if WIRELESS_EXT > 13
+ case SIOCGIWSCAN:
+#if defined(WL_IW_USE_ISCAN)
+ if (g_iscan)
+ max_tokens = wrq->u.data.length;
+ else
+#endif
+ max_tokens = IW_SCAN_MAX_DATA;
+ break;
+#endif /* WIRELESS_EXT > 13 */
+
+ case SIOCSIWSPY:
+ token_size = sizeof(struct sockaddr);
+ max_tokens = IW_MAX_SPY;
+ break;
+
+ case SIOCGIWSPY:
+ token_size =
+ sizeof(struct sockaddr) + sizeof(struct iw_quality);
+ max_tokens = IW_MAX_SPY;
+ break;
+
+#if WIRELESS_EXT > 17
+ case SIOCSIWPMKSA:
+ case SIOCSIWGENIE:
+#endif
+ case SIOCSIWPRIV:
+ max_tokens = wrq->u.data.length;
+ break;
+ }
+
+ if (max_tokens && wrq->u.data.pointer) {
+ if (wrq->u.data.length > max_tokens) {
+ WL_ERROR(("%s: error in cmd=%x wrq->u.data.length=%d "
+ "> max_tokens=%d\n",
+ __func__, cmd, wrq->u.data.length, max_tokens));
+ return -E2BIG;
+ }
+ extra = kmalloc(max_tokens * token_size, GFP_KERNEL);
+ if (!extra)
+ return -ENOMEM;
+
+ if (copy_from_user
+ (extra, wrq->u.data.pointer,
+ wrq->u.data.length * token_size)) {
+ kfree(extra);
+ return -EFAULT;
+ }
+ }
+
+ info.cmd = cmd;
+ info.flags = 0;
+
+ ret = handler(dev, &info, &wrq->u, extra);
+
+ if (extra) {
+ if (copy_to_user
+ (wrq->u.data.pointer, extra,
+ wrq->u.data.length * token_size)) {
+ kfree(extra);
+ return -EFAULT;
+ }
+
+ kfree(extra);
+ }
+
+ return ret;
+}
+
+bool
+wl_iw_conn_status_str(u32 event_type, u32 status, u32 reason,
+ char *stringBuf, uint buflen)
+{
+ typedef struct conn_fail_event_map_t {
+ u32 inEvent;
+ u32 inStatus;
+ u32 inReason;
+ const char *outName;
+ const char *outCause;
+ } conn_fail_event_map_t;
+
+#define WL_IW_DONT_CARE 9999
+ const conn_fail_event_map_t event_map[] = {
+ {WLC_E_SET_SSID, WLC_E_STATUS_SUCCESS, WL_IW_DONT_CARE,
+ "Conn", "Success"},
+ {WLC_E_SET_SSID, WLC_E_STATUS_NO_NETWORKS, WL_IW_DONT_CARE,
+ "Conn", "NoNetworks"},
+ {WLC_E_SET_SSID, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE,
+ "Conn", "ConfigMismatch"},
+ {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_PRUNE_ENCR_MISMATCH,
+ "Conn", "EncrypMismatch"},
+ {WLC_E_PRUNE, WL_IW_DONT_CARE, WLC_E_RSN_MISMATCH,
+ "Conn", "RsnMismatch"},
+ {WLC_E_AUTH, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE,
+ "Conn", "AuthTimeout"},
+ {WLC_E_AUTH, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE,
+ "Conn", "AuthFail"},
+ {WLC_E_AUTH, WLC_E_STATUS_NO_ACK, WL_IW_DONT_CARE,
+ "Conn", "AuthNoAck"},
+ {WLC_E_REASSOC, WLC_E_STATUS_FAIL, WL_IW_DONT_CARE,
+ "Conn", "ReassocFail"},
+ {WLC_E_REASSOC, WLC_E_STATUS_TIMEOUT, WL_IW_DONT_CARE,
+ "Conn", "ReassocTimeout"},
+ {WLC_E_REASSOC, WLC_E_STATUS_ABORT, WL_IW_DONT_CARE,
+ "Conn", "ReassocAbort"},
+ {WLC_E_PSK_SUP, WLC_SUP_KEYED, WL_IW_DONT_CARE,
+ "Sup", "ConnSuccess"},
+ {WLC_E_PSK_SUP, WL_IW_DONT_CARE, WL_IW_DONT_CARE,
+ "Sup", "WpaHandshakeFail"},
+ {WLC_E_DEAUTH_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE,
+ "Conn", "Deauth"},
+ {WLC_E_DISASSOC_IND, WL_IW_DONT_CARE, WL_IW_DONT_CARE,
+ "Conn", "DisassocInd"},
+ {WLC_E_DISASSOC, WL_IW_DONT_CARE, WL_IW_DONT_CARE,
+ "Conn", "Disassoc"}
+ };
+
+ const char *name = "";
+ const char *cause = NULL;
+ int i;
+
+ for (i = 0; i < sizeof(event_map) / sizeof(event_map[0]); i++) {
+ const conn_fail_event_map_t *row = &event_map[i];
+ if (row->inEvent == event_type &&
+ (row->inStatus == status
+ || row->inStatus == WL_IW_DONT_CARE)
+ && (row->inReason == reason
+ || row->inReason == WL_IW_DONT_CARE)) {
+ name = row->outName;
+ cause = row->outCause;
+ break;
+ }
+ }
+
+ if (cause) {
+ memset(stringBuf, 0, buflen);
+ snprintf(stringBuf, buflen, "%s %s %02d %02d",
+ name, cause, status, reason);
+ WL_INFORM(("Connection status: %s\n", stringBuf));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#if WIRELESS_EXT > 14
+
+static bool
+wl_iw_check_conn_fail(wl_event_msg_t *e, char *stringBuf, uint buflen)
+{
+ u32 event = ntoh32(e->event_type);
+ u32 status = ntoh32(e->status);
+ u32 reason = ntoh32(e->reason);
+
+ if (wl_iw_conn_status_str(event, status, reason, stringBuf, buflen)) {
+ return true;
+ } else
+ return false;
+}
+#endif
+
+#ifndef IW_CUSTOM_MAX
+#define IW_CUSTOM_MAX 256
+#endif
+
+void wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void *data)
+{
+#if WIRELESS_EXT > 13
+ union iwreq_data wrqu;
+ char extra[IW_CUSTOM_MAX + 1];
+ int cmd = 0;
+ u32 event_type = ntoh32(e->event_type);
+ u16 flags = ntoh16(e->flags);
+ u32 datalen = ntoh32(e->datalen);
+ u32 status = ntoh32(e->status);
+ wl_iw_t *iw;
+ u32 toto;
+ memset(&wrqu, 0, sizeof(wrqu));
+ memset(extra, 0, sizeof(extra));
+ iw = 0;
+
+ if (!dev) {
+ WL_ERROR(("%s: dev is null\n", __func__));
+ return;
+ }
+
+ iw = *(wl_iw_t **) netdev_priv(dev);
+
+ WL_TRACE(("%s: dev=%s event=%d\n", __func__, dev->name, event_type));
+
+ switch (event_type) {
+ case WLC_E_TXFAIL:
+ cmd = IWEVTXDROP;
+ memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN);
+ wrqu.addr.sa_family = ARPHRD_ETHER;
+ break;
+#if WIRELESS_EXT > 14
+ case WLC_E_JOIN:
+ case WLC_E_ASSOC_IND:
+ case WLC_E_REASSOC_IND:
+ memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN);
+ wrqu.addr.sa_family = ARPHRD_ETHER;
+ cmd = IWEVREGISTERED;
+ break;
+ case WLC_E_DEAUTH_IND:
+ case WLC_E_DISASSOC_IND:
+ cmd = SIOCGIWAP;
+ bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN);
+ wrqu.addr.sa_family = ARPHRD_ETHER;
+ bzero(&extra, ETHER_ADDR_LEN);
+ break;
+ case WLC_E_LINK:
+ case WLC_E_NDIS_LINK:
+ cmd = SIOCGIWAP;
+ if (!(flags & WLC_EVENT_MSG_LINK)) {
+ bzero(wrqu.addr.sa_data, ETHER_ADDR_LEN);
+ bzero(&extra, ETHER_ADDR_LEN);
+ WAKE_LOCK_TIMEOUT(iw->pub, WAKE_LOCK_LINK_DOWN_TMOUT,
+ 20 * HZ);
+ } else {
+ memcpy(wrqu.addr.sa_data, &e->addr, ETHER_ADDR_LEN);
+ WL_TRACE(("Link UP\n"));
+
+ }
+ wrqu.addr.sa_family = ARPHRD_ETHER;
+ break;
+ case WLC_E_ACTION_FRAME:
+ cmd = IWEVCUSTOM;
+ if (datalen + 1 <= sizeof(extra)) {
+ wrqu.data.length = datalen + 1;
+ extra[0] = WLC_E_ACTION_FRAME;
+ memcpy(&extra[1], data, datalen);
+ WL_TRACE(("WLC_E_ACTION_FRAME len %d \n",
+ wrqu.data.length));
+ }
+ break;
+
+ case WLC_E_ACTION_FRAME_COMPLETE:
+ cmd = IWEVCUSTOM;
+ memcpy(&toto, data, 4);
+ if (sizeof(status) + 1 <= sizeof(extra)) {
+ wrqu.data.length = sizeof(status) + 1;
+ extra[0] = WLC_E_ACTION_FRAME_COMPLETE;
+ memcpy(&extra[1], &status, sizeof(status));
+ printf("wl_iw_event status %d PacketId %d\n", status,
+ toto);
+ printf("WLC_E_ACTION_FRAME_COMPLETE len %d\n",
+ wrqu.data.length);
+ }
+ break;
+#endif /* WIRELESS_EXT > 14 */
+#if WIRELESS_EXT > 17
+ case WLC_E_MIC_ERROR:
+ {
+ struct iw_michaelmicfailure *micerrevt =
+ (struct iw_michaelmicfailure *)&extra;
+ cmd = IWEVMICHAELMICFAILURE;
+ wrqu.data.length = sizeof(struct iw_michaelmicfailure);
+ if (flags & WLC_EVENT_MSG_GROUP)
+ micerrevt->flags |= IW_MICFAILURE_GROUP;
+ else
+ micerrevt->flags |= IW_MICFAILURE_PAIRWISE;
+ memcpy(micerrevt->src_addr.sa_data, &e->addr,
+ ETHER_ADDR_LEN);
+ micerrevt->src_addr.sa_family = ARPHRD_ETHER;
+
+ break;
+ }
+ case WLC_E_PMKID_CACHE:
+ {
+ if (data) {
+ struct iw_pmkid_cand *iwpmkidcand =
+ (struct iw_pmkid_cand *)&extra;
+ pmkid_cand_list_t *pmkcandlist;
+ pmkid_cand_t *pmkidcand;
+ int count;
+
+ cmd = IWEVPMKIDCAND;
+ pmkcandlist = data;
+ count =
+ ntoh32_ua((u8 *) &
+ pmkcandlist->npmkid_cand);
+ ASSERT(count >= 0);
+ wrqu.data.length = sizeof(struct iw_pmkid_cand);
+ pmkidcand = pmkcandlist->pmkid_cand;
+ while (count) {
+ bzero(iwpmkidcand,
+ sizeof(struct iw_pmkid_cand));
+ if (pmkidcand->preauth)
+ iwpmkidcand->flags |=
+ IW_PMKID_CAND_PREAUTH;
+ bcopy(&pmkidcand->BSSID,
+ &iwpmkidcand->bssid.sa_data,
+ ETHER_ADDR_LEN);
+#ifndef SANDGATE2G
+ wireless_send_event(dev, cmd, &wrqu,
+ extra);
+#endif
+ pmkidcand++;
+ count--;
+ }
+ }
+ return;
+ }
+#endif /* WIRELESS_EXT > 17 */
+
+ case WLC_E_SCAN_COMPLETE:
+#if defined(WL_IW_USE_ISCAN)
+ if ((g_iscan) && (g_iscan->sysioc_tsk) &&
+ (g_iscan->iscan_state != ISCAN_STATE_IDLE)) {
+ up(&g_iscan->sysioc_sem);
+ } else {
+ cmd = SIOCGIWSCAN;
+ wrqu.data.length = strlen(extra);
+ WL_TRACE(("Event WLC_E_SCAN_COMPLETE from specific "
+ "scan %d\n", g_iscan->iscan_state));
+ }
+#else
+ cmd = SIOCGIWSCAN;
+ wrqu.data.length = strlen(extra);
+ WL_TRACE(("Event WLC_E_SCAN_COMPLETE\n"));
+#endif
+ break;
+
+ case WLC_E_PFN_NET_FOUND:
+ {
+ wlc_ssid_t *ssid;
+ ssid = (wlc_ssid_t *) data;
+ WL_ERROR(("%s Event WLC_E_PFN_NET_FOUND, send %s up : "
+ "find %s len=%d\n", __func__, PNO_EVENT_UP,
+ ssid->SSID, ssid->SSID_len));
+ WAKE_LOCK_TIMEOUT(iw->pub, WAKE_LOCK_PNO_FIND_TMOUT,
+ 20 * HZ);
+ cmd = IWEVCUSTOM;
+ memset(&wrqu, 0, sizeof(wrqu));
+ strcpy(extra, PNO_EVENT_UP);
+ wrqu.data.length = strlen(extra);
+ }
+ break;
+
+ default:
+ WL_TRACE(("Unknown Event %d: ignoring\n", event_type));
+ break;
+ }
+#ifndef SANDGATE2G
+ if (cmd) {
+ if (cmd == SIOCGIWSCAN)
+ wireless_send_event(dev, cmd, &wrqu, NULL);
+ else
+ wireless_send_event(dev, cmd, &wrqu, extra);
+ }
+#endif
+
+#if WIRELESS_EXT > 14
+ memset(extra, 0, sizeof(extra));
+ if (wl_iw_check_conn_fail(e, extra, sizeof(extra))) {
+ cmd = IWEVCUSTOM;
+ wrqu.data.length = strlen(extra);
+#ifndef SANDGATE2G
+ wireless_send_event(dev, cmd, &wrqu, extra);
+#endif
+ }
+#endif /* WIRELESS_EXT > 14 */
+#endif /* WIRELESS_EXT > 13 */
+}
+
+int
+wl_iw_get_wireless_stats(struct net_device *dev, struct iw_statistics *wstats)
+{
+ int res = 0;
+ wl_cnt_t cnt;
+ int phy_noise;
+ int rssi;
+ scb_val_t scb_val;
+
+ phy_noise = 0;
+ res = dev_wlc_ioctl(dev, WLC_GET_PHY_NOISE, &phy_noise,
+ sizeof(phy_noise));
+ if (res)
+ goto done;
+
+ phy_noise = dtoh32(phy_noise);
+ WL_TRACE(("wl_iw_get_wireless_stats phy noise=%d\n", phy_noise));
+
+ bzero(&scb_val, sizeof(scb_val_t));
+ res = dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t));
+ if (res)
+ goto done;
+
+ rssi = dtoh32(scb_val.val);
+ WL_TRACE(("wl_iw_get_wireless_stats rssi=%d\n", rssi));
+ if (rssi <= WL_IW_RSSI_NO_SIGNAL)
+ wstats->qual.qual = 0;
+ else if (rssi <= WL_IW_RSSI_VERY_LOW)
+ wstats->qual.qual = 1;
+ else if (rssi <= WL_IW_RSSI_LOW)
+ wstats->qual.qual = 2;
+ else if (rssi <= WL_IW_RSSI_GOOD)
+ wstats->qual.qual = 3;
+ else if (rssi <= WL_IW_RSSI_VERY_GOOD)
+ wstats->qual.qual = 4;
+ else
+ wstats->qual.qual = 5;
+
+ wstats->qual.level = 0x100 + rssi;
+ wstats->qual.noise = 0x100 + phy_noise;
+#if WIRELESS_EXT > 18
+ wstats->qual.updated |= (IW_QUAL_ALL_UPDATED | IW_QUAL_DBM);
+#else
+ wstats->qual.updated |= 7;
+#endif
+
+#if WIRELESS_EXT > 11
+ WL_TRACE(("wl_iw_get_wireless_stats counters=%d\n",
+ (int)sizeof(wl_cnt_t)));
+
+ memset(&cnt, 0, sizeof(wl_cnt_t));
+ res =
+ dev_wlc_bufvar_get(dev, "counters", (char *)&cnt, sizeof(wl_cnt_t));
+ if (res) {
+ WL_ERROR(("wl_iw_get_wireless_stats counters failed error=%d\n",
+ res));
+ goto done;
+ }
+
+ cnt.version = dtoh16(cnt.version);
+ if (cnt.version != WL_CNT_T_VERSION) {
+ WL_TRACE(("\tIncorrect version of counters struct: expected "
+ "%d; got %d\n",
+ WL_CNT_T_VERSION, cnt.version));
+ goto done;
+ }
+
+ wstats->discard.nwid = 0;
+ wstats->discard.code = dtoh32(cnt.rxundec);
+ wstats->discard.fragment = dtoh32(cnt.rxfragerr);
+ wstats->discard.retries = dtoh32(cnt.txfail);
+ wstats->discard.misc = dtoh32(cnt.rxrunt) + dtoh32(cnt.rxgiant);
+ wstats->miss.beacon = 0;
+
+ WL_TRACE(("wl_iw_get_wireless_stats counters txframe=%d txbyte=%d\n",
+ dtoh32(cnt.txframe), dtoh32(cnt.txbyte)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxfrmtoolong=%d\n",
+ dtoh32(cnt.rxfrmtoolong)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxbadplcp=%d\n",
+ dtoh32(cnt.rxbadplcp)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxundec=%d\n",
+ dtoh32(cnt.rxundec)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxfragerr=%d\n",
+ dtoh32(cnt.rxfragerr)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters txfail=%d\n",
+ dtoh32(cnt.txfail)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxrunt=%d\n",
+ dtoh32(cnt.rxrunt)));
+ WL_TRACE(("wl_iw_get_wireless_stats counters rxgiant=%d\n",
+ dtoh32(cnt.rxgiant)));
+#endif /* WIRELESS_EXT > 11 */
+
+done:
+ return res;
+}
+
+int wl_iw_attach(struct net_device *dev, void *dhdp)
+{
+ int params_size;
+ wl_iw_t *iw;
+#if defined(WL_IW_USE_ISCAN)
+ iscan_info_t *iscan = NULL;
+
+ if (!dev)
+ return 0;
+
+ memset(&g_wl_iw_params, 0, sizeof(wl_iw_extra_params_t));
+
+#ifdef CSCAN
+ params_size =
+ (WL_SCAN_PARAMS_FIXED_SIZE + offsetof(wl_iscan_params_t, params)) +
+ (WL_NUMCHANNELS * sizeof(u16)) +
+ WL_SCAN_PARAMS_SSID_MAX * sizeof(wlc_ssid_t);
+#else
+ params_size =
+ (WL_SCAN_PARAMS_FIXED_SIZE + offsetof(wl_iscan_params_t, params));
+#endif
+ iscan = kmalloc(sizeof(iscan_info_t), GFP_KERNEL);
+
+ if (!iscan)
+ return -ENOMEM;
+ memset(iscan, 0, sizeof(iscan_info_t));
+
+ iscan->iscan_ex_params_p =
+ (wl_iscan_params_t *) kmalloc(params_size, GFP_KERNEL);
+ if (!iscan->iscan_ex_params_p)
+ return -ENOMEM;
+ iscan->iscan_ex_param_size = params_size;
+ iscan->sysioc_tsk = NULL;
+
+ g_iscan = iscan;
+ iscan->dev = dev;
+ iscan->iscan_state = ISCAN_STATE_IDLE;
+
+ iscan->timer_ms = 3000;
+ init_timer(&iscan->timer);
+ iscan->timer.data = (unsigned long) iscan;
+ iscan->timer.function = wl_iw_timerfunc;
+
+ sema_init(&iscan->sysioc_sem, 0);
+ iscan->sysioc_tsk = kthread_run(_iscan_sysioc_thread, iscan,
+ "_iscan_sysioc");
+ if (IS_ERR(iscan->sysioc_tsk)) {
+ iscan->sysioc_tsk = NULL;
+ return -ENOMEM;
+ }
+#endif /* defined(WL_IW_USE_ISCAN) */
+
+ iw = *(wl_iw_t **) netdev_priv(dev);
+ iw->pub = (dhd_pub_t *) dhdp;
+ MUTEX_LOCK_INIT(iw->pub);
+ MUTEX_LOCK_WL_SCAN_SET_INIT();
+#ifdef SOFTAP
+ priv_dev = dev;
+ MUTEX_LOCK_SOFTAP_SET_INIT(iw->pub);
+#endif
+ g_scan = NULL;
+
+ g_scan = (void *)kmalloc(G_SCAN_RESULTS, GFP_KERNEL);
+ if (!g_scan)
+ return -ENOMEM;
+
+ memset(g_scan, 0, G_SCAN_RESULTS);
+ g_scan_specified_ssid = 0;
+
+ return 0;
+}
+
+void wl_iw_detach(void)
+{
+#if defined(WL_IW_USE_ISCAN)
+ iscan_buf_t *buf;
+ iscan_info_t *iscan = g_iscan;
+
+ if (!iscan)
+ return;
+ if (iscan->sysioc_tsk) {
+ send_sig(SIGTERM, iscan->sysioc_tsk, 1);
+ kthread_stop(iscan->sysioc_tsk);
+ iscan->sysioc_tsk = NULL;
+ }
+
+ MUTEX_LOCK_WL_SCAN_SET();
+ while (iscan->list_hdr) {
+ buf = iscan->list_hdr->next;
+ kfree(iscan->list_hdr);
+ iscan->list_hdr = buf;
+ }
+ MUTEX_UNLOCK_WL_SCAN_SET();
+ kfree(iscan->iscan_ex_params_p);
+ kfree(iscan);
+ g_iscan = NULL;
+#endif /* WL_IW_USE_ISCAN */
+
+ kfree(g_scan);
+
+ g_scan = NULL;
+}
diff --git a/drivers/staging/brcm80211/brcmfmac/wl_iw.h b/drivers/staging/brcm80211/brcmfmac/wl_iw.h
new file mode 100644
index 000000000000..edbf61f30b47
--- /dev/null
+++ b/drivers/staging/brcm80211/brcmfmac/wl_iw.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _wl_iw_h_
+#define _wl_iw_h_
+
+#include <linux/wireless.h>
+
+#include <proto/ethernet.h>
+#include <wlioctl.h>
+
+#define WL_SCAN_PARAMS_SSID_MAX 10
+#define GET_SSID "SSID="
+#define GET_CHANNEL "CH="
+#define GET_NPROBE "NPROBE="
+#define GET_ACTIVE_ASSOC_DWELL "ACTIVE="
+#define GET_PASSIVE_ASSOC_DWELL "PASSIVE="
+#define GET_HOME_DWELL "HOME="
+#define GET_SCAN_TYPE "TYPE="
+
+#define BAND_GET_CMD "BANDGET"
+#define BAND_SET_CMD "BANDSET"
+#define DTIM_SKIP_GET_CMD "DTIMSKIPGET"
+#define DTIM_SKIP_SET_CMD "DTIMSKIPSET"
+#define SETSUSPEND_CMD "SETSUSPENDOPT"
+#define PNOSSIDCLR_SET_CMD "PNOSSIDCLR"
+#define PNOSETUP_SET_CMD "PNOSETUP"
+#define PNOENABLE_SET_CMD "PNOFORCE"
+#define PNODEBUG_SET_CMD "PNODEBUG"
+
+typedef struct wl_iw_extra_params {
+ int target_channel;
+} wl_iw_extra_params_t;
+
+#define WL_IW_RSSI_MINVAL -200
+#define WL_IW_RSSI_NO_SIGNAL -91
+#define WL_IW_RSSI_VERY_LOW -80
+#define WL_IW_RSSI_LOW -70
+#define WL_IW_RSSI_GOOD -68
+#define WL_IW_RSSI_VERY_GOOD -58
+#define WL_IW_RSSI_EXCELLENT -57
+#define WL_IW_RSSI_INVALID 0
+#define MAX_WX_STRING 80
+#define WL_IW_SET_ACTIVE_SCAN (SIOCIWFIRSTPRIV+1)
+#define WL_IW_GET_RSSI (SIOCIWFIRSTPRIV+3)
+#define WL_IW_SET_PASSIVE_SCAN (SIOCIWFIRSTPRIV+5)
+#define WL_IW_GET_LINK_SPEED (SIOCIWFIRSTPRIV+7)
+#define WL_IW_GET_CURR_MACADDR (SIOCIWFIRSTPRIV+9)
+#define WL_IW_SET_STOP (SIOCIWFIRSTPRIV+11)
+#define WL_IW_SET_START (SIOCIWFIRSTPRIV+13)
+
+#define WL_SET_AP_CFG (SIOCIWFIRSTPRIV+15)
+#define WL_AP_STA_LIST (SIOCIWFIRSTPRIV+17)
+#define WL_AP_MAC_FLTR (SIOCIWFIRSTPRIV+19)
+#define WL_AP_BSS_START (SIOCIWFIRSTPRIV+21)
+#define AP_LPB_CMD (SIOCIWFIRSTPRIV+23)
+#define WL_AP_STOP (SIOCIWFIRSTPRIV+25)
+#define WL_FW_RELOAD (SIOCIWFIRSTPRIV+27)
+#define WL_COMBO_SCAN (SIOCIWFIRSTPRIV+29)
+#define WL_AP_SPARE3 (SIOCIWFIRSTPRIV+31)
+#define G_SCAN_RESULTS (8*1024)
+#define WE_ADD_EVENT_FIX 0x80
+#define G_WLAN_SET_ON 0
+#define G_WLAN_SET_OFF 1
+
+#define CHECK_EXTRA_FOR_NULL(extra) \
+if (!extra) { \
+ WL_ERROR(("%s: error : extra is null pointer\n", __func__)); \
+ return -EINVAL; \
+}
+
+typedef struct wl_iw {
+ char nickname[IW_ESSID_MAX_SIZE];
+
+ struct iw_statistics wstats;
+
+ int spy_num;
+ u32 pwsec;
+ u32 gwsec;
+ bool privacy_invoked;
+
+ struct ether_addr spy_addr[IW_MAX_SPY];
+ struct iw_quality spy_qual[IW_MAX_SPY];
+ void *wlinfo;
+ dhd_pub_t *pub;
+} wl_iw_t;
+
+#if WIRELESS_EXT > 12
+#include <net/iw_handler.h>
+extern const struct iw_handler_def wl_iw_handler_def;
+#endif
+
+extern int wl_iw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+extern void wl_iw_event(struct net_device *dev, wl_event_msg_t *e, void *data);
+extern int wl_iw_get_wireless_stats(struct net_device *dev,
+ struct iw_statistics *wstats);
+int wl_iw_attach(struct net_device *dev, void *dhdp);
+void wl_iw_detach(void);
+extern int net_os_set_suspend_disable(struct net_device *dev, int val);
+extern int net_os_set_suspend(struct net_device *dev, int val);
+extern int net_os_set_dtim_skip(struct net_device *dev, int val);
+extern int net_os_set_packet_filter(struct net_device *dev, int val);
+
+#define IWE_STREAM_ADD_EVENT(info, stream, ends, iwe, extra) \
+ iwe_stream_add_event(info, stream, ends, iwe, extra)
+#define IWE_STREAM_ADD_VALUE(info, event, value, ends, iwe, event_len) \
+ iwe_stream_add_value(info, event, value, ends, iwe, event_len)
+#define IWE_STREAM_ADD_POINT(info, stream, ends, iwe, extra) \
+ iwe_stream_add_point(info, stream, ends, iwe, extra)
+
+extern int dhd_pno_enable(dhd_pub_t *dhd, int pfn_enabled);
+extern int dhd_pno_clean(dhd_pub_t *dhd);
+extern int dhd_pno_set(dhd_pub_t *dhd, wlc_ssid_t *ssids_local, int nssid,
+ unsigned char scan_fr);
+extern int dhd_pno_get_status(dhd_pub_t *dhd);
+extern int dhd_dev_pno_reset(struct net_device *dev);
+extern int dhd_dev_pno_set(struct net_device *dev, wlc_ssid_t *ssids_local,
+ int nssid, unsigned char scan_fr);
+extern int dhd_dev_pno_enable(struct net_device *dev, int pfn_enabled);
+extern int dhd_dev_get_pno_status(struct net_device *dev);
+
+#define PNO_TLV_PREFIX 'S'
+#define PNO_TLV_VERSION 1
+#define PNO_TLV_SUBVERSION 0
+#define PNO_TLV_RESERVED 0
+#define PNO_TLV_TYPE_SSID_IE 'S'
+#define PNO_TLV_TYPE_TIME 'T'
+#define PNO_EVENT_UP "PNO_EVENT"
+
+typedef struct cmd_tlv {
+ char prefix;
+ char version;
+ char subver;
+ char reserved;
+} cmd_tlv_t;
+#endif /* _wl_iw_h_ */