From cf2b448852abd47cee21007b8313fbf962bf3c9a Mon Sep 17 00:00:00 2001 From: Henry Ptasinski Date: Mon, 20 Sep 2010 22:33:12 -0700 Subject: [PATCH] staging: brcm80211: add fullmac driver This patch to the existing bcm80211 directory in the staging tree adds fullmac driver support for the BCM4329 SDIO chip from Broadcom. Configuration of the mac80211 driver or the fullmac driver can be done through menuconfig. Signed-off-by: Henry Ptasinski Signed-off-by: Nohee Ko Signed-off-by: Greg Kroah-Hartman --- drivers/staging/brcm80211/Kconfig | 1 + drivers/staging/brcm80211/Makefile | 1 + drivers/staging/brcm80211/TODO | 2 + drivers/staging/brcm80211/brcmfmac/Kconfig | 15 + drivers/staging/brcm80211/brcmfmac/Makefile | 38 + drivers/staging/brcm80211/brcmfmac/README | 36 + drivers/staging/brcm80211/brcmfmac/bcmsdh.c | 628 ++ .../staging/brcm80211/brcmfmac/bcmsdh_linux.c | 657 ++ .../staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c | 1255 ++++ .../brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c | 236 + drivers/staging/brcm80211/brcmfmac/bcmutils.c | 1784 +++++ drivers/staging/brcm80211/brcmfmac/bcmwifi.c | 170 + drivers/staging/brcm80211/brcmfmac/dhd.h | 479 ++ drivers/staging/brcm80211/brcmfmac/dhd_bus.h | 82 + drivers/staging/brcm80211/brcmfmac/dhd_cdc.c | 495 ++ .../staging/brcm80211/brcmfmac/dhd_common.c | 1919 ++++++ .../brcm80211/brcmfmac/dhd_custom_gpio.c | 161 + drivers/staging/brcm80211/brcmfmac/dhd_dbg.h | 103 + .../staging/brcm80211/brcmfmac/dhd_linux.c | 2963 ++++++++ .../brcm80211/brcmfmac/dhd_linux_sched.c | 26 + .../staging/brcm80211/brcmfmac/dhd_proto.h | 92 + drivers/staging/brcm80211/brcmfmac/dhd_sdio.c | 6119 +++++++++++++++++ .../staging/brcm80211/brcmfmac/dngl_stats.h | 32 + .../staging/brcm80211/brcmfmac/linux_osl.c | 588 ++ .../staging/brcm80211/brcmfmac/wl_cfg80211.c | 4020 +++++++++++ .../staging/brcm80211/brcmfmac/wl_cfg80211.h | 383 ++ drivers/staging/brcm80211/brcmfmac/wl_iw.c | 3728 ++++++++++ drivers/staging/brcm80211/brcmfmac/wl_iw.h | 154 + drivers/staging/brcm80211/include/bcmcdc.h | 98 + drivers/staging/brcm80211/include/bcmdefs.h | 7 + drivers/staging/brcm80211/include/bcmdevs.h | 5 + .../staging/brcm80211/include/bcmsdh_sdmmc.h | 2 + drivers/staging/brcm80211/include/bcmutils.h | 57 +- drivers/staging/brcm80211/include/dhdioctl.h | 109 + .../brcm80211/include/hndrte_armtrap.h | 77 + .../staging/brcm80211/include/hndrte_cons.h | 59 + drivers/staging/brcm80211/include/linux_osl.h | 88 +- drivers/staging/brcm80211/include/msgtrace.h | 71 + .../staging/brcm80211/include/proto/802.11.h | 2 +- .../staging/brcm80211/include/proto/bcmeth.h | 52 + .../brcm80211/include/proto/bcmevent.h | 7 + .../staging/brcm80211/include/proto/bcmip.h | 136 + .../staging/brcm80211/include/proto/vlan.h | 49 + drivers/staging/brcm80211/include/wlioctl.h | 40 +- drivers/staging/brcm80211/util/hndpmu.c | 91 + drivers/staging/brcm80211/util/sbutils.c | 584 ++ drivers/staging/brcm80211/util/siutils.c | 84 +- 47 files changed, 27755 insertions(+), 30 deletions(-) create mode 100644 drivers/staging/brcm80211/brcmfmac/Kconfig create mode 100644 drivers/staging/brcm80211/brcmfmac/Makefile create mode 100644 drivers/staging/brcm80211/brcmfmac/README create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmsdh.c create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmutils.c create mode 100644 drivers/staging/brcm80211/brcmfmac/bcmwifi.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd.h create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_bus.h create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_cdc.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_common.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_dbg.h create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_linux.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_linux_sched.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_proto.h create mode 100644 drivers/staging/brcm80211/brcmfmac/dhd_sdio.c create mode 100644 drivers/staging/brcm80211/brcmfmac/dngl_stats.h create mode 100644 drivers/staging/brcm80211/brcmfmac/linux_osl.c create mode 100644 drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c create mode 100644 drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h create mode 100644 drivers/staging/brcm80211/brcmfmac/wl_iw.c create mode 100644 drivers/staging/brcm80211/brcmfmac/wl_iw.h create mode 100644 drivers/staging/brcm80211/include/bcmcdc.h create mode 100644 drivers/staging/brcm80211/include/dhdioctl.h create mode 100644 drivers/staging/brcm80211/include/hndrte_armtrap.h create mode 100644 drivers/staging/brcm80211/include/hndrte_cons.h create mode 100644 drivers/staging/brcm80211/include/msgtrace.h create mode 100644 drivers/staging/brcm80211/include/proto/bcmeth.h create mode 100644 drivers/staging/brcm80211/include/proto/bcmip.h create mode 100644 drivers/staging/brcm80211/include/proto/vlan.h create mode 100644 drivers/staging/brcm80211/util/sbutils.c diff --git a/drivers/staging/brcm80211/Kconfig b/drivers/staging/brcm80211/Kconfig index 6b74685a331f..ee321bd4b439 100644 --- a/drivers/staging/brcm80211/Kconfig +++ b/drivers/staging/brcm80211/Kconfig @@ -12,3 +12,4 @@ config BRCM80211_PCI depends on BRCM80211 default y +source "drivers/staging/brcm80211/brcmfmac/Kconfig" diff --git a/drivers/staging/brcm80211/Makefile b/drivers/staging/brcm80211/Makefile index 05a4103fe0a5..907e08879d1e 100644 --- a/drivers/staging/brcm80211/Makefile +++ b/drivers/staging/brcm80211/Makefile @@ -63,3 +63,4 @@ EXTRA_CFLAGS += $(PCI_CFLAGS) $(MODULEPFX)-objs = $(BRCM80211_OFILES) $(PCIFILES) endif +obj-$(CONFIG_BRCMFMAC) += brcmfmac/ diff --git a/drivers/staging/brcm80211/TODO b/drivers/staging/brcm80211/TODO index f4bb28d237ea..8803d300b531 100644 --- a/drivers/staging/brcm80211/TODO +++ b/drivers/staging/brcm80211/TODO @@ -32,6 +32,8 @@ Other ===== - wlc_mac80211.[ch], wl_mac80211.[ch] and linux_osl.c all need to be refactored and combined. +- Merge files that are partially duplicated between the softmac and fullmac + drivers - Replace driver's proprietary ssb interface with generic kernel ssb module (only used when compiling for SDIO). - PCI and SDIO support are currently #ifdef'ed exclusive of each other, which 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..b1af6477dfa4 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/Makefile @@ -0,0 +1,38 @@ +# +# 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. + +DHDCFLAGS = -DLINUX -DBCMDRIVER -DBCMDONGLEHOST -DDHDTHREAD -DBCMWPA2 \ + -DUNRELEASEDCHIP -Dlinux -DDHD_SDALIGN=64 -DMAX_HDR_READ=64 \ + -DDHD_FIRSTREAD=64 -DDHD_GPL -DDHD_SCHED -DBDC -DTOE -DDHD_BCMEVENTS \ + -DSHOW_EVENTS -DBCMSDIO -DDHD_GPL -DBCMLXSDMMC -DBCMPLATFORM_BUS \ + -Wall -Wstrict-prototypes -Werror -DOEM_CHROMIUMOS -DEMBEDDED_PLATFORM \ + -DARP_OFFLOAD_SUPPORT -DPKT_FILTER_SUPPORT -DBRCM_FULLMAC \ + -DCONFIG_CFG80211 -DMMC_SDIO_ABORT -DDHD_DEBUG_TRAP -DBCMDBG -DDHD_DEBUG \ + -Idrivers/staging/brcm80211/brcmfmac \ + -Idrivers/staging/brcm80211/brcmfmac/include \ + -Idrivers/staging/brcm80211/include \ + -Idrivers/staging/brcm80211/util + +DHDOFILES = dhd_linux.o linux_osl.o 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 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-$(CONFIG_BRCMFMAC) += brcmfmac.o +brcmfmac-objs += $(DHDOFILES) +EXTRA_CFLAGS = $(DHDCFLAGS) +EXTRA_LDFLAGS += --strip-debug 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..d5ec20ef463a --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh.c @@ -0,0 +1,628 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include /* BRCM API for SDIO + clients (such as wl, dhd) */ +#include /* common SDIO/controller interface */ +#include /* BRCM sdio device core */ + +#include /* 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 */ + uint32 vendevid; /* Target Vendor and Device ID on SD bus */ + osl_t *osh; + bool regfail; /* Save status of last + reg_read/reg_write call */ + uint32 sbwad; /* Save backplane window address */ +}; +/* local copy of bcm sd handler */ +bcmsdh_info_t *l_bcmsdh = NULL; + +#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; + + if ((bcmsdh = + (bcmsdh_info_t *) MALLOC(osh, sizeof(bcmsdh_info_t))) == NULL) { + BCMSDH_ERROR(("bcmsdh_attach: out of memory, " + "malloced %d bytes\n", MALLOCED(osh))); + return NULL; + } + bzero((char *)bcmsdh, sizeof(bcmsdh_info_t)); + + /* save the handler locally */ + l_bcmsdh = bcmsdh; + + if (!(bcmsdh->sdioh = sdioh_attach(osh, cfghdl, irq))) { + bcmsdh_detach(osh, bcmsdh); + return NULL; + } + + bcmsdh->osh = osh; + bcmsdh->init_success = TRUE; + + *regsva = (uint32 *) 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; + } + MFREE(osh, bcmsdh, sizeof(bcmsdh_info_t)); + } + + 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; +} + +uint8 bcmsdh_cfg_read(void *sdh, uint fnc_num, uint32 addr, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh; + SDIOH_API_RC status; +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + int32 retry = 0; +#endif + uint8 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 */ + OSL_DELAY(1000); +#endif + status = + sdioh_cfg_read(bcmsdh->sdioh, fnc_num, addr, + (uint8 *) &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, uint8data = 0x%x\n", + __func__, fnc_num, addr, data)); + + return data; +} + +void +bcmsdh_cfg_write(void *sdh, uint fnc_num, uint32 addr, uint8 data, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh; + SDIOH_API_RC status; +#ifdef SDIOH_API_ACCESS_RETRY_LIMIT + int32 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 */ + OSL_DELAY(1000); +#endif + status = + sdioh_cfg_write(bcmsdh->sdioh, fnc_num, addr, + (uint8 *) &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, uint8data = 0x%x\n", + __func__, fnc_num, addr, data)); +} + +uint32 bcmsdh_cfg_read_word(void *sdh, uint fnc_num, uint32 addr, int *err) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh; + SDIOH_API_RC status; + uint32 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, uint32data = 0x%x\n", + __func__, fnc_num, addr, data)); + + return data; +} + +void +bcmsdh_cfg_write_word(void *sdh, uint fnc_num, uint32 addr, uint32 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, uint32data = 0x%x\n", + __func__, fnc_num, addr, data)); +} + +int bcmsdh_cis_read(void *sdh, uint func, uint8 * cis, uint length) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh; + SDIOH_API_RC status; + + uint8 *tmp_buf, *tmp_ptr; + uint8 *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. */ + if ((tmp_buf = (uint8 *) MALLOC(bcmsdh->osh, length)) == 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"); + } + MFREE(bcmsdh->osh, tmp_buf, length); + } + + return SDIOH_API_SUCCESS(status) ? 0 : BCME_ERROR; +} + +static int bcmsdhsdio_set_sbaddr_window(void *sdh, uint32 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; +} + +uint32 bcmsdh_reg_read(void *sdh, uint32 addr, uint size) +{ + bcmsdh_info_t *bcmsdh = (bcmsdh_info_t *) sdh; + SDIOH_API_RC status; + uint32 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(("uint32data = 0x%x\n", word)); + + /* if ok, return appropriately masked word */ + if (SDIOH_API_SUCCESS(status)) { + switch (size) { + case sizeof(uint8): + return word & 0xff; + case sizeof(uint16): + return word & 0xffff; + case sizeof(uint32): + 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; +} + +uint32 bcmsdh_reg_write(void *sdh, uint32 addr, uint size, uint32 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) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + 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, uint32 addr, uint fn, uint flags, + uint8 *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) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + 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, uint32 addr, uint fn, uint flags, + uint8 *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) { + if ((err = bcmsdhsdio_set_sbaddr_window(bcmsdh, bar0))) + 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, uint32 addr, uint8 *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. */ +uint32 bcmsdh_get_dstatus(void *sdh) +{ + return 0; +} + +uint32 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, uint32 chip, uint32 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..ac804e93c832 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_linux.c @@ -0,0 +1,657 @@ +/* + * 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 +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(OOB_INTR_ONLY) +#include +extern void dhdsdio_isr(void *args); +#include +#include +#include +#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 +#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 = NULL; + +/* 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(uint16 vendor, uint16 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; + ulong 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; + uint32 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 */ + if (!(osh = osl_attach(dev, PCI_BUS, FALSE))) { + SDLX_MSG(("%s: osl_attach failed\n", __func__)); + goto err; + } + if (!(sdhc = MALLOC(osh, sizeof(bcmsdh_hc_t)))) { + SDLX_MSG(("%s: out of memory, allocated %d bytes\n", + __func__, MALLOCED(osh))); + goto err; + } + bzero(sdhc, sizeof(bcmsdh_hc_t)); + sdhc->osh = osh; + + sdhc->dev = (void *)dev; + +#ifdef BCMLXSDMMC + if (!(sdh = bcmsdh_attach(osh, (void *)0, + (void **)®s, irq))) { + SDLX_MSG(("%s: bcmsdh_attach failed\n", __func__)); + goto err; + } +#else + if (!(sdh = bcmsdh_attach(osh, (void *)r->start, + (void **)®s, irq))) { + 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 */ + if (!(sdhc->ch = drvinfo.attach((vendevid >> 16), + (vendevid & 0xFFFF), 0, 0, 0, 0, + (void *)regs, NULL, sdh))) { + 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); + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + } + 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; + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + 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; + ulong 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))) { + uint32 config_reg; + + SDLX_MSG(("%s: Disabling TI FlashMedia Controller.\n", + __func__)); + if (!(osh = osl_attach(pdev, PCI_BUS, FALSE))) { + 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 */ + if (!(osh = osl_attach(pdev, PCI_BUS, FALSE))) { + SDLX_MSG(("%s: osl_attach failed\n", __func__)); + goto err; + } + if (!(sdhc = MALLOC(osh, sizeof(bcmsdh_hc_t)))) { + SDLX_MSG(("%s: out of memory, allocated %d bytes\n", + __func__, MALLOCED(osh))); + goto err; + } + bzero(sdhc, sizeof(bcmsdh_hc_t)); + 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; + } + if (! + (sdh = + bcmsdh_attach(osh, (void *)(uintptr) pci_resource_start(pdev, 0), + (void **)®s, pdev->irq))) { + SDLX_MSG(("%s: bcmsdh_attach failed\n", __func__)); + goto err; + } + + sdhc->sdh = sdh; + + /* try to attach to the target device */ + if (!(sdhc->ch = drvinfo.attach(VENDOR_BROADCOM, /* pdev->vendor, */ + bcmsdh_query_device(sdh) & 0xFFFF, 0, 0, + 0, 0, (void *)regs, NULL, sdh))) { + 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) + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + 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; + MFREE(osh, sdhc, sizeof(bcmsdh_hc_t)); + 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) + if (!(error = pci_register_driver(&bcmsdh_pci_driver))) + return 0; + + SDLX_MSG(("%s: pci_module_init 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..a6616010d617 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc.c @@ -0,0 +1,1255 @@ +/* + * 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 + +#include +#include +#include +#include +#include /* SDIO Device and Protocol Specs */ +#include /* SDIO Host Controller Specification */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* ioctl/iovars */ + +#include +#include +#include + +#include +#include + +#if defined(CONFIG_PM_SLEEP) +#include +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, uint32 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, uint32 regaddr, + int regsize, uint32 *data); + +static int sdioh_sdmmc_card_enablefuncs(sdioh_info_t *sd) +{ + int err_ret; + uint32 fbraddr; + uint8 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; + } + + if ((sd = (sdioh_info_t *) MALLOC(osh, sizeof(sdioh_info_t))) == NULL) { + sd_err(("sdioh_attach: out of memory, malloced %d bytes\n", + MALLOCED(osh))); + return NULL; + } + bzero((char *)sd, sizeof(sdioh_info_t)); + sd->osh = osh; + if (sdioh_sdmmc_osinit(sd) != 0) { + sd_err(("%s:sdioh_sdmmc_osinit() failed\n", __func__)); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + 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); + + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + } + return SDIOH_API_RC_SUCCESS; +} + +#if defined(OOB_INTR_ONLY) && defined(HW_OOB) + +extern SDIOH_API_RC sdioh_enable_func_intr(void) +{ + uint8 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) +{ + uint8 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; + int32 int_val = 0; + bool bool_val; + uint32 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)); + + if ((vi = bcm_iovar_lookup(sdioh_iovars, name)) == NULL) { + bcmerror = BCME_UNSUPPORTED; + goto exit; + } + + if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, set)) != 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 = (int32) 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 = (int32) 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 ((uint32) int_val > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + int_val = (int32) si->client_block_size[int_val]; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_BLOCKSIZE): + { + uint func = ((uint32) int_val >> 16); + uint blksize = (uint16) 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 = (int32) 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 = (int32) 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 = (uint32) 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 = (uint32) 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 = (uint32) 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 = (uint32) 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 = (uint32) 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 = (int32) si->intrcount; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_NUMLOCALINTS): + int_val = (int32) 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; + uint8 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; + uint8 data = (uint8) 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; + uint8 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, uint32 addr, uint8 *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, uint32 addr, uint8 *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, uint32 regaddr) +{ + /* read 24 bits and return valid 17 bit addr */ + int i; + uint32 scratch, regdata; + uint8 *ptr = (uint8 *)&scratch; + for (i = 0; i < 3; i++) { + if ((sdioh_sdmmc_card_regread(sd, 0, regaddr, 1, ®data)) != + SUCCESS) + sd_err(("%s: Can't read!\n", __func__)); + + *ptr++ = (uint8) 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, uint8 *cisd, uint32 length) +{ + uint32 count; + int offset; + uint32 foo; + uint8 *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 = (uint8) (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, + uint8 *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, uint32 *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); + uint32 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(((uint32) (PKTDATA(pkt)) & DMA_ALIGN_MASK) == 0); + + if ((write) && (!fifo)) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((uint8 *) PKTDATA(pnext)), + pkt_len); + } else if (write) { + err_ret = sdio_memcpy_toio(gInstance->func[func], addr, + ((uint8 *) PKTDATA(pnext)), + pkt_len); + } else if (fifo) { + err_ret = sdio_readsb(gInstance->func[func], + ((uint8 *) PKTDATA(pnext)), + addr, pkt_len); + } else { + err_ret = sdio_memcpy_fromio(gInstance->func[func], + ((uint8 *) 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, + uint8 *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)); +#ifdef DHD_USE_STATIC_BUF + if (!(mypkt = + PKTGET_STATIC(sd->osh, buflen_u, write ? TRUE : FALSE))) { +#else + if (!(mypkt = PKTGET(sd->osh, buflen_u, + write ? TRUE : FALSE))) { +#endif /* DHD_USE_STATIC_BUF */ + 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); + +#ifdef DHD_USE_STATIC_BUF + PKTFREE_STATIC(sd->osh, mypkt, write ? TRUE : FALSE); +#else + PKTFREE(sd->osh, mypkt, write ? TRUE : FALSE); +#endif /* DHD_USE_STATIC_BUF */ + } else if (((uint32) (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))); +#ifdef DHD_USE_STATIC_BUF + if (!(mypkt = + PKTGET_STATIC(sd->osh, PKTLEN(pkt), + write ? TRUE : FALSE))) { +#else + if (!(mypkt = + PKTGET(sd->osh, PKTLEN(pkt), write ? TRUE : FALSE))) { +#endif /* DHD_USE_STATIC_BUF */ + 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)); + +#ifdef DHD_USE_STATIC_BUF + PKTFREE_STATIC(sd->osh, mypkt, write ? TRUE : FALSE); +#else + PKTFREE(sd->osh, mypkt, write ? TRUE : FALSE); +#endif /* DHD_USE_STATIC_BUF */ + } 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, uint32 regaddr, + int regsize, uint32 *data) +{ + + if ((func == 0) || (regsize == 1)) { + uint8 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, uint32 regaddr, + int regsize, uint32 data) +{ + + if ((func == 0) || (regsize == 1)) { + uint8 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..7d95c3a58fe4 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmsdh_sdmmc_linux.c @@ -0,0 +1,236 @@ +/* + * 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 +#include +#include /* SDIO Specs */ +#include /* bcmsdh to/from specific controller APIs */ +#include /* to get msglevel bit values */ + +#include /* request_irq() */ + +#include +#include +#include +#include + +#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 + +#include +#ifdef CONFIG_CFG80211 +#include +#endif + +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 = 0; + +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) { +#ifdef CONFIG_CFG80211 + wl_cfg80211_sdio_func(func); +#endif + 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 = (struct sdos_info *)MALLOC(sd->osh, sizeof(struct sdos_info)); + 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; + MFREE(sd->osh, sdos, sizeof(struct sdos_info)); +} + +/* Interrupt enable/disable */ +SDIOH_API_RC sdioh_interrupt_set(sdioh_info_t *sd, bool enable) +{ + ulong 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/bcmutils.c b/drivers/staging/brcm80211/brcmfmac/bcmutils.c new file mode 100644 index 000000000000..768f4d676310 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmutils.c @@ -0,0 +1,1784 @@ +/* + * 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 +#include +#include +#include +#ifdef BCMDRIVER +#include +#include +#else +#include +#include +/* This case for external supplicant use */ +#if defined(BCMEXTSUP) +#include +#endif + +#endif /* BCMDRIVER */ +#include +#include +#include +#include +#include +#include +#include + +#ifdef BCMDRIVER + +/* copy a pkt buffer chain into a buffer */ +uint pktcopy(osl_t *osh, void *p, uint offset, int len, uchar * buf) +{ + uint n, ret = 0; + + if (len < 0) + len = 4096; /* "infinite" */ + + /* skip 'offset' bytes */ + for (; p && offset; p = PKTNEXT(p)) { + if (offset < (uint) PKTLEN(p)) + break; + offset -= PKTLEN(p); + } + + if (!p) + return 0; + + /* copy the data */ + for (; p && len; p = PKTNEXT(p)) { + n = MIN((uint) PKTLEN(p) - offset, (uint) len); + bcopy(PKTDATA(p) + offset, buf, n); + buf += n; + len -= n; + ret += n; + offset = 0; + } + + return ret; +} + +/* copy a buffer into a pkt buffer chain */ +uint pktfrombuf(osl_t *osh, void *p, uint offset, int len, uchar *buf) +{ + uint n, ret = 0; + + /* skip 'offset' bytes */ + for (; p && offset; p = PKTNEXT(p)) { + if (offset < (uint) PKTLEN(p)) + break; + offset -= PKTLEN(p); + } + + if (!p) + return 0; + + /* copy the data */ + for (; p && len; p = PKTNEXT(p)) { + n = MIN((uint) PKTLEN(p) - offset, (uint) len); + bcopy(buf, PKTDATA(p) + offset, n); + buf += n; + len -= n; + ret += n; + offset = 0; + } + + return ret; +} + +/* return total length of buffer chain */ +uint pkttotlen(osl_t *osh, void *p) +{ + uint total; + + total = 0; + for (; p; p = PKTNEXT(p)) + total += PKTLEN(p); + return total; +} + +/* return the last buffer of chained pkt */ +void *pktlast(osl_t *osh, void *p) +{ + for (; PKTNEXT(p); p = PKTNEXT(p)) + ; + + return p; +} + +/* count segments of a chained packet */ +uint pktsegcnt(osl_t *osh, void *p) +{ + uint cnt; + + for (cnt = 0; p; p = PKTNEXT(p)) + cnt++; + + return cnt; +} + +/* + * osl multiple-precedence packet queue + * hi_prec is always >= the number of the highest non-empty precedence + */ +void *pktq_penq(struct pktq *pq, int prec, void *p) +{ + struct pktq_prec *q; + + ASSERT(prec >= 0 && prec < pq->num_prec); + ASSERT(PKTLINK(p) == NULL); /* queueing chains not allowed */ + + ASSERT(!pktq_full(pq)); + ASSERT(!pktq_pfull(pq, prec)); + + q = &pq->q[prec]; + + if (q->head) + PKTSETLINK(q->tail, p); + else + q->head = p; + + q->tail = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (uint8) prec; + + return p; +} + +void *pktq_penq_head(struct pktq *pq, int prec, void *p) +{ + struct pktq_prec *q; + + ASSERT(prec >= 0 && prec < pq->num_prec); + ASSERT(PKTLINK(p) == NULL); /* queueing chains not allowed */ + + ASSERT(!pktq_full(pq)); + ASSERT(!pktq_pfull(pq, prec)); + + q = &pq->q[prec]; + + if (q->head == NULL) + q->tail = p; + + PKTSETLINK(p, q->head); + q->head = p; + q->len++; + + pq->len++; + + if (pq->hi_prec < prec) + pq->hi_prec = (uint8) prec; + + return p; +} + +void *pktq_pdeq(struct pktq *pq, int prec) +{ + struct pktq_prec *q; + void *p; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + pq->len--; + + PKTSETLINK(p, NULL); + + return p; +} + +void *pktq_pdeq_tail(struct pktq *pq, int prec) +{ + struct pktq_prec *q; + void *p, *prev; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + for (prev = NULL; p != q->tail; p = PKTLINK(p)) + prev = p; + + if (prev) + PKTSETLINK(prev, NULL); + else + q->head = NULL; + + q->tail = prev; + q->len--; + + pq->len--; + + return p; +} + +void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, bool dir) +{ + struct pktq_prec *q; + void *p; + + q = &pq->q[prec]; + p = q->head; + while (p) { + q->head = PKTLINK(p); + PKTSETLINK(p, NULL); + PKTFREE(osh, p, dir); + q->len--; + pq->len--; + p = q->head; + } + ASSERT(q->len == 0); + q->tail = NULL; +} + +bool pktq_pdel(struct pktq *pq, void *pktbuf, int prec) +{ + struct pktq_prec *q; + void *p; + + ASSERT(prec >= 0 && prec < pq->num_prec); + + if (!pktbuf) + return FALSE; + + q = &pq->q[prec]; + + if (q->head == pktbuf) { + if ((q->head = PKTLINK(pktbuf)) == NULL) + q->tail = NULL; + } else { + for (p = q->head; p && PKTLINK(p) != pktbuf; p = PKTLINK(p)) + ; + if (p == NULL) + return FALSE; + + PKTSETLINK(p, PKTLINK(pktbuf)); + if (q->tail == pktbuf) + q->tail = p; + } + + q->len--; + pq->len--; + PKTSETLINK(pktbuf, NULL); + return TRUE; +} + +void pktq_init(struct pktq *pq, int num_prec, int max_len) +{ + int prec; + + ASSERT(num_prec > 0 && num_prec <= PKTQ_MAX_PREC); + + /* pq is variable size; only zero out what's requested */ + bzero(pq, + OFFSETOF(struct pktq, q) + (sizeof(struct pktq_prec) * num_prec)); + + pq->num_prec = (uint16) num_prec; + + pq->max = (uint16) max_len; + + for (prec = 0; prec < num_prec; prec++) + pq->q[prec].max = pq->max; +} + +void *pktq_deq(struct pktq *pq, int *prec_out) +{ + struct pktq_prec *q; + void *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + pq->len--; + + if (prec_out) + *prec_out = prec; + + PKTSETLINK(p, NULL); + + return p; +} + +void *pktq_deq_tail(struct pktq *pq, int *prec_out) +{ + struct pktq_prec *q; + void *p, *prev; + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (pq->q[prec].head) + break; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + for (prev = NULL; p != q->tail; p = PKTLINK(p)) + prev = p; + + if (prev) + PKTSETLINK(prev, NULL); + else + q->head = NULL; + + q->tail = prev; + q->len--; + + pq->len--; + + if (prec_out) + *prec_out = prec; + + PKTSETLINK(p, NULL); + + return p; +} + +void *pktq_peek(struct pktq *pq, int *prec_out) +{ + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + if (prec_out) + *prec_out = prec; + + return pq->q[prec].head; +} + +void *pktq_peek_tail(struct pktq *pq, int *prec_out) +{ + int prec; + + if (pq->len == 0) + return NULL; + + for (prec = 0; prec < pq->hi_prec; prec++) + if (pq->q[prec].head) + break; + + if (prec_out) + *prec_out = prec; + + return pq->q[prec].tail; +} + +void pktq_flush(osl_t *osh, struct pktq *pq, bool dir) +{ + int prec; + for (prec = 0; prec < pq->num_prec; prec++) + pktq_pflush(osh, pq, prec, dir); + ASSERT(pq->len == 0); +} + +/* Return sum of lengths of a specific set of precedences */ +int pktq_mlen(struct pktq *pq, uint prec_bmp) +{ + int prec, len; + + len = 0; + + for (prec = 0; prec <= pq->hi_prec; prec++) + if (prec_bmp & (1 << prec)) + len += pq->q[prec].len; + + return len; +} + +/* Priority dequeue from a specific set of precedences */ +void *pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out) +{ + struct pktq_prec *q; + void *p; + int prec; + + if (pq->len == 0) + return NULL; + + while ((prec = pq->hi_prec) > 0 && pq->q[prec].head == NULL) + pq->hi_prec--; + + while ((prec_bmp & (1 << prec)) == 0 || pq->q[prec].head == NULL) + if (prec-- == 0) + return NULL; + + q = &pq->q[prec]; + + if ((p = q->head) == NULL) + return NULL; + + if ((q->head = PKTLINK(p)) == NULL) + q->tail = NULL; + + q->len--; + + if (prec_out) + *prec_out = prec; + + pq->len--; + + PKTSETLINK(p, NULL); + + return p; +} +#endif /* BCMDRIVER */ + +const unsigned char bcm_ctype[] = { + _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, + _BCM_C, _BCM_C | _BCM_S, _BCM_C | _BCM_S, _BCM_C | _BCM_S, + _BCM_C | _BCM_S, _BCM_C | _BCM_S, _BCM_C, + _BCM_C, /* 8-15 */ + _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, + _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, _BCM_C, + _BCM_S | _BCM_SP, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_D, _BCM_D, _BCM_D, _BCM_D, _BCM_D, _BCM_D, _BCM_D, _BCM_D, + _BCM_D, _BCM_D, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_U | _BCM_X, _BCM_U | _BCM_X, _BCM_U | _BCM_X, + _BCM_U | _BCM_X, _BCM_U | _BCM_X, + _BCM_U | _BCM_X, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_L | _BCM_X, _BCM_L | _BCM_X, _BCM_L | _BCM_X, + _BCM_L | _BCM_X, _BCM_L | _BCM_X, + _BCM_L | _BCM_X, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_C, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128-143 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 144-159 */ + _BCM_S | _BCM_SP, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, /* 160-175 */ + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, + _BCM_P, _BCM_P, + _BCM_P, _BCM_P, _BCM_P, _BCM_P, _BCM_P, /* 176-191 */ + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, + _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, /* 192-207 */ + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_P, _BCM_U, + _BCM_U, _BCM_U, + _BCM_U, _BCM_U, _BCM_U, _BCM_U, _BCM_L, /* 208-223 */ + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, + _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, /* 224-239 */ + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_P, _BCM_L, + _BCM_L, _BCM_L, + _BCM_L, _BCM_L, _BCM_L, _BCM_L, _BCM_L /* 240-255 */ +}; + +ulong bcm_strtoul(char *cp, char **endp, uint base) +{ + ulong result, last_result = 0, value; + bool minus; + + minus = FALSE; + + while (bcm_isspace(*cp)) + cp++; + + if (cp[0] == '+') + cp++; + else if (cp[0] == '-') { + minus = TRUE; + cp++; + } + + if (base == 0) { + if (cp[0] == '0') { + if ((cp[1] == 'x') || (cp[1] == 'X')) { + base = 16; + cp = &cp[2]; + } else { + base = 8; + cp = &cp[1]; + } + } else + base = 10; + } else if (base == 16 && (cp[0] == '0') + && ((cp[1] == 'x') || (cp[1] == 'X'))) { + cp = &cp[2]; + } + + result = 0; + + while (bcm_isxdigit(*cp) && + (value = + bcm_isdigit(*cp) ? *cp - '0' : bcm_toupper(*cp) - 'A' + 10) < + base) { + result = result * base + value; + /* Detected overflow */ + if (result < last_result && !minus) + return (ulong)-1; + last_result = result; + cp++; + } + + if (minus) + result = (ulong) (-(long)result); + + if (endp) + *endp = (char *)cp; + + return result; +} + +int bcm_atoi(char *s) +{ + return (int)bcm_strtoul(s, NULL, 10); +} + +/* return pointer to location of substring 'needle' in 'haystack' */ +char *bcmstrstr(char *haystack, char *needle) +{ + int len, nlen; + int i; + + if ((haystack == NULL) || (needle == NULL)) + return haystack; + + nlen = strlen(needle); + len = strlen(haystack) - nlen + 1; + + for (i = 0; i < len; i++) + if (memcmp(needle, &haystack[i], nlen) == 0) + return &haystack[i]; + return NULL; +} + +char *bcmstrcat(char *dest, const char *src) +{ + char *p; + + p = dest + strlen(dest); + + while ((*p++ = *src++) != '\0') + ; + + return dest; +} + +char *bcmstrncat(char *dest, const char *src, uint size) +{ + char *endp; + char *p; + + p = dest + strlen(dest); + endp = p + size; + + while (p != endp && (*p++ = *src++) != '\0') + ; + + return dest; +} + +/**************************************************************************** +* Function: bcmstrtok +* +* Purpose: +* Tokenizes a string. This function is conceptually similiar +* to ANSI C strtok(), +* but allows strToken() to be used by different strings or callers at the same +* time. Each call modifies '*string' by substituting a NULL character for the +* first delimiter that is encountered, and updates 'string' to point to +* the char +* after the delimiter. Leading delimiters are skipped. +* +* Parameters: +* string (mod) Ptr to string ptr, updated by token. +* delimiters (in) Set of delimiter characters. +* tokdelim (out) Character that delimits the returned token. (May +* be set to NULL if token delimiter is not required). +* +* Returns: Pointer to the next token found. NULL when no more tokens are found. +***************************************************************************** +*/ +char *bcmstrtok(char **string, const char *delimiters, char *tokdelim) +{ + unsigned char *str; + unsigned long map[8]; + int count; + char *nextoken; + + if (tokdelim != NULL) { + /* Prime the token delimiter */ + *tokdelim = '\0'; + } + + /* Clear control map */ + for (count = 0; count < 8; count++) + map[count] = 0; + + /* Set bits in delimiter table */ + do { + map[*delimiters >> 5] |= (1 << (*delimiters & 31)); + } + while (*delimiters++) + ; + + str = (unsigned char *)*string; + + /* Find beginning of token (skip over leading delimiters). Note that + * there is no token iff this loop sets str to point to the terminal + * null (*str == '\0') + */ + while (((map[*str >> 5] & (1 << (*str & 31))) && *str) || (*str == ' ')) + str++; + + nextoken = (char *)str; + + /* Find the end of the token. If it is not the end of the string, + * put a null there. + */ + for (; *str; str++) { + if (map[*str >> 5] & (1 << (*str & 31))) { + if (tokdelim != NULL) + *tokdelim = *str; + + *str++ = '\0'; + break; + } + } + + *string = (char *)str; + + /* Determine if a token has been found. */ + if (nextoken == (char *)str) + return NULL; + else + return nextoken; +} + +#define xToLower(C) \ + ((C >= 'A' && C <= 'Z') ? (char)((int)C - (int)'A' + (int)'a') : C) + +/**************************************************************************** +* Function: bcmstricmp +* +* Purpose: Compare to strings case insensitively. +* +* Parameters: s1 (in) First string to compare. +* s2 (in) Second string to compare. +* +* Returns: Return 0 if the two strings are equal, -1 if t1 < t2 and 1 if +* t1 > t2, when ignoring case sensitivity. +***************************************************************************** +*/ +int bcmstricmp(const char *s1, const char *s2) +{ + char dc, sc; + + while (*s2 && *s1) { + dc = xToLower(*s1); + sc = xToLower(*s2); + if (dc < sc) + return -1; + if (dc > sc) + return 1; + s1++; + s2++; + } + + if (*s1 && !*s2) + return 1; + if (!*s1 && *s2) + return -1; + return 0; +} + +/**************************************************************************** +* Function: bcmstrnicmp +* +* Purpose: Compare to strings case insensitively, upto a max of 'cnt' +* characters. +* +* Parameters: s1 (in) First string to compare. +* s2 (in) Second string to compare. +* cnt (in) Max characters to compare. +* +* Returns: Return 0 if the two strings are equal, -1 if t1 < t2 and 1 if +* t1 > t2, when ignoring case sensitivity. +***************************************************************************** +*/ +int bcmstrnicmp(const char *s1, const char *s2, int cnt) +{ + char dc, sc; + + while (*s2 && *s1 && cnt) { + dc = xToLower(*s1); + sc = xToLower(*s2); + if (dc < sc) + return -1; + if (dc > sc) + return 1; + s1++; + s2++; + cnt--; + } + + if (!cnt) + return 0; + if (*s1 && !*s2) + return 1; + if (!*s1 && *s2) + return -1; + return 0; +} + +/* parse a xx:xx:xx:xx:xx:xx format ethernet address */ +int bcm_ether_atoe(char *p, struct ether_addr *ea) +{ + int i = 0; + + for (;;) { + ea->octet[i++] = (char)bcm_strtoul(p, &p, 16); + if (!*p++ || i == 6) + break; + } + + return i == 6; +} + +#if defined(CONFIG_USBRNDIS_RETAIL) || defined(NDIS_MINIPORT_DRIVER) +/* registry routine buffer preparation utility functions: + * parameter order is like strncpy, but returns count + * of bytes copied. Minimum bytes copied is null char(1)/wchar(2) + */ +ulong wchar2ascii(char *abuf, ushort * wbuf, ushort wbuflen, ulong abuflen) +{ + ulong copyct = 1; + ushort i; + + if (abuflen == 0) + return 0; + + /* wbuflen is in bytes */ + wbuflen /= sizeof(ushort); + + for (i = 0; i < wbuflen; ++i) { + if (--abuflen == 0) + break; + *abuf++ = (char)*wbuf++; + ++copyct; + } + *abuf = '\0'; + + return copyct; +} +#endif /* CONFIG_USBRNDIS_RETAIL || NDIS_MINIPORT_DRIVER */ + +char *bcm_ether_ntoa(const struct ether_addr *ea, char *buf) +{ + static const char template[] = "%02x:%02x:%02x:%02x:%02x:%02x"; + snprintf(buf, 18, template, + ea->octet[0] & 0xff, ea->octet[1] & 0xff, ea->octet[2] & 0xff, + ea->octet[3] & 0xff, ea->octet[4] & 0xff, ea->octet[5] & 0xff); + return buf; +} + +char *bcm_ip_ntoa(struct ipv4_addr *ia, char *buf) +{ + snprintf(buf, 16, "%d.%d.%d.%d", + ia->addr[0], ia->addr[1], ia->addr[2], ia->addr[3]); + return buf; +} + +#ifdef BCMDRIVER + +void bcm_mdelay(uint ms) +{ + uint i; + + for (i = 0; i < ms; i++) + OSL_DELAY(1000); +} + +#if defined(DHD_DEBUG) +/* pretty hex print a pkt buffer chain */ +void prpkt(const char *msg, osl_t *osh, void *p0) +{ + void *p; + + if (msg && (msg[0] != '\0')) + printf("%s:\n", msg); + + for (p = p0; p; p = PKTNEXT(p)) + prhex(NULL, PKTDATA(p), PKTLEN(p)); +} +#endif + +/* Takes an Ethernet frame and sets out-of-bound PKTPRIO. + * Also updates the inplace vlan tag if requested. + * For debugging, it returns an indication of what it did. + */ +uint pktsetprio(void *pkt, bool update_vtag) +{ + struct ether_header *eh; + struct ethervlan_header *evh; + uint8 *pktdata; + int priority = 0; + int rc = 0; + + pktdata = (uint8 *) PKTDATA(pkt); + ASSERT(ISALIGNED((uintptr) pktdata, sizeof(uint16))); + + eh = (struct ether_header *)pktdata; + + if (ntoh16(eh->ether_type) == ETHER_TYPE_8021Q) { + uint16 vlan_tag; + int vlan_prio, dscp_prio = 0; + + evh = (struct ethervlan_header *)eh; + + vlan_tag = ntoh16(evh->vlan_tag); + vlan_prio = (int)(vlan_tag >> VLAN_PRI_SHIFT) & VLAN_PRI_MASK; + + if (ntoh16(evh->ether_type) == ETHER_TYPE_IP) { + uint8 *ip_body = + pktdata + sizeof(struct ethervlan_header); + uint8 tos_tc = IP_TOS(ip_body); + dscp_prio = (int)(tos_tc >> IPV4_TOS_PREC_SHIFT); + } + + /* DSCP priority gets precedence over 802.1P (vlan tag) */ + if (dscp_prio != 0) { + priority = dscp_prio; + rc |= PKTPRIO_VDSCP; + } else { + priority = vlan_prio; + rc |= PKTPRIO_VLAN; + } + /* + * If the DSCP priority is not the same as the VLAN priority, + * then overwrite the priority field in the vlan tag, with the + * DSCP priority value. This is required for Linux APs because + * the VLAN driver on Linux, overwrites the skb->priority field + * with the priority value in the vlan tag + */ + if (update_vtag && (priority != vlan_prio)) { + vlan_tag &= ~(VLAN_PRI_MASK << VLAN_PRI_SHIFT); + vlan_tag |= (uint16) priority << VLAN_PRI_SHIFT; + evh->vlan_tag = hton16(vlan_tag); + rc |= PKTPRIO_UPD; + } + } else if (ntoh16(eh->ether_type) == ETHER_TYPE_IP) { + uint8 *ip_body = pktdata + sizeof(struct ether_header); + uint8 tos_tc = IP_TOS(ip_body); + priority = (int)(tos_tc >> IPV4_TOS_PREC_SHIFT); + rc |= PKTPRIO_DSCP; + } + + ASSERT(priority >= 0 && priority <= MAXPRIO); + PKTSETPRIO(pkt, priority); + return rc | priority; +} + +static char bcm_undeferrstr[BCME_STRLEN]; + +static const char *bcmerrorstrtable[] = BCMERRSTRINGTABLE; + +/* Convert the error codes into related error strings */ +const char *bcmerrorstr(int bcmerror) +{ + /* check if someone added a bcmerror code but + forgot to add errorstring */ + ASSERT(ABS(BCME_LAST) == (ARRAYSIZE(bcmerrorstrtable) - 1)); + + if (bcmerror > 0 || bcmerror < BCME_LAST) { + snprintf(bcm_undeferrstr, BCME_STRLEN, "Undefined error %d", + bcmerror); + return bcm_undeferrstr; + } + + ASSERT(strlen(bcmerrorstrtable[-bcmerror]) < BCME_STRLEN); + + return bcmerrorstrtable[-bcmerror]; +} + +/* iovar table lookup */ +const bcm_iovar_t *bcm_iovar_lookup(const bcm_iovar_t *table, const char *name) +{ + const bcm_iovar_t *vi; + const char *lookup_name; + + /* skip any ':' delimited option prefixes */ + lookup_name = strrchr(name, ':'); + if (lookup_name != NULL) + lookup_name++; + else + lookup_name = name; + + ASSERT(table != NULL); + + for (vi = table; vi->name; vi++) { + if (!strcmp(vi->name, lookup_name)) + return vi; + } + /* ran to end of table */ + + return NULL; /* var name not found */ +} + +int bcm_iovar_lencheck(const bcm_iovar_t *vi, void *arg, int len, bool set) +{ + int bcmerror = 0; + + /* length check on io buf */ + switch (vi->type) { + case IOVT_BOOL: + case IOVT_INT8: + case IOVT_INT16: + case IOVT_INT32: + case IOVT_UINT8: + case IOVT_UINT16: + case IOVT_UINT32: + /* all integers are int32 sized args at the ioctl interface */ + if (len < (int)sizeof(int)) + bcmerror = BCME_BUFTOOSHORT; + break; + + case IOVT_BUFFER: + /* buffer must meet minimum length requirement */ + if (len < vi->minlen) + bcmerror = BCME_BUFTOOSHORT; + break; + + case IOVT_VOID: + if (!set) { + /* Cannot return nil... */ + bcmerror = BCME_UNSUPPORTED; + } else if (len) { + /* Set is an action w/o parameters */ + bcmerror = BCME_BUFTOOLONG; + } + break; + + default: + /* unknown type for length check in iovar info */ + ASSERT(0); + bcmerror = BCME_UNSUPPORTED; + } + + return bcmerror; +} + +#endif /* BCMDRIVER */ + +/**************************************************************************** + * crc8 + * + * Computes a crc8 over the input data using the polynomial: + * + * x^8 + x^7 +x^6 + x^4 + x^2 + 1 + * + * The caller provides the initial value (either CRC8_INIT_VALUE + * or the previous returned value) to allow for processing of + * discontiguous blocks of data. When generating the CRC the + * caller is responsible for complementing the final return value + * and inserting it into the byte stream. When checking, a final + * return value of CRC8_GOOD_VALUE indicates a valid CRC. + * + * Reference: Dallas Semiconductor Application Note 27 + * Williams, Ross N., "A Painless Guide to CRC Error Detection Algorithms", + * ver 3, Aug 1993, ross@guest.adelaide.edu.au, Rocksoft Pty Ltd., + * ftp://ftp.rocksoft.com/clients/rocksoft/papers/crc_v3.txt + * + * **************************************************************************** + */ + +STATIC const uint8 crc8_table[256] = { + 0x00, 0xF7, 0xB9, 0x4E, 0x25, 0xD2, 0x9C, 0x6B, + 0x4A, 0xBD, 0xF3, 0x04, 0x6F, 0x98, 0xD6, 0x21, + 0x94, 0x63, 0x2D, 0xDA, 0xB1, 0x46, 0x08, 0xFF, + 0xDE, 0x29, 0x67, 0x90, 0xFB, 0x0C, 0x42, 0xB5, + 0x7F, 0x88, 0xC6, 0x31, 0x5A, 0xAD, 0xE3, 0x14, + 0x35, 0xC2, 0x8C, 0x7B, 0x10, 0xE7, 0xA9, 0x5E, + 0xEB, 0x1C, 0x52, 0xA5, 0xCE, 0x39, 0x77, 0x80, + 0xA1, 0x56, 0x18, 0xEF, 0x84, 0x73, 0x3D, 0xCA, + 0xFE, 0x09, 0x47, 0xB0, 0xDB, 0x2C, 0x62, 0x95, + 0xB4, 0x43, 0x0D, 0xFA, 0x91, 0x66, 0x28, 0xDF, + 0x6A, 0x9D, 0xD3, 0x24, 0x4F, 0xB8, 0xF6, 0x01, + 0x20, 0xD7, 0x99, 0x6E, 0x05, 0xF2, 0xBC, 0x4B, + 0x81, 0x76, 0x38, 0xCF, 0xA4, 0x53, 0x1D, 0xEA, + 0xCB, 0x3C, 0x72, 0x85, 0xEE, 0x19, 0x57, 0xA0, + 0x15, 0xE2, 0xAC, 0x5B, 0x30, 0xC7, 0x89, 0x7E, + 0x5F, 0xA8, 0xE6, 0x11, 0x7A, 0x8D, 0xC3, 0x34, + 0xAB, 0x5C, 0x12, 0xE5, 0x8E, 0x79, 0x37, 0xC0, + 0xE1, 0x16, 0x58, 0xAF, 0xC4, 0x33, 0x7D, 0x8A, + 0x3F, 0xC8, 0x86, 0x71, 0x1A, 0xED, 0xA3, 0x54, + 0x75, 0x82, 0xCC, 0x3B, 0x50, 0xA7, 0xE9, 0x1E, + 0xD4, 0x23, 0x6D, 0x9A, 0xF1, 0x06, 0x48, 0xBF, + 0x9E, 0x69, 0x27, 0xD0, 0xBB, 0x4C, 0x02, 0xF5, + 0x40, 0xB7, 0xF9, 0x0E, 0x65, 0x92, 0xDC, 0x2B, + 0x0A, 0xFD, 0xB3, 0x44, 0x2F, 0xD8, 0x96, 0x61, + 0x55, 0xA2, 0xEC, 0x1B, 0x70, 0x87, 0xC9, 0x3E, + 0x1F, 0xE8, 0xA6, 0x51, 0x3A, 0xCD, 0x83, 0x74, + 0xC1, 0x36, 0x78, 0x8F, 0xE4, 0x13, 0x5D, 0xAA, + 0x8B, 0x7C, 0x32, 0xC5, 0xAE, 0x59, 0x17, 0xE0, + 0x2A, 0xDD, 0x93, 0x64, 0x0F, 0xF8, 0xB6, 0x41, + 0x60, 0x97, 0xD9, 0x2E, 0x45, 0xB2, 0xFC, 0x0B, + 0xBE, 0x49, 0x07, 0xF0, 0x9B, 0x6C, 0x22, 0xD5, + 0xF4, 0x03, 0x4D, 0xBA, 0xD1, 0x26, 0x68, 0x9F +}; + +#define CRC_INNER_LOOP(n, c, x) \ + (c) = ((c) >> 8) ^ crc##n##_table[((c) ^ (x)) & 0xff] + +uint8 hndcrc8(uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint8 crc /* either CRC8_INIT_VALUE or previous + return value */ + ) +{ + /* hard code the crc loop instead of using CRC_INNER_LOOP macro + * to avoid the undefined and unnecessary (uint8 >> 8) operation. + */ + while (nbytes-- > 0) + crc = crc8_table[(crc ^ *pdata++) & 0xff]; + + return crc; +} + +/***************************************************************************** + * crc16 + * + * Computes a crc16 over the input data using the polynomial: + * + * x^16 + x^12 +x^5 + 1 + * + * The caller provides the initial value (either CRC16_INIT_VALUE + * or the previous returned value) to allow for processing of + * discontiguous blocks of data. When generating the CRC the + * caller is responsible for complementing the final return value + * and inserting it into the byte stream. When checking, a final + * return value of CRC16_GOOD_VALUE indicates a valid CRC. + * + * Reference: Dallas Semiconductor Application Note 27 + * Williams, Ross N., "A Painless Guide to CRC Error Detection Algorithms", + * ver 3, Aug 1993, ross@guest.adelaide.edu.au, Rocksoft Pty Ltd., + * ftp://ftp.rocksoft.com/clients/rocksoft/papers/crc_v3.txt + * + * **************************************************************************** + */ + +static const uint16 crc16_table[256] = { + 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, + 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, + 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, + 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, + 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD, + 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5, + 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C, + 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974, + 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB, + 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, + 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, + 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, + 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, + 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1, + 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738, + 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70, + 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7, + 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF, + 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, + 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, + 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, + 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, + 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134, + 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C, + 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3, + 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB, + 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232, + 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, + 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, + 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, + 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, + 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78 +}; + +uint16 hndcrc16(uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint16 crc /* either CRC16_INIT_VALUE or previous + return value */ +) +{ + while (nbytes-- > 0) + CRC_INNER_LOOP(16, crc, *pdata++); + return crc; +} + +STATIC const uint32 crc32_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D +}; + +uint32 hndcrc32(uint8 *pdata, /* pointer to array of data to process */ + uint nbytes, /* number of input data bytes to process */ + uint32 crc /* either CRC32_INIT_VALUE or previous + return value */ +) +{ + uint8 *pend; +#ifdef __mips__ + uint8 tmp[4]; + ulong *tptr = (ulong *) tmp; + + /* in case the beginning of the buffer isn't aligned */ + pend = (uint8 *) ((uint) (pdata + 3) & 0xfffffffc); + nbytes -= (pend - pdata); + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); + + /* handle bulk of data as 32-bit words */ + pend = pdata + (nbytes & 0xfffffffc); + while (pdata < pend) { + *tptr = *(ulong *) pdata; + pdata += sizeof(ulong *); + CRC_INNER_LOOP(32, crc, tmp[0]); + CRC_INNER_LOOP(32, crc, tmp[1]); + CRC_INNER_LOOP(32, crc, tmp[2]); + CRC_INNER_LOOP(32, crc, tmp[3]); + } + + /* 1-3 bytes at end of buffer */ + pend = pdata + (nbytes & 0x03); + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); +#else + pend = pdata + nbytes; + while (pdata < pend) + CRC_INNER_LOOP(32, crc, *pdata++); +#endif /* __mips__ */ + + return crc; +} + +#ifdef notdef +#define CLEN 1499 /* CRC Length */ +#define CBUFSIZ (CLEN+4) +#define CNBUFS 5 /* # of bufs */ + +void testcrc32(void) +{ + uint j, k, l; + uint8 *buf; + uint len[CNBUFS]; + uint32 crcr; + uint32 crc32tv[CNBUFS] = { + 0xd2cb1faa, 0xd385c8fa, 0xf5b4f3f3, 0x55789e20, 0x00343110}; + + ASSERT((buf = MALLOC(CBUFSIZ * CNBUFS)) != NULL); + + /* step through all possible alignments */ + for (l = 0; l <= 4; l++) { + for (j = 0; j < CNBUFS; j++) { + len[j] = CLEN; + for (k = 0; k < len[j]; k++) + *(buf + j * CBUFSIZ + (k + l)) = (j + k) & 0xff; + } + + for (j = 0; j < CNBUFS; j++) { + crcr = + crc32(buf + j * CBUFSIZ + l, len[j], + CRC32_INIT_VALUE); + ASSERT(crcr == crc32tv[j]); + } + } + + MFREE(buf, CBUFSIZ * CNBUFS); + return; +} +#endif /* notdef */ + +/* + * Advance from the current 1-byte tag/1-byte length/variable-length value + * triple, to the next, returning a pointer to the next. + * If the current or next TLV is invalid (does not fit in given buffer length), + * NULL is returned. + * *buflen is not modified if the TLV elt parameter is invalid, + * or is decremented + * by the TLV parameter's length if it is valid. + */ +bcm_tlv_t *bcm_next_tlv(bcm_tlv_t *elt, int *buflen) +{ + int len; + + /* validate current elt */ + if (!bcm_valid_tlv(elt, *buflen)) + return NULL; + + /* advance to next elt */ + len = elt->len; + elt = (bcm_tlv_t *) (elt->data + len); + *buflen -= (2 + len); + + /* validate next elt */ + if (!bcm_valid_tlv(elt, *buflen)) + return NULL; + + return elt; +} + +/* + * Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag + */ +bcm_tlv_t *bcm_parse_tlvs(void *buf, int buflen, uint key) +{ + bcm_tlv_t *elt; + int totlen; + + elt = (bcm_tlv_t *) buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= 2) { + int len = elt->len; + + /* validate remaining totlen */ + if ((elt->id == key) && (totlen >= (len + 2))) + return elt; + + elt = (bcm_tlv_t *) ((uint8 *) elt + (len + 2)); + totlen -= (len + 2); + } + + return NULL; +} + +/* + * Traverse a string of 1-byte tag/1-byte length/variable-length value + * triples, returning a pointer to the substring whose first element + * matches tag. Stop parsing when we see an element whose ID is greater + * than the target key. + */ +bcm_tlv_t *bcm_parse_ordered_tlvs(void *buf, int buflen, uint key) +{ + bcm_tlv_t *elt; + int totlen; + + elt = (bcm_tlv_t *) buf; + totlen = buflen; + + /* find tagged parameter */ + while (totlen >= 2) { + uint id = elt->id; + int len = elt->len; + + /* Punt if we start seeing IDs > than target key */ + if (id > key) + return NULL; + + /* validate remaining totlen */ + if ((id == key) && (totlen >= (len + 2))) + return elt; + + elt = (bcm_tlv_t *) ((uint8 *) elt + (len + 2)); + totlen -= (len + 2); + } + return NULL; +} + +#if defined(WLMSG_PRHDRS) || defined(WLMSG_PRPKT) || defined(WLMSG_ASSOC) || \ + defined(DHD_DEBUG) +int +bcm_format_flags(const bcm_bit_desc_t *bd, uint32 flags, char *buf, int len) +{ + int i; + char *p = buf; + char hexstr[16]; + int slen = 0; + uint32 bit; + const char *name; + + if (len < 2 || !buf) + return 0; + + buf[0] = '\0'; + len -= 1; + + for (i = 0; flags != 0; i++) { + bit = bd[i].bit; + name = bd[i].name; + if (bit == 0 && flags) { + /* print any unnamed bits */ + sprintf(hexstr, "0x%X", flags); + name = hexstr; + flags = 0; /* exit loop */ + } else if ((flags & bit) == 0) + continue; + slen += strlen(name); + if (len < slen) + break; + if (p != buf) + p += sprintf(p, " "); /* btwn flag space */ + strcat(p, name); + p += strlen(name); + flags &= ~bit; + len -= slen; + slen = 1; /* account for btwn flag space */ + } + + /* indicate the str was too short */ + if (flags != 0) { + if (len == 0) + p--; /* overwrite last char */ + p += sprintf(p, ">"); + } + + return (int)(p - buf); +} + +/* +* print bytes formatted as hex to a string. return the resulting +* string length +*/ +int bcm_format_hex(char *str, const void *bytes, int len) +{ + int i; + char *p = str; + const uint8 *src = (const uint8 *)bytes; + + for (i = 0; i < len; i++) { + p += sprintf(p, "%02X", *src); + src++; + } + return (int)(p - str); +} + +/* pretty hex print a contiguous buffer */ +void prhex(const char *msg, uchar *buf, uint nbytes) +{ + char line[128], *p; + uint i; + + if (msg && (msg[0] != '\0')) + printf("%s:\n", msg); + + p = line; + for (i = 0; i < nbytes; i++) { + if (i % 16 == 0) + p += sprintf(p, " %04d: ", i); /* line prefix */ + + p += sprintf(p, "%02x ", buf[i]); + if (i % 16 == 15) { + printf("%s\n", line); /* flush line */ + p = line; + } + } + + /* flush last partial line */ + if (p != line) + printf("%s\n", line); +} +#endif /* defined(WLMSG_PRHDRS) || defined(WLMSG_PRPKT) */ + +/* Produce a human-readable string for boardrev */ +char *bcm_brev_str(uint32 brev, char *buf) +{ + if (brev < 0x100) + snprintf(buf, 8, "%d.%d", (brev & 0xf0) >> 4, brev & 0xf); + else + snprintf(buf, 8, "%c%03x", + ((brev & 0xf000) == 0x1000) ? 'P' : 'A', brev & 0xfff); + + return buf; +} + +#define BUFSIZE_TODUMP_ATONCE 512 /* Buffer size */ + +/* dump large strings to console */ +void printbig(char *buf) +{ + uint len, max_len; + char c; + + len = strlen(buf); + + max_len = BUFSIZE_TODUMP_ATONCE; + + while (len > max_len) { + c = buf[max_len]; + buf[max_len] = '\0'; + printf("%s", buf); + buf[max_len] = c; + + buf += max_len; + len -= max_len; + } + /* print the remaining string */ + printf("%s\n", buf); + return; +} + +/* routine to dump fields in a fileddesc structure */ +uint +bcmdumpfields(bcmutl_rdreg_rtn read_rtn, void *arg0, uint arg1, + struct fielddesc *fielddesc_array, char *buf, uint32 bufsize) +{ + uint filled_len; + int len; + struct fielddesc *cur_ptr; + + filled_len = 0; + cur_ptr = fielddesc_array; + + while (bufsize > 1) { + if (cur_ptr->nameandfmt == NULL) + break; + len = snprintf(buf, bufsize, cur_ptr->nameandfmt, + read_rtn(arg0, arg1, cur_ptr->offset)); + /* check for snprintf overflow or error */ + if (len < 0 || (uint32) len >= bufsize) + len = bufsize - 1; + buf += len; + bufsize -= len; + filled_len += len; + cur_ptr++; + } + return filled_len; +} + +uint bcm_mkiovar(char *name, char *data, uint datalen, char *buf, uint buflen) +{ + uint len; + + len = strlen(name) + 1; + + if ((len + datalen) > buflen) + return 0; + + strncpy(buf, name, buflen); + + /* append data onto the end of the name string */ + memcpy(&buf[len], data, datalen); + len += datalen; + + return len; +} + +/* Quarter dBm units to mW + * Table starts at QDBM_OFFSET, so the first entry is mW for qdBm=153 + * Table is offset so the last entry is largest mW value that fits in + * a uint16. + */ + +#define QDBM_OFFSET 153 /* Offset for first entry */ +#define QDBM_TABLE_LEN 40 /* Table size */ + +/* Smallest mW value that will round up to the first table entry, QDBM_OFFSET. + * Value is ( mW(QDBM_OFFSET - 1) + mW(QDBM_OFFSET) ) / 2 + */ +#define QDBM_TABLE_LOW_BOUND 6493 /* Low bound */ + +/* Largest mW value that will round down to the last table entry, + * QDBM_OFFSET + QDBM_TABLE_LEN-1. + * Value is ( mW(QDBM_OFFSET + QDBM_TABLE_LEN - 1) + + * mW(QDBM_OFFSET + QDBM_TABLE_LEN) ) / 2. + */ +#define QDBM_TABLE_HIGH_BOUND 64938 /* High bound */ + +static const uint16 nqdBm_to_mW_map[QDBM_TABLE_LEN] = { +/* qdBm: +0 +1 +2 +3 +4 +5 +6 +7 */ +/* 153: */ 6683, 7079, 7499, 7943, 8414, 8913, 9441, 10000, +/* 161: */ 10593, 11220, 11885, 12589, 13335, 14125, 14962, 15849, +/* 169: */ 16788, 17783, 18836, 19953, 21135, 22387, 23714, 25119, +/* 177: */ 26607, 28184, 29854, 31623, 33497, 35481, 37584, 39811, +/* 185: */ 42170, 44668, 47315, 50119, 53088, 56234, 59566, 63096 +}; + +uint16 bcm_qdbm_to_mw(uint8 qdbm) +{ + uint factor = 1; + int idx = qdbm - QDBM_OFFSET; + + if (idx >= QDBM_TABLE_LEN) { + /* clamp to max uint16 mW value */ + return 0xFFFF; + } + + /* scale the qdBm index up to the range of the table 0-40 + * where an offset of 40 qdBm equals a factor of 10 mW. + */ + while (idx < 0) { + idx += 40; + factor *= 10; + } + + /* return the mW value scaled down to the correct factor of 10, + * adding in factor/2 to get proper rounding. + */ + return (nqdBm_to_mW_map[idx] + factor / 2) / factor; +} + +uint8 bcm_mw_to_qdbm(uint16 mw) +{ + uint8 qdbm; + int offset; + uint mw_uint = mw; + uint boundary; + + /* handle boundary case */ + if (mw_uint <= 1) + return 0; + + offset = QDBM_OFFSET; + + /* move mw into the range of the table */ + while (mw_uint < QDBM_TABLE_LOW_BOUND) { + mw_uint *= 10; + offset -= 40; + } + + for (qdbm = 0; qdbm < QDBM_TABLE_LEN - 1; qdbm++) { + boundary = nqdBm_to_mW_map[qdbm] + (nqdBm_to_mW_map[qdbm + 1] - + nqdBm_to_mW_map[qdbm]) / 2; + if (mw_uint < boundary) + break; + } + + qdbm += (uint8) offset; + + return qdbm; +} + +uint bcm_bitcount(uint8 *bitmap, uint length) +{ + uint bitcount = 0, i; + uint8 tmp; + for (i = 0; i < length; i++) { + tmp = bitmap[i]; + while (tmp) { + bitcount++; + tmp &= (tmp - 1); + } + } + return bitcount; +} + +#ifdef BCMDRIVER + +/* Initialization of bcmstrbuf structure */ +void bcm_binit(struct bcmstrbuf *b, char *buf, uint size) +{ + b->origsize = b->size = size; + b->origbuf = b->buf = buf; +} + +/* Buffer sprintf wrapper to guard against buffer overflow */ +int bcm_bprintf(struct bcmstrbuf *b, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = vsnprintf(b->buf, b->size, fmt, ap); + + /* Non Ansi C99 compliant returns -1, + * Ansi compliant return r >= b->size, + * bcmstdlib returns 0, handle all + */ + if ((r == -1) || (r >= (int)b->size) || (r == 0)) { + b->size = 0; + } else { + b->size -= r; + b->buf += r; + } + + va_end(ap); + + return r; +} + +void bcm_inc_bytes(uchar *num, int num_bytes, uint8 amount) +{ + int i; + + for (i = 0; i < num_bytes; i++) { + num[i] += amount; + if (num[i] >= amount) + break; + amount = 1; + } +} + +int bcm_cmp_bytes(uchar *arg1, uchar *arg2, uint8 nbytes) +{ + int i; + + for (i = nbytes - 1; i >= 0; i--) { + if (arg1[i] != arg2[i]) + return arg1[i] - arg2[i]; + } + return 0; +} + +void bcm_print_bytes(char *name, const uchar *data, int len) +{ + int i; + int per_line = 0; + + printf("%s: %d \n", name ? name : "", len); + for (i = 0; i < len; i++) { + printf("%02x ", *data++); + per_line++; + if (per_line == 16) { + per_line = 0; + printf("\n"); + } + } + printf("\n"); +} + +/* + * buffer length needed for wlc_format_ssid + * 32 SSID chars, max of 4 chars for each SSID char "\xFF", plus NULL. + */ + +#if defined(WLTINYDUMP) || defined(WLMSG_INFORM) || defined(WLMSG_ASSOC) || \ + defined(WLMSG_PRPKT) || defined(WLMSG_WSEC) +int bcm_format_ssid(char *buf, const uchar ssid[], uint ssid_len) +{ + uint i, c; + char *p = buf; + char *endp = buf + SSID_FMT_BUF_LEN; + + if (ssid_len > DOT11_MAX_SSID_LEN) + ssid_len = DOT11_MAX_SSID_LEN; + + for (i = 0; i < ssid_len; i++) { + c = (uint) ssid[i]; + if (c == '\\') { + *p++ = '\\'; + *p++ = '\\'; + } else if (bcm_isprint((uchar) c)) { + *p++ = (char)c; + } else { + p += snprintf(p, (endp - p), "\\x%02X", c); + } + } + *p = '\0'; + ASSERT(p < endp); + + return (int)(p - buf); +} +#endif /* defined(WLTINYDUMP) || + defined(WLMSG_INFORM) || defined(WLMSG_ASSOC) */ + +#endif /* BCMDRIVER */ diff --git a/drivers/staging/brcm80211/brcmfmac/bcmwifi.c b/drivers/staging/brcm80211/brcmfmac/bcmwifi.c new file mode 100644 index 000000000000..20baf447558f --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/bcmwifi.c @@ -0,0 +1,170 @@ +/* + * 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 + +#ifdef BCMDRIVER +#include +#include +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) +#define tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#else +#include +#include +#include +#endif +#include + +#if defined(WIN32) && (defined(BCMDLL) || defined(WLMDLL)) +#include +#endif + +char *wf_chspec_ntoa(chanspec_t chspec, char *buf) +{ + const char *band, *bw, *sb; + uint channel; + + band = ""; + bw = ""; + sb = ""; + channel = CHSPEC_CHANNEL(chspec); + + if ((CHSPEC_IS2G(chspec) && channel > CH_MAX_2G_CHANNEL) || + (CHSPEC_IS5G(chspec) && channel <= CH_MAX_2G_CHANNEL)) + band = (CHSPEC_IS2G(chspec)) ? "b" : "a"; + if (CHSPEC_IS40(chspec)) { + if (CHSPEC_SB_UPPER(chspec)) { + sb = "u"; + channel += CH_10MHZ_APART; + } else { + sb = "l"; + channel -= CH_10MHZ_APART; + } + } else if (CHSPEC_IS10(chspec)) { + bw = "n"; + } + + snprintf(buf, 6, "%d%s%s%s", channel, band, bw, sb); + return buf; +} + +chanspec_t wf_chspec_aton(char *a) +{ + char *endp = NULL; + uint channel, band, bw, ctl_sb; + char c; + + channel = strtoul(a, &endp, 10); + + if (endp == a) + return 0; + + if (channel > MAXCHANNEL) + return 0; + + band = + ((channel <= + CH_MAX_2G_CHANNEL) ? WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G); + bw = WL_CHANSPEC_BW_20; + ctl_sb = WL_CHANSPEC_CTL_SB_NONE; + + a = endp; + + c = tolower(a[0]); + if (c == '\0') + goto done; + + if (c == 'a' || c == 'b') { + band = (c == 'a') ? WL_CHANSPEC_BAND_5G : WL_CHANSPEC_BAND_2G; + a++; + c = tolower(a[0]); + if (c == '\0') + goto done; + } + + if (c == 'n') { + bw = WL_CHANSPEC_BW_10; + } else if (c == 'l') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_LOWER; + + if (channel <= (MAXCHANNEL - CH_20MHZ_APART)) + channel += CH_10MHZ_APART; + else + return 0; + } else if (c == 'u') { + bw = WL_CHANSPEC_BW_40; + ctl_sb = WL_CHANSPEC_CTL_SB_UPPER; + + if (channel > CH_20MHZ_APART) + channel -= CH_10MHZ_APART; + else + return 0; + } else { + return 0; + } + +done: + return channel | band | bw | ctl_sb; +} + +int wf_mhz2channel(uint freq, uint start_factor) +{ + int ch = -1; + uint base; + int offset; + + if (start_factor == 0) { + if (freq >= 2400 && freq <= 2500) + start_factor = WF_CHAN_FACTOR_2_4_G; + else if (freq >= 5000 && freq <= 6000) + start_factor = WF_CHAN_FACTOR_5_G; + } + + if (freq == 2484 && start_factor == WF_CHAN_FACTOR_2_4_G) + return 14; + + base = start_factor / 2; + + if ((freq < base) || (freq > base + 1000)) + return -1; + + offset = freq - base; + ch = offset / 5; + + if (offset != (ch * 5)) + return -1; + + if (start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 13)) + return -1; + + return ch; +} + +int wf_channel2mhz(uint ch, uint start_factor) +{ + int freq; + + if ((start_factor == WF_CHAN_FACTOR_2_4_G && (ch < 1 || ch > 14)) || + (ch <= 200)) + freq = -1; + if ((start_factor == WF_CHAN_FACTOR_2_4_G) && (ch == 14)) + freq = 2484; + else + freq = ch * 5 + start_factor / 2; + + return freq; +} diff --git a/drivers/staging/brcm80211/brcmfmac/dhd.h b/drivers/staging/brcm80211/brcmfmac/dhd.h new file mode 100644 index 000000000000..c09b4b7ceff3 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd.h @@ -0,0 +1,479 @@ +/* + * 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_ + +#if defined(LINUX) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_HAS_WAKELOCK) +#include +#endif /* defined (CONFIG_HAS_WAKELOCK) */ +/* The kernel threading is sdio-specific */ +#else /* LINUX */ +#define ENOMEM 1 +#define EFAULT 2 +#define EINVAL 3 +#define EIO 4 +#define ETIMEDOUT 5 +#define ERESTARTSYS 6 +#endif /* LINUX */ + +#include + +/* 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 +}; +#ifdef DHD_USE_STATIC_BUF +extern void *dhd_os_prealloc(int section, unsigned long size); +#endif +/* 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 */ + uint8 wme_dp; /* wme discard priority */ + + /* Dongle media info */ + bool iswl; /* Dongle-resident driver is wl */ + ulong 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 */ + ulong tx_packets; /* Data packets sent to dongle */ + ulong tx_multicast; /* Multicast data packets sent to dongle */ + ulong tx_errors; /* Errors in sending data to dongle */ + ulong tx_ctlpkts; /* Control packets sent to dongle */ + ulong tx_ctlerrs; /* Errors sending control frames to dongle */ + ulong rx_packets; /* Packets sent up the network interface */ + ulong rx_multicast; /* Multicast packets sent up the network + interface */ + ulong rx_errors; /* Errors processing rx data packets */ + ulong rx_ctlpkts; /* Control frames processed from dongle */ + ulong rx_ctlerrs; /* Errors in processing rx control frames */ + ulong rx_dropped; /* Packets dropped locally (no memory) */ + ulong rx_flushed; /* Packets flushed due to + unscheduled sendup thread */ + ulong wd_dpc_sched; /* Number of times dhd dpc scheduled by + watchdog timer */ + + ulong rx_readahead_cnt; /* Number of packets where header read-ahead + was used. */ + ulong tx_realloc; /* Number of tx packets we had to realloc for + headroom */ + ulong 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; + + uint8 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)) { \ + OSL_DELAY(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 { + uint8 ifidx; + uint8 action; + uint8 flags; + uint8 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, uint8 *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 { + uint32 limit; /* Expiration time (usec) */ + uint32 increment; /* Current expiration increment (usec) */ + uint32 elapsed; /* Current elapsed time (usec) */ + uint32 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 uint8 *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, uint8 *mac_addr, uint32 flags, uint8 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, uchar * 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, uint8 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..13252f5d063e --- /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, uchar *msg, uint msglen); +extern int dhd_bus_rxctl(struct dhd_bus *bus, uchar *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, uchar *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..d5bd0ff3451a --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd_cdc.c @@ -0,0 +1,495 @@ +/* + * 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 +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#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 { + uint16 reqid; + uint8 pending; + uint32 lastcmd; + uint8 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, (uchar *)&prot->msg, len); +} + +static int dhdcdc_cmplt(dhd_pub_t *dhd, uint32 id, uint32 len) +{ + int ret; + dhd_prot_t *prot = dhd->prot; + + DHD_TRACE(("%s: Enter\n", __func__)); + + do { + ret = + dhd_bus_rxctl(dhd->bus, (uchar *)&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; + uint32 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); + + if ((ret = dhdcdc_msg(dhd)) < 0) { + DHD_ERROR(("dhdcdc_query_ioctl: dhdcdc_msg failed w/status " + "%d\n", ret)); + goto done; + } + +retry: + /* wait for interrupt and get first fragment */ + if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 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; + uint32 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); + + if ((ret = dhdcdc_msg(dhd)) < 0) + goto done; + + if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 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 = (uint8) 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, uint8 * 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); + + if ((*ifidx = BDC_GET_IF_IDX(h)) >= 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; + +#ifndef DHD_USE_STATIC_BUF + if (!(cdc = (dhd_prot_t *) MALLOC(dhd->osh, sizeof(dhd_prot_t)))) { + DHD_ERROR(("%s: kmalloc failed\n", __func__)); + goto fail; + } +#else + if (! + (cdc = + (dhd_prot_t *) dhd_os_prealloc(DHD_PREALLOC_PROT, + sizeof(dhd_prot_t)))) { + DHD_ERROR(("%s: kmalloc failed\n", __func__)); + goto fail; + } +#endif /* DHD_USE_STATIC_BUF */ + memset(cdc, 0, sizeof(dhd_prot_t)); + + /* ensure that the msg buf directly follows the cdc msg struct */ + if ((uintptr) (&cdc->msg + 1) != (uintptr) 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: +#ifndef DHD_USE_STATIC_BUF + if (cdc != NULL) + MFREE(dhd->osh, cdc, sizeof(dhd_prot_t)); +#endif + return BCME_NOMEM; +} + +/* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */ +void dhd_prot_detach(dhd_pub_t *dhd) +{ +#ifndef DHD_USE_STATIC_BUF + MFREE(dhd->osh, dhd->prot, sizeof(dhd_prot_t)); +#endif + 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..ff6bae77c817 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd_common.c @@ -0,0 +1,1919 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int dhd_msg_level; +char fw_path[MOD_PARAM_PATHLEN]; +char nv_path[MOD_PARAM_PATHLEN]; + +/* Last connection success/failure status */ +uint32 dhd_conn_event; +uint32 dhd_conn_status; +uint32 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) +{ + char eabuf[ETHER_ADDR_STR_LEN]; + + 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 %s\n", + dhdp->iswl, dhdp->drv_version, bcm_ether_ntoa(&dhdp->mac, + eabuf)); + 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, uint32 actionid, + const char *name, void *params, int plen, void *arg, int len, + int val_size) +{ + int bcmerror = 0; + int32 int_val = 0; + + DHD_TRACE(("%s: Enter\n", __func__)); + + if ((bcmerror = + bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) != 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 = (int32) 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 = (int32) dhd_pub->bcmerror; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_WDTICK): + int_val = (int32) 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 = (int32) 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 = (int32) 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(uint32 event, uint32 status, uint32 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; + uint32 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)); + + if ((vi = bcm_iovar_lookup(dhd_iovars, name)) == 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; + uchar *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, "%02x:%02x:%02x:%02x:%02x:%02x", + (uchar) event->addr.octet[0] & 0xff, + (uchar) event->addr.octet[1] & 0xff, + (uchar) event->addr.octet[2] & 0xff, + (uchar) event->addr.octet[3] & 0xff, + (uchar) event->addr.octet[4] & 0xff, + (uchar) event->addr.octet[5] & 0xff); + + event_name = "UNKNOWN"; + for (i = 0; i < ARRAYSIZE(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 uint32 seqnum_prev = 0; + msgtrace_hdr_t hdr; + uint32 nblost; + char *s, *p; + + buf = (uchar *) 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 = (uchar *) 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; + uint32 type, status; + uint16 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) { + uint32 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"); +} + +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) + +/* 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] = (uint8) 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; + + if (!(arg_save = MALLOC(dhd->osh, strlen(arg) + 1))) { + DHD_ERROR(("%s: kmalloc failed\n", __func__)); + goto fail; + } + arg_org = arg_save; + memcpy(arg_save, arg, strlen(arg) + 1); + + argv[i] = bcmstrtok(&arg_save, " ", 0); + + 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(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) + MFREE(dhd->osh, arg_org, strlen(arg) + 1); +} + +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; + uint32 mask_size; + uint32 pattern_size; + char *argv[8], *buf = 0; + int i = 0; + char *arg_save = 0, *arg_org = 0; +#define BUF_SIZE 2048 + + if (!(arg_save = MALLOC(dhd->osh, strlen(arg) + 1))) { + DHD_ERROR(("%s: kmalloc failed\n", __func__)); + goto fail; + } + + arg_org = arg_save; + + if (!(buf = MALLOC(dhd->osh, BUF_SIZE))) { + 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] = bcmstrtok(&arg_save, " ", 0); + while (argv[i++]) + argv[i] = bcmstrtok(&arg_save, " ", 0); + + 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(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(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(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(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) + MFREE(dhd->osh, arg_org, strlen(arg) + 1); + + if (buf) + MFREE(dhd->osh, buf, BUF_SIZE); +} + +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; + uint32 dongle_align = DHD_SDALIGN; + uint32 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)); + bcmstrtok(&ptr, "\n", 0); + /* 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 = 0; + +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 = (iscan_buf_t *) MALLOC(dhd->osh, sizeof(iscan_buf_t)); + 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; + MFREE(dhd->osh, iscanbuf_free, sizeof(iscan_buf_t)); + } + 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; + MFREE(dhd->osh, iscan_delete, sizeof(iscan_buf_t)); + + 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 *) ((uintptr) 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; + + uchar *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 *) ((uintptr) 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 + *) ((uintptr) bi + + dtoh32 + (bi->length)); + bcopy(bi, bi_new, + dtoh32 + (bi->length)); + bi_new = + (wl_bss_info_t + *) ((uintptr) + 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 *) ((uintptr) 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 *) ((uintptr) 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, uint16 action) +{ + int rc; + wl_iscan_params_t params; + dhd_pub_t *dhd = dhd_bus_pub(dhdp); + char buf[WLC_IOCTL_SMLEN]; + + memset(¶ms, 0, sizeof(wl_iscan_params_t)); + memcpy(¶ms.params.bssid, ðer_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 *)¶ms, 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)); + if ((ret = + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, + sizeof(iovbuf))) >= 0) { + /* clear pfn */ + iov_len = bcm_mkiovar("pfnclear", 0, 0, iovbuf, sizeof(iovbuf)); + if (iov_len) { + if ((ret = + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, + iov_len)) < 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 */ + if ((ret = + bcm_mkiovar("pfn", (char *)&pfn_enabled, 4, iovbuf, + sizeof(iovbuf))) > 0) { + if ((ret = + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, + sizeof(iovbuf))) < 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, uchar 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 */ + if ((err = dhd_pno_clean(dhd)) < 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; + + if ((err = + bcm_mkiovar("pfn_add", (char *)&pfn_element, + sizeof(pfn_element), iovbuf, + sizeof(iovbuf))) > 0) { + if ((err = + dhdcdc_set_ioctl(dhd, 0, WLC_SET_VAR, iovbuf, + sizeof(iovbuf))) < 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..bd044c1a17c4 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd_custom_gpio.c @@ -0,0 +1,161 @@ +/* + * 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 +#include +#include +#include + +#include +#include + +#include +#include + +#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 +#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 */ + OSL_DELAY(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 + +#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..ce7ab1af0e55 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd_linux.c @@ -0,0 +1,2963 @@ +/* + * 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 +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_CFG80211 +#include +#endif + +#define EPI_VERSION_STR "4.218.248.5" + +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) +#include + +struct semaphore wifi_control_sem; + +struct dhd_bus *g_bus; + +static struct wifi_platform_data *wifi_control_data = NULL; +static struct resource *wifi_irqres = NULL; + +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 +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"); + +/* Linux wireless extension support */ +#if defined(CONFIG_WIRELESS_EXT) +#include +extern wl_iw_extra_params_t g_wl_iw_params; +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +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 */ + uint8 mac_addr[ETHER_ADDR_LEN]; /* assigned MAC address */ + bool attached; /* Delayed attachment when unset */ + bool txflowcontrol; /* Per interface flow control indicator */ + char name[IFNAMSIZ + 1]; /* 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; + long watchdog_pid; + struct semaphore watchdog_sem; + struct completion watchdog_exited; + long dpc_pid; + struct semaphore dpc_sem; + struct completion dpc_exited; + + /* Thread to issue ioctl for multicast */ + long sysioc_pid; + struct semaphore sysioc_sem; + struct completion sysioc_exited; + 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 = 0; +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 = 0; +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 = 0; +#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); + +#define DAEMONIZE(a) daemonize(a); \ + allow_signal(SIGKILL); \ + allow_signal(SIGTERM); + +#define BLOCKABLE() (!in_atomic()) + +/* 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); + +#ifdef CONFIG_CFG80211 +/* Use cfg80211 */ +uint dhd_cfg80211 = TRUE; +module_param(dhd_cfg80211, uint, 0); +#endif + +/* 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 = 0; +module_param(dhd_pktgen, uint, 0); + +/* Echo packet len (0 => sawtooth, max 2040) */ +uint dhd_pktgen_len = 0; +module_param(dhd_pktgen_len, uint, 0); +#endif + +#ifdef CONFIG_CFG80211 +#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) +#endif + +/* 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(ulong 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, uint32 *toe_ol); +static int dhd_toe_set(dhd_info_t *dhd, int idx, uint32 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) { + OSL_DELAY(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 (dhd->iflist[ifidx] == NULL) { + DHD_ERROR(("%s: null i/f %d\n", __func__, ifidx)); + return ""; + } + + if (dhd->iflist[ifidx]->net) + return dhd->iflist[ifidx]->net->name; + + return ""; +} + +static void _dhd_set_multicast_list(dhd_info_t *dhd, int ifidx) +{ + struct net_device *dev; + struct netdev_hw_addr *ha; + uint32 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); + if (!(bufp = buf = MALLOC(dhd->pub.osh, buflen))) { + 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; + } + + MFREE(dhd->pub.osh, buf, buflen); + + /* 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); + if (!(buf = MALLOC(dhd->pub.osh, buflen))) { + 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)); + MFREE(dhd->pub.osh, buf, buflen); + 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))); + } + + MFREE(dhd->pub.osh, buf, buflen); + + /* 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 */ + if (!(ifp->net = alloc_etherdev(sizeof(dhd)))) { + 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)); + if ((err = dhd_net_attach(&dhd->pub, ifp->idx)) != 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; + MFREE(dhd->pub.osh, ifp, sizeof(*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 + + DAEMONIZE("dhd_sysioc"); + + while (down_interruptible(&dhd->sysioc_sem) == 0) { + 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); + } + } + } + } + complete_and_exit(&dhd->sysioc_exited, 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_pid >= 0); + 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_pid >= 0); + 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) { + uint8 *pktdata = (uint8 *) 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); + } + + /* Look into the packet and update the packet priority */ + if ((PKTPRIO(pktbuf) == 0)) + pktsetprio(pktbuf, FALSE); + + /* 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); + if ((skb = skb2) == NULL) { + DHD_ERROR(("%s: skb_realloc_headroom failed\n", + dhd_ifname(&dhd->pub, ifidx))); + ret = -ENOMEM; + goto done; + } + } + + /* Convert to packet */ + if (!(pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb))) { + 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; + uchar *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; + uint16 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, ¶m); + } +#endif /* DHD_SCHED */ + + DAEMONIZE("dhd_watchdog"); + + /* Run until signal received */ + while (1) { + 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); + complete_and_exit(&dhd->watchdog_exited, 0); +} + +static void dhd_watchdog(ulong data) +{ + dhd_info_t *dhd = (dhd_info_t *) data; + + if (dhd->watchdog_pid >= 0) { + 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, ¶m); + } +#endif /* DHD_SCHED */ + + DAEMONIZE("dhd_dpc"); + + /* Run until signal received */ + while (1) { + 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); + + complete_and_exit(&dhd->dpc_exited, 0); +} + +static void dhd_dpc(ulong 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_pid >= 0) { + 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, uint32 *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"); + if ((ret = + dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 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(uint32)); + 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, uint32 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(uint32)); + + if ((ret = + dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 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(uint32)); + + if ((ret = + dhd_prot_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 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, "wl"); + sprintf(info->version, "%lu", dhd->pub.drv_version); +} + +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)]; + uint32 cmd; +#ifdef TOE + struct ethtool_value edata; + uint32 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(uint32))) + 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: + if ((ret = dhd_toe_get(dhd, 0, &toe_cmpnt)) < 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 */ + if ((ret = dhd_toe_get(dhd, 0, &toe_cmpnt)) < 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; + + if ((ret = dhd_toe_set(dhd, 0, toe_cmpnt)) < 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(ioc.len, DHD_IOCTL_MAXLEN); + /* optimization for direct ioctl calls from kernel */ + /* + if (segment_eq(get_fs(), KERNEL_DS)) { + buf = ioc.buf; + } else { + */ + { + if (!(buf = (char *)MALLOC(dhd->pub.osh, buflen))) { + 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) + MFREE(dhd->pub.osh, buf, buflen); + + 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__)); +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE()) { + wl_cfg80211_down(); + } +#endif + 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) */ + + OLD_MOD_DEC_USE_COUNT; + return 0; +} + +static int dhd_open(struct net_device *net) +{ + dhd_info_t *dhd = *(dhd_info_t **) netdev_priv(net); +#ifdef TOE + uint32 toe_ol; +#endif + int ifidx = dhd_net2idx(dhd, net); + int32 ret = 0; + + DHD_TRACE(("%s: ifidx %d\n", __func__, ifidx)); + + if (ifidx == 0) { /* do it only for primary eth0 */ + + /* try to bring up bus */ + if ((ret = dhd_bus_start(&dhd->pub)) != 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; +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE()) { + if (unlikely(wl_cfg80211_up())) { + DHD_ERROR(("%s: failed to bring up cfg80211\n", + __func__)); + return -1; + } + } +#endif + + OLD_MOD_INC_USE_COUNT; + return ret; +} + +osl_t *dhd_osl_attach(void *pdev, uint bustype) +{ + return osl_attach(pdev, bustype, TRUE); +} + +void dhd_osl_detach(osl_t *osh) +{ + if (MALLOCED(osh)) { + DHD_ERROR(("%s: MEMORY LEAK %d bytes\n", __func__, + MALLOCED(osh))); + } + osl_detach(osh); +} + +int +dhd_add_if(dhd_info_t *dhd, int ifidx, void *handle, char *name, + uint8 *mac_addr, uint32 flags, uint8 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 = MALLOC(dhd->pub.osh, sizeof(dhd_if_t)))) { + 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; + strncpy(ifp->name, name, IFNAMSIZ); + ifp->name[IFNAMSIZ] = '\0'; + 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_pid >= 0); + 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_pid >= 0); + 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 */ + if (!(net = alloc_etherdev(sizeof(dhd)))) { + DHD_ERROR(("%s: OOM - alloc_etherdev\n", __func__)); + goto fail; + } + + /* Allocate primary dhd_info */ + if (!(dhd = MALLOC(osh, sizeof(dhd_info_t)))) { + 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; + init_MUTEX(&dhd->proto_sem); + /* 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) */ + +#ifdef CONFIG_CFG80211 + /* 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()); + } +#endif + + /* Set up the watchdog timer */ + init_timer(&dhd->timer); + dhd->timer.data = (ulong) dhd; + dhd->timer.function = dhd_watchdog; + + /* Initialize thread based operation and lock */ + init_MUTEX(&dhd->sdsem); + 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); + init_completion(&dhd->watchdog_exited); + dhd->watchdog_pid = kernel_thread(dhd_watchdog_thread, dhd, 0); + } else { + dhd->watchdog_pid = -1; + } + + /* Set up the bottom half handler */ + if (dhd_dpc_prio >= 0) { + /* Initialize DPC thread */ + sema_init(&dhd->dpc_sem, 0); + init_completion(&dhd->dpc_exited); + dhd->dpc_pid = kernel_thread(dhd_dpc_thread, dhd, 0); + } else { + tasklet_init(&dhd->tasklet, dhd_dpc, (ulong) dhd); + dhd->dpc_pid = -1; + } + + if (dhd_sysioc) { + sema_init(&dhd->sysioc_sem, 0); + init_completion(&dhd->sysioc_exited); + dhd->sysioc_pid = kernel_thread(_dhd_sysioc_thread, dhd, 0); + } else { + dhd->sysioc_pid = -1; + } + + /* + * 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 */ + if ((ret = dhd_bus_init(&dhd->pub, TRUE)) != 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 */ + if ((ret = dhd_prot_init(&dhd->pub)) < 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; + uint8 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 defined(CONFIG_CFG80211) + if (!IS_CFG80211_FAVORITE()) { +#endif +#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 */ +#if defined(CONFIG_CFG80211) + } +#endif +#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_pid >= 0) { + KILL_PROC(dhd->watchdog_pid, SIGTERM); + wait_for_completion(&dhd->watchdog_exited); + } + + if (dhd->dpc_pid >= 0) { + KILL_PROC(dhd->dpc_pid, SIGTERM); + wait_for_completion(&dhd->dpc_exited); + } else + tasklet_kill(&dhd->tasklet); + + if (dhd->sysioc_pid >= 0) { + KILL_PROC(dhd->sysioc_pid, SIGTERM); + wait_for_completion(&dhd->sysioc_exited); + } + + dhd_bus_detach(dhdp); + + if (dhdp->prot) + dhd_prot_detach(dhdp); + +#if defined(CONFIG_WIRELESS_EXT) + wl_iw_detach(); +#endif /* (CONFIG_WIRELESS_EXT) */ + +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE()) + wl_cfg80211_detach(); +#endif /* CONFIG_CFG80211 */ + +#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); + MFREE(dhd->pub.osh, ifp, sizeof(*ifp)); + MFREE(dhd->pub.osh, dhd, sizeof(*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 = 0; + 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; + +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE() && !NO_FW_REQ()) + return wl_cfg80211_request_fw(filename); +#endif + + 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; + +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE() && !NO_FW_REQ()) + return wl_cfg80211_read_fw(buf, len); +#endif + + 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) +{ +#ifdef CONFIG_CFG80211 + if (IS_CFG80211_FAVORITE() && !NO_FW_REQ()) + return wl_cfg80211_release_fw(); +#endif + 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); +} + +#ifdef DHD_USE_STATIC_BUF +void *dhd_os_prealloc(int section, unsigned long size) +{ +#if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) + void *alloc_ptr = NULL; + if (wifi_control_data && wifi_control_data->mem_prealloc) { + alloc_ptr = wifi_control_data->mem_prealloc(section, size); + if (alloc_ptr) { + DHD_INFO(("success alloc section %d\n", section)); + bzero(alloc_ptr, size); + return alloc_ptr; + } + } + + DHD_ERROR(("can't alloc section %d\n", section)); + return 0; +#else + return MALLOC(0, size); +#endif /* #if defined(CUSTOMER_HW2) && defined(CONFIG_WIFI_CONTROL_FUNC) */ +} +#endif /* DHD_USE_STATIC_BUF */ +#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 defined(CONFIG_CFG80211) + if (!IS_CFG80211_FAVORITE()) { +#endif + 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); +#if defined(CONFIG_CFG80211) + } +#endif +#endif /* defined(CONFIG_WIRELESS_EXT) */ + +#ifdef CONFIG_CFG80211 + 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); + } +#endif + + 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, uint8 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, + uchar 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, uint8 *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 */ + MFREE(dhd->osh, buf, size); + /* 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 +#include +#include +#include + +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..f837663461c8 --- /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 +#include + +#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, uint8 *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..04d68646106d --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/dhd_sdio.c @@ -0,0 +1,6119 @@ +/* + * 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 +#include +#include + +#ifdef BCMEMBEDIMAGE +#include BCMEMBEDIMAGE +#endif /* BCMEMBEDIMAGE */ + +#include +#include +#include +#include + +#include +#include +#include +#ifdef DHD_DEBUG +#include +#include +#endif /* DHD_DEBUG */ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ + uint8 *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 */ + uint32 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 */ + uint32 ramsize; /* Size of RAM in SOCRAM (bytes) */ + uint32 orig_ramsize; /* Size of RAM in SOCRAM (bytes) */ + + uint32 bus; /* gSPI or SDIO bus */ + uint32 hostintmask; /* Copy of Host Interrupt Mask */ + uint32 intstatus; /* Intstatus bits (events) pending */ + bool dpc_sched; /* Indicates DPC schedule (intrpt rcvd) */ + bool fcstate; /* State of dongle flow-control */ + + uint16 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 */ + uint8 flowcontrol; /* per prio flow control bitmask */ + uint8 tx_seq; /* Transmit sequence number (next) */ + uint8 tx_max; /* Maximum transmit sequence allowed */ + + uint8 hdrbuf[MAX_HDR_READ + DHD_SDALIGN]; + uint8 *rxhdr; /* Header of current rx frame (in hdrbuf) */ + uint16 nextlen; /* Next Read Len from last header */ + uint8 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 */ + + uint8 *rxbuf; /* Buffer for receiving control packets */ + uint rxblen; /* Allocated length of rxbuf */ + uint8 *rxctl; /* Aligned pointer into rxbuf */ + uint8 *databuf; /* Buffer for receiving big glom packet */ + uint8 *dataptr; /* Aligned pointer into databuf */ + uint rxlen; /* Length of valid data in buffer */ + + uint8 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 */ + int32 idletime; /* Control for activity timeout */ + int32 idlecount; /* Activity timeout counter */ + int32 idleclock; /* How to set bus driver when idle */ + int32 sd_divisor; /* Speed control to bus driver */ + int32 sd_mode; /* Mode control to bus driver */ + int32 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; + uint8 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 */ + uint16 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 */ + + uint8 *ctrl_frame_buf; + uint32 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 = (uintptr)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) \ + (((uint8)(bus->tx_max - bus->tx_seq) != 0) && \ + (((uint8)(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, uint8 *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(uint16 chipid); +static bool dhdsdio_probe_attach(dhd_bus_t *bus, osl_t *osh, void *sdh, + void *regsva, uint16 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, uint32 addr, uint fn, + uint flags, uint8 *buf, uint nbytes, void *pkt, + bcmsdh_cmplt_fn_t complete, void *handle); +static int dhd_bcmsdh_send_buf(dhd_bus_t *bus, uint32 addr, uint fn, + uint flags, uint8 *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) +{ + int32 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 < (int32) bus->orig_ramsize)) + bus->ramsize = dhd_dongle_memsize; +} + +static int dhdsdio_set_siaddr_window(dhd_bus_t *bus, uint32 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; + uint8 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))) { + uint32 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; + int32 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, ®s->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, ®s->tosbmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_DEV_INT, ®s->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, ®s->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, ®s->tosbmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_DEV_INT, ®s->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; + uint8 *frame; + uint16 len, pad = 0; + uint32 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 = (uint8 *) PKTDATA(pkt); + + /* Add alignment padding, allocate new packet if needed */ + pad = ((uintptr) 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 = (uint8 *) PKTDATA(pkt); + ASSERT(((uintptr) frame % DHD_SDALIGN) == 0); + pad = 0; + } else { + PKTPUSH(pkt, pad); + frame = (uint8 *) 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 = (uint16) PKTLEN(pkt); + *(uint16 *) frame = htol16(len); + *(((uint16 *) 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(len, 16)); + } +#endif + + /* Raise len to next SDIO block to eliminate tail command */ + if (bus->roundup && bus->blocksize && (len > bus->blocksize)) { + uint16 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++) { + uint8 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) { + uint8 *data; + PKTPUSH(pkt, SDPCM_TEST_HDRLEN); + data = PKTDATA(pkt); + *data++ = SDPCM_TEST_ECHOREQ; + *data++ = (uint8) bus->loopid++; + *data++ = (datalen >> 0); + *data++ = (datalen >> 8); + datalen += SDPCM_TEST_HDRLEN; + } +#endif /* SDTEST */ + + /* Add space for the header */ + PKTPUSH(pkt, SDPCM_HDRLEN); + ASSERT(ISALIGNED((uintptr) 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; + uint32 intstatus = 0; + uint retries = 0; + int ret = 0, prec_out; + uint cnt = 0; + uint datalen; + uint8 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, ®s->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, uchar *msg, uint msglen) +{ + uint8 *frame; + uint16 len; + uint32 swheader; + uint retries = 0; + bcmsdh_info_t *sdh = bus->sdh; + uint8 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) { + if ((doff = ((uintptr) frame % DHD_SDALIGN))) { + 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)) { + uint16 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(ISALIGNED((uintptr) 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) */ + *(uint16 *) frame = htol16((uint16) msglen); + *(((uint16 *) 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(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++) { + uint8 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, uchar *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, uint8 *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, uint8 *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, uint32 address, uint8 *data, + uint size) +{ + int bcmerror = 0; + uint32 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(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) +{ + uint32 addr; + int rv; + + /* Read last word in memory to determine address of + sdpcm_shared structure */ + if ((rv = + dhdsdio_membytes(bus, FALSE, bus->ramsize - 4, (uint8 *)&addr, + 4)) < 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, (uint8 *) 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, uint8 *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 = MALLOC(bus->dhd->osh, msize); + if (mbuffer == NULL) { + DHD_ERROR(("%s: MALLOC(%d) failed\n", __func__, + msize)); + bcmerror = BCME_NOMEM; + goto done; + } + } + + if ((str = MALLOC(bus->dhd->osh, maxstrlen)) == NULL) { + DHD_ERROR(("%s: MALLOC(%d) failed\n", __func__, maxstrlen)); + bcmerror = BCME_NOMEM; + goto done; + } + + if ((bcmerror = dhdsdio_readshared(bus, &sdpcm_shared)) < 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'; + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.assert_exp_addr, + (uint8 *) str, + maxstrlen)) < 0) + goto done; + + str[maxstrlen - 1] = '\0'; + bcm_bprintf(&strbuf, " expr \"%s\"", str); + } + + if (sdpcm_shared.assert_file_addr != 0) { + str[0] = '\0'; + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.assert_file_addr, + (uint8 *) str, + maxstrlen)) < 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) { + if ((bcmerror = dhdsdio_membytes(bus, FALSE, + sdpcm_shared.trap_addr, + (uint8 *)&tr, + sizeof(trap_t))) < 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) + MFREE(bus->dhd->osh, mbuffer, msize); + if (str) + MFREE(bus->dhd->osh, str, maxstrlen); + + 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 */ + uint8 *buf = NULL, *databuf = NULL; + + /* Get full mem size */ + size = bus->ramsize; + buf = MALLOC(bus->dhd->osh, size); + 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) + MFREE(bus->dhd->osh, buf, size); + 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; + uint8 line[CONSOLE_LINE_MAX], ch; + uint32 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); + if ((rv = + dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&c->log, + sizeof(c->log))) < 0) + return rv; + + /* Allocate console buffer (one time only) */ + if (c->buf == NULL) { + c->bufsize = ltoh32(c->log.buf_size); + if ((c->buf = MALLOC(bus->dhd->osh, c->bufsize)) == 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) + MFREE(bus->dhd->osh, bus->vars, bus->varsz); + + bus->vars = MALLOC(bus->dhd->osh, len); + 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, uint32 actionid, + const char *name, void *params, int plen, void *arg, int len, + int val_size) +{ + int bcmerror = 0; + int32 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 = (int32) 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 = (int32) 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 = (int32) 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 = (int32) 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 = (int32) 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): + { + uint32 address; + uint size, dsize; + uint8 *data; + + bool set = (actionid == IOV_SVAL(IOV_MEMBYTES)); + + ASSERT(plen >= 2 * sizeof(int)); + + address = (uint32) 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 ? (uint8 *) params + + 2 * sizeof(int) : (uint8 *) arg; + + /* Call to do the transfer */ + bcmerror = + dhdsdio_membytes(bus, set, address, data, size); + + break; + } + + case IOV_GVAL(IOV_MEMSIZE): + int_val = (int32) bus->ramsize; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_SDIOD_DRIVE): + int_val = (int32) 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 = (int32) 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 = (int32) 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 = (int32) 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; + uint32 addr, size; + + sd_ptr = (sdreg_t *) params; + + addr = (uintptr) bus->regs + sd_ptr->offset; + size = sd_ptr->func; + int_val = (int32) bcmsdh_reg_read(bus->sdh, addr, size); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + bcopy(&int_val, arg, sizeof(int32)); + break; + } + + case IOV_SVAL(IOV_SDREG): + { + sdreg_t *sd_ptr; + uint32 addr, size; + + sd_ptr = (sdreg_t *) params; + + addr = (uintptr) 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; + uint32 addr, size; + + bcopy(params, &sdreg, sizeof(sdreg)); + + addr = SI_ENUM_BASE + sdreg.offset; + size = sdreg.func; + int_val = (int32) bcmsdh_reg_read(bus->sdh, addr, size); + if (bcmsdh_regfail(bus->sdh)) + bcmerror = BCME_SDIO_ERROR; + bcopy(&int_val, arg, sizeof(int32)); + break; + } + + case IOV_SVAL(IOV_SBREG): + { + sdreg_t sdreg; + uint32 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; + + bcmstrcat(arg, "\nFunc 0\n"); + bcmsdh_cis_read(bus->sdh, 0x10, + (uint8 *) arg + strlen(arg), + SBSDIO_CIS_SIZE_LIMIT); + bcmstrcat(arg, "\nFunc 1\n"); + bcmsdh_cis_read(bus->sdh, 0x11, + (uint8 *) arg + strlen(arg), + SBSDIO_CIS_SIZE_LIMIT); + bcmstrcat(arg, "\nFunc 2\n"); + bcmsdh_cis_read(bus->sdh, 0x12, + (uint8 *) arg + strlen(arg), + SBSDIO_CIS_SIZE_LIMIT); + break; + } + + case IOV_GVAL(IOV_FORCEEVEN): + int_val = (int32) forcealign; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_FORCEEVEN): + forcealign = bool_val; + break; + + case IOV_GVAL(IOV_TXBOUND): + int_val = (int32) 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 = (int32) 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 = (int32) 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 = (int32) 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, (uint8) 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; + uint32 varsize; + uint32 varaddr; + uint8 *vbuffer; + uint32 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 = (uint8 *) MALLOC(bus->dhd->osh, varsize); + 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 = (char *)MALLOC(bus->dhd->osh, varsize); + 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__)); + + MFREE(bus->dhd->osh, nvram_ularray, varsize); +#endif /* DHD_DEBUG */ + + MFREE(bus->dhd->osh, vbuffer, varsize); + } + + /* 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), + (uint8 *)&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) { + uint32 zeros = 0; + dhdsdio_membytes(bus, TRUE, bus->ramsize - 4, + (uint8 *)&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; + uint32 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(int32), + 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(int32), + 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) { + int32 fnum = 2; + if (bcmsdh_iovar_op + (bus->sdh, "sd_blocksize", &fnum, sizeof(int32), + &bus->blocksize, sizeof(int32), + 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; + uint32 local_hostintmask; + uint8 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; + uint8 ready, enable; + int err, ret = 0; + uint8 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, + (uint8) 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; + uint16 lastrbc; + uint8 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, ®s->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, uint8 *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 = ((uintptr) 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 uint8 dhdsdio_rxglom(dhd_bus_t *bus, uint8 rxseq) +{ + uint16 dlen, totlen; + uint8 *dptr, num = 0; + + uint16 sublen, check; + void *pfirst, *plast, *pnext, *save_pfirst; + osl_t *osh = bus->dhd->osh; + + int errcode; + uint8 chan, seq, doff, sfdoff; + uint8 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 = (uint16) 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(uint16); + dptr += sizeof(uint16); + 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, (uint8 *) PKTDATA(pnext), + PKTLEN(pnext), PKTLEN(pnext))); + } + } + + pfirst = bus->glom; + dlen = (uint16) 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, + (uint8 *) 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 = + (uint16) 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(PKTLEN(pfirst), 48)); + } +#endif + + /* Validate the superframe header */ + dptr = (uint8 *) PKTDATA(pfirst); + sublen = ltoh16_ua(dptr); + check = ltoh16_ua(dptr + sizeof(uint16)); + + 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 ((uint16)~(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 ((uint8) (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 = (uint8 *) PKTDATA(pnext); + dlen = (uint16) PKTLEN(pnext); + sublen = ltoh16_ua(dptr); + check = ltoh16_ua(dptr + sizeof(uint16)); + 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 ((uint16)~(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 = (uint8 *) 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("", (uint8 *) PKTDATA(pfirst), + MIN(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; + + uint16 len, check; /* Extracted hardware header fields */ + uint8 chan, seq, doff; /* Extracted software header fields */ + uint8 fcbits; /* Extracted fcbits from software header */ + uint8 delta; + + void *pkt; /* Packet for event or data frames */ + uint16 pad; /* Number of pad bytes to read */ + uint16 rdlen; /* Total number of bytes to read */ + uint8 rxseq; /* Next sequence number to expect */ + uint rxleft = 0; /* Remaining number of frames allowed */ + int sdret; /* Return code from bcmsdh calls */ + uint8 txmax; /* Maximum tx sequence offered */ + bool len_consistent; /* Result of comparing readahead len and + len from hw-hdr */ + uint8 *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) { + uint8 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) { + uint16 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 = ((uintptr) 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 = (uint8 *) 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(uint16)); + + /* 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 ((uint16)~(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 ((uint8) (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(uint16)); + + /* All zeros means no more frames */ + if (!(len | check)) { + *finished = TRUE; + break; + } + + /* Validate check bytes */ + if ((uint16) ~ (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 ((uint8) (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, ((uint8 *) 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 uint32 dhdsdio_hostmail(dhd_bus_t *bus) +{ + sdpcmd_regs_t *regs = bus->regs; + uint32 intstatus = 0; + uint32 hmb_data; + uint8 fcbits; + uint retries = 0; + + DHD_TRACE(("%s: Enter\n", __func__)); + + /* Read mailbox data and ack that we did so */ + R_SDREG(hmb_data, ®s->tohostmailboxdata, retries); + if (retries <= retry_limit) + W_SDREG(SMB_INT_ACK, ®s->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; + uint32 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; + uint8 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, ®s->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, ®s->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, ®s->intstatus, retries); + R_SDREG(newstatus, ®s->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, (uint8 *) bus->ctrl_frame_buf, + (uint32) 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++) { + uint8 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 = (uint16) 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; + uint8 *data; + uint pktcount; + uint fillbyte; + osl_t *osh = bus->dhd->osh; + uint16 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; + if (!(pkt = PKTGET(osh, + (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + + DHD_SDALIGN), TRUE))) { + DHD_ERROR(("%s: PKTGET failed!\n", __func__)); + break; + } + PKTALIGN(osh, pkt, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN), + DHD_SDALIGN); + data = (uint8 *) 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++ = (uint8) bus->pktgen_sent; + break; + + case DHD_PKTGEN_SEND: + *data++ = SDPCM_TEST_DISCARD; + *data++ = (uint8) bus->pktgen_sent; + break; + + case DHD_PKTGEN_RXBURST: + *data++ = SDPCM_TEST_BURST; + *data++ = (uint8) 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, (uint8) bus->pktgen_sent); + +#ifdef DHD_DEBUG + if (DHD_BYTES_ON() && DHD_DATA_ON()) { + data = (uint8 *) 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 = (uint16) 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; + uint8 *data; + osl_t *osh = bus->dhd->osh; + + /* Allocate the packet */ + if (! + (pkt = + PKTGET(osh, SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN, + TRUE))) { + DHD_ERROR(("%s: PKTGET failed!\n", __func__)); + return; + } + PKTALIGN(osh, pkt, (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN), DHD_SDALIGN); + data = (uint8 *) 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; + uint8 *data; + uint pktlen; + + uint8 cmd; + uint8 extra; + uint16 len; + uint16 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) */ + *(uint8 *) (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)) { + uint32 intstatus = 0; + + /* Reset poll tick */ + bus->polltick = 0; + + /* Check device if no interrupts */ + if (!bus->intr || (bus->intrcount == bus->lastintrs)) { + + if (!bus->dpc_sched) { + uint8 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, uchar *msg, uint msglen) +{ + dhd_bus_t *bus = dhdp->bus; + uint32 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); + if ((rv = + dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, + sizeof(val))) < 0) + goto done; + + /* Write message into cbuf */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, cbuf); + if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)msg, msglen)) < 0) + goto done; + + /* Write length into vcons_in */ + addr = bus->console_addr + OFFSETOF(hndrte_cons_t, vcons_in); + val = htol32(msglen); + if ((rv = + dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, + sizeof(val))) < 0) + goto done; + + /* Bump dongle by sending an empty event pkt. + * sdpcm_sendup (RX) checks for virtual console input. + */ + if (((pkt = PKTGET(bus->dhd->osh, 4 + SDPCM_RESERVE, TRUE)) != 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, uint8 *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(uint16 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(uint16 venid, uint16 devid, uint16 bus_no, + uint16 slot, uint16 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((uintptr) 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 = MALLOC(osh, sizeof(dhd_bus_t)); + if (!bus) { + DHD_ERROR(("%s: MALLOC of dhd_bus_t failed\n", __func__)); + goto fail; + } + bzero(bus, sizeof(dhd_bus_t)); + bus->sdh = sdh; + bus->cl_devid = (uint16) 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 */ + if ((ret = dhd_bus_start(bus->dhd)) != 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, + uint16 devid) +{ + uint8 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; + uint8 *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); + OSL_DELAY(65); + + for (fn = 0; fn <= numfn; fn++) { + if (!(cis[fn] = MALLOC(osh, SBSDIO_CIS_SIZE_LIMIT))) { + DHD_INFO(("dhdsdio_probe: fn %d cis malloc " + "failed\n", fn)); + break; + } + bzero(cis[fn], SBSDIO_CIS_SIZE_LIMIT); + + if ((err = + bcmsdh_cis_read(sdh, fn, cis[fn], + SBSDIO_CIS_SIZE_LIMIT))) { + DHD_INFO(("dhdsdio_probe: fn %d cis read " + "err %d\n", fn, err)); + MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT); + break; + } + dhd_dump_cis(fn, cis[fn]); + } + + while (fn-- > 0) { + ASSERT(cis[fn]); + MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT); + } + + 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((uint16) 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 */ + if (!(bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0)) && + !(bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0))) { + 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 = (uint8 *) ROUNDUP((uintptr)&bus->hdrbuf[0], DHD_SDALIGN); + + /* Set the poll and/or interrupt flags */ + bus->intr = (bool) dhd_intr; + if ((bus->poll = (bool) dhd_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__)); + +#ifndef DHD_USE_STATIC_BUF + if (bus->dhd->maxctl) { + bus->rxblen = + ROUNDUP((bus->dhd->maxctl + SDPCM_HDRLEN), + ALIGNMENT) + DHD_SDALIGN; + bus->rxbuf = MALLOC(osh, bus->rxblen); + if (!(bus->rxbuf)) { + DHD_ERROR(("%s: MALLOC of %d-byte rxbuf failed\n", + __func__, bus->rxblen)); + goto fail; + } + } + + /* Allocate buffer to receive glomed packet */ + bus->databuf = MALLOC(osh, MAX_DATA_BUF); + if (!(bus->databuf)) { + DHD_ERROR(("%s: MALLOC of %d-byte databuf failed\n", + __func__, MAX_DATA_BUF)); + /* release rxbuf which was already located as above */ + if (!bus->rxblen) + MFREE(osh, bus->rxbuf, bus->rxblen); + goto fail; + } +#else + if (bus->dhd->maxctl) { + bus->rxblen = + ROUNDUP((bus->dhd->maxctl + SDPCM_HDRLEN), + ALIGNMENT) + DHD_SDALIGN; + bus->rxbuf = dhd_os_prealloc(DHD_PREALLOC_RXBUF, bus->rxblen); + if (!(bus->rxbuf)) { + DHD_ERROR(("%s: MALLOC of %d-byte rxbuf failed\n", + __func__, bus->rxblen)); + goto fail; + } + } + /* Allocate buffer to receive glomed packet */ + bus->databuf = dhd_os_prealloc(DHD_PREALLOC_DATABUF, MAX_DATA_BUF); + if (!(bus->databuf)) { + DHD_ERROR(("%s: MALLOC of %d-byte databuf failed\n", + __func__, MAX_DATA_BUF)); + goto fail; + } +#endif /* DHD_USE_STATIC_BUF */ + + /* Align the buffer */ + if ((uintptr) bus->databuf % DHD_SDALIGN) + bus->dataptr = + bus->databuf + (DHD_SDALIGN - + ((uintptr) 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) +{ + int32 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 = (int32) 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(int32), + 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(int32), 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(int32), + &bus->blocksize, sizeof(int32), 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(int32), + 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); + + MFREE(osh, bus, sizeof(dhd_bus_t)); + } + + 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) { +#ifndef DHD_USE_STATIC_BUF + MFREE(osh, bus->rxbuf, bus->rxblen); +#endif + bus->rxctl = bus->rxbuf = NULL; + bus->rxlen = 0; + } + + if (bus->databuf) { +#ifndef DHD_USE_STATIC_BUF + MFREE(osh, bus->databuf, MAX_DATA_BUF); +#endif + 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) + MFREE(osh, bus->vars, bus->varsz); + 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 = MALLOC(bus->dhd->osh, bus->ramsize); + /* 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__)); + + MFREE(bus->dhd->osh, ularray, bus->ramsize); + } +#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; + uint8 *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 = MALLOC(bus->dhd->osh, MEMBLOCK + DHD_SDALIGN); + if (memblock == NULL) { + DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", + __func__, MEMBLOCK)); + goto err; + } + if ((uint32) (uintptr) memblock % DHD_SDALIGN) + memptr += + (DHD_SDALIGN - ((uint32) (uintptr) 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) + MFREE(bus->dhd->osh, memblock, MEMBLOCK + DHD_SDALIGN); + + if (image) + dhd_os_close_image(image); + + return bcmerror; +} + +/* + * ProcessVars:Takes a buffer of "=\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 = MALLOC(bus->dhd->osh, MEMBLOCK); + 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) + MFREE(bus->dhd->osh, memblock, 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, uint32 addr, uint fn, uint flags, + uint8 *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, uint32 addr, uint fn, uint flags, + uint8 *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, uint8 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, + (uint32 *) 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/linux_osl.c b/drivers/staging/brcm80211/brcmfmac/linux_osl.c new file mode 100644 index 000000000000..770238bdcf9e --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/linux_osl.c @@ -0,0 +1,588 @@ +/* + * 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 + +#define LINUX_OSL +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCI_CFG_RETRY 10 + +#define OS_HANDLE_MAGIC 0x1234abcd +#define BCM_MEM_FILENAME_LEN 24 + +#ifdef DHD_USE_STATIC_BUF +#define MAX_STATIC_BUF_NUM 16 +#define STATIC_BUF_SIZE (PAGE_SIZE*2) +#define STATIC_BUF_TOTAL_LEN (MAX_STATIC_BUF_NUM*STATIC_BUF_SIZE) +typedef struct bcm_static_buf { + struct semaphore static_sem; + unsigned char *buf_ptr; + unsigned char buf_use[MAX_STATIC_BUF_NUM]; +} bcm_static_buf_t; + +static bcm_static_buf_t *bcm_static_buf = 0; + +#define MAX_STATIC_PKT_NUM 8 +typedef struct bcm_static_pkt { + struct sk_buff *skb_4k[MAX_STATIC_PKT_NUM]; + struct sk_buff *skb_8k[MAX_STATIC_PKT_NUM]; + struct semaphore osl_pkt_sem; + unsigned char pkt_use[MAX_STATIC_PKT_NUM * 2]; +} bcm_static_pkt_t; +static bcm_static_pkt_t *bcm_static_skb = 0; +#endif /* DHD_USE_STATIC_BUF */ + +typedef struct bcm_mem_link { + struct bcm_mem_link *prev; + struct bcm_mem_link *next; + uint size; + int line; + char file[BCM_MEM_FILENAME_LEN]; +} bcm_mem_link_t; + +struct osl_info { + osl_pubinfo_t pub; + uint magic; + void *pdev; + uint malloced; + uint failed; + uint bustype; + bcm_mem_link_t *dbgmem_list; +}; + +static int16 linuxbcmerrormap[] = { 0, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -E2BIG, + -E2BIG, + -EBUSY, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EFAULT, + -ENOMEM, + -EOPNOTSUPP, + -EMSGSIZE, + -EINVAL, + -EPERM, + -ENOMEM, + -EINVAL, + -ERANGE, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EINVAL, + -EIO, + -ENODEV, + -EINVAL, + -EIO, + -EIO, + -EINVAL, + -EINVAL, + -ENODATA, + +#if BCME_LAST != BCME_NONRESIDENT +#error "You need to add a OS error translation in the linuxbcmerrormap \ + for new error code defined in bcmutils.h" +#endif +}; + +/* Global ASSERT type flag */ +uint32 g_assert_type = 0; + +int osl_error(int bcmerror) +{ + if (bcmerror > 0) + bcmerror = 0; + else if (bcmerror < BCME_LAST) + bcmerror = BCME_ERROR; + + return linuxbcmerrormap[-bcmerror]; +} + +void *dhd_os_prealloc(int section, unsigned long size); +osl_t *osl_attach(void *pdev, uint bustype, bool pkttag) +{ + osl_t *osh; + + osh = kmalloc(sizeof(osl_t), GFP_ATOMIC); + ASSERT(osh); + + bzero(osh, sizeof(osl_t)); + + ASSERT(ABS(BCME_LAST) == (ARRAYSIZE(linuxbcmerrormap) - 1)); + + osh->magic = OS_HANDLE_MAGIC; + osh->malloced = 0; + osh->failed = 0; + osh->dbgmem_list = NULL; + osh->pdev = pdev; + osh->pub.pkttag = pkttag; + osh->bustype = bustype; + + switch (bustype) { + case PCI_BUS: + case SI_BUS: + case PCMCIA_BUS: + osh->pub.mmbus = TRUE; + break; + case JTAG_BUS: + case SDIO_BUS: + case USB_BUS: + case SPI_BUS: + osh->pub.mmbus = FALSE; + break; + default: + ASSERT(FALSE); + break; + } + +#ifdef DHD_USE_STATIC_BUF + + if (!bcm_static_buf) { + if (!(bcm_static_buf = + (bcm_static_buf_t *) dhd_os_prealloc(3, + STATIC_BUF_SIZE + STATIC_BUF_TOTAL_LEN))) { + printk(KERN_ERR "can not alloc static buf!\n"); + } else + printk(KERN_ERR "alloc static buf at %x!\n", + (unsigned int)bcm_static_buf); + + init_MUTEX(&bcm_static_buf->static_sem); + + bcm_static_buf->buf_ptr = + (unsigned char *)bcm_static_buf + STATIC_BUF_SIZE; + + } + + if (!bcm_static_skb) { + int i; + void *skb_buff_ptr = 0; + bcm_static_skb = + (bcm_static_pkt_t *) ((char *)bcm_static_buf + 2048); + skb_buff_ptr = dhd_os_prealloc(4, 0); + + bcopy(skb_buff_ptr, bcm_static_skb, + sizeof(struct sk_buff *) * 16); + for (i = 0; i < MAX_STATIC_PKT_NUM * 2; i++) + bcm_static_skb->pkt_use[i] = 0; + + init_MUTEX(&bcm_static_skb->osl_pkt_sem); + } +#endif /* DHD_USE_STATIC_BUF */ + return osh; +} + +void osl_detach(osl_t *osh) +{ + if (osh == NULL) + return; + +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) + bcm_static_buf = 0; + + if (bcm_static_skb) + bcm_static_skb = 0; +#endif + ASSERT(osh->magic == OS_HANDLE_MAGIC); + kfree(osh); +} + +void *osl_pktget(osl_t *osh, uint len) +{ + struct sk_buff *skb; + + if ((skb = dev_alloc_skb(len))) { + skb_put(skb, len); + skb->priority = 0; + + osh->pub.pktalloced++; + } + + return (void *)skb; +} + +void osl_pktfree(osl_t *osh, void *p, bool send) +{ + struct sk_buff *skb, *nskb; + + skb = (struct sk_buff *)p; + + if (send && osh->pub.tx_fn) + osh->pub.tx_fn(osh->pub.tx_ctx, p, 0); + + while (skb) { + nskb = skb->next; + skb->next = NULL; + + if (skb->destructor) + dev_kfree_skb_any(skb); + else + dev_kfree_skb(skb); + + osh->pub.pktalloced--; + + skb = nskb; + } +} + +#ifdef DHD_USE_STATIC_BUF +void *osl_pktget_static(osl_t *osh, uint len) +{ + int i = 0; + struct sk_buff *skb; + + if (len > (PAGE_SIZE * 2)) { + printk(KERN_ERR "Do we really need this big skb??\n"); + return osl_pktget(osh, len); + } + + down(&bcm_static_skb->osl_pkt_sem); + if (len <= PAGE_SIZE) { + for (i = 0; i < MAX_STATIC_PKT_NUM; i++) { + if (bcm_static_skb->pkt_use[i] == 0) + break; + } + + if (i != MAX_STATIC_PKT_NUM) { + bcm_static_skb->pkt_use[i] = 1; + up(&bcm_static_skb->osl_pkt_sem); + + skb = bcm_static_skb->skb_4k[i]; + skb->tail = skb->data + len; + skb->len = len; + + return skb; + } + } + + for (i = 0; i < MAX_STATIC_PKT_NUM; i++) { + if (bcm_static_skb->pkt_use[i + MAX_STATIC_PKT_NUM] == 0) + break; + } + + if (i != MAX_STATIC_PKT_NUM) { + bcm_static_skb->pkt_use[i + MAX_STATIC_PKT_NUM] = 1; + up(&bcm_static_skb->osl_pkt_sem); + skb = bcm_static_skb->skb_8k[i]; + skb->tail = skb->data + len; + skb->len = len; + + return skb; + } + + up(&bcm_static_skb->osl_pkt_sem); + printk(KERN_ERR "all static pkt in use!\n"); + return osl_pktget(osh, len); +} + +void osl_pktfree_static(osl_t *osh, void *p, bool send) +{ + int i; + + for (i = 0; i < MAX_STATIC_PKT_NUM * 2; i++) { + if (p == bcm_static_skb->skb_4k[i]) { + down(&bcm_static_skb->osl_pkt_sem); + bcm_static_skb->pkt_use[i] = 0; + up(&bcm_static_skb->osl_pkt_sem); + + return; + } + } + return osl_pktfree(osh, p, send); +} +#endif /* DHD_USE_STATIC_BUF */ + +uint32 osl_pci_read_config(osl_t *osh, uint offset, uint size) +{ + uint val = 0; + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + ASSERT(size == 4); + + do { + pci_read_config_dword(osh->pdev, offset, &val); + if (val != 0xffffffff) + break; + } while (retry--); + + return val; +} + +void osl_pci_write_config(osl_t *osh, uint offset, uint size, uint val) +{ + uint retry = PCI_CFG_RETRY; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + ASSERT(size == 4); + + do { + pci_write_config_dword(osh->pdev, offset, val); + if (offset != PCI_BAR0_WIN) + break; + if (osl_pci_read_config(osh, offset, size) == val) + break; + } while (retry--); + +} + +uint osl_pci_bus(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return ((struct pci_dev *)osh->pdev)->bus->number; +} + +uint osl_pci_slot(osl_t *osh) +{ + ASSERT(osh && (osh->magic == OS_HANDLE_MAGIC) && osh->pdev); + + return PCI_SLOT(((struct pci_dev *)osh->pdev)->devfn); +} + +static void +osl_pcmcia_attr(osl_t *osh, uint offset, char *buf, int size, bool write) +{ +} + +void osl_pcmcia_read_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *)buf, size, FALSE); +} + +void osl_pcmcia_write_attr(osl_t *osh, uint offset, void *buf, int size) +{ + osl_pcmcia_attr(osh, offset, (char *)buf, size, TRUE); +} + +void *osl_malloc(osl_t *osh, uint size) +{ + void *addr; + + if (osh) + ASSERT(osh->magic == OS_HANDLE_MAGIC); + +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) { + int i = 0; + if ((size >= PAGE_SIZE) && (size <= STATIC_BUF_SIZE)) { + down(&bcm_static_buf->static_sem); + for (i = 0; i < MAX_STATIC_BUF_NUM; i++) { + if (bcm_static_buf->buf_use[i] == 0) + break; + } + if (i == MAX_STATIC_BUF_NUM) { + up(&bcm_static_buf->static_sem); + printk(KERN_ERR "all static buff in use!\n"); + goto original; + } + bcm_static_buf->buf_use[i] = 1; + up(&bcm_static_buf->static_sem); + + bzero(bcm_static_buf->buf_ptr + STATIC_BUF_SIZE * i, + size); + if (osh) + osh->malloced += size; + + return (void *)(bcm_static_buf->buf_ptr + + STATIC_BUF_SIZE * i); + } + } +original: +#endif /* DHD_USE_STATIC_BUF */ + + if ((addr = kmalloc(size, GFP_ATOMIC)) == NULL) { + if (osh) + osh->failed++; + return NULL; + } + if (osh) + osh->malloced += size; + + return addr; +} + +void osl_mfree(osl_t *osh, void *addr, uint size) +{ +#ifdef DHD_USE_STATIC_BUF + if (bcm_static_buf) { + if ((addr > (void *)bcm_static_buf) && ((unsigned char *)addr + <= ((unsigned char *) + bcm_static_buf + + STATIC_BUF_TOTAL_LEN))) { + int buf_idx = 0; + buf_idx = + ((unsigned char *)addr - + bcm_static_buf->buf_ptr) / STATIC_BUF_SIZE; + down(&bcm_static_buf->static_sem); + bcm_static_buf->buf_use[buf_idx] = 0; + up(&bcm_static_buf->static_sem); + + if (osh) { + ASSERT(osh->magic == OS_HANDLE_MAGIC); + osh->malloced -= size; + } + return; + } + } +#endif /* DHD_USE_STATIC_BUF */ + if (osh) { + ASSERT(osh->magic == OS_HANDLE_MAGIC); + osh->malloced -= size; + } + kfree(addr); +} + +uint osl_malloced(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return osh->malloced; +} + +uint osl_malloc_failed(osl_t *osh) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + return osh->failed; +} + +void *osl_dma_alloc_consistent(osl_t *osh, uint size, ulong *pap) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + return pci_alloc_consistent(osh->pdev, size, (dma_addr_t *) pap); +} + +void osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa) +{ + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + + pci_free_consistent(osh->pdev, size, va, (dma_addr_t) pa); +} + +uint osl_dma_map(osl_t *osh, void *va, uint size, int direction) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE; + return pci_map_single(osh->pdev, va, size, dir); +} + +void osl_dma_unmap(osl_t *osh, uint pa, uint size, int direction) +{ + int dir; + + ASSERT((osh && (osh->magic == OS_HANDLE_MAGIC))); + dir = (direction == DMA_TX) ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE; + pci_unmap_single(osh->pdev, (uint32) pa, size, dir); +} + +#if defined(BCMDBG_ASSERT) +void osl_assert(char *exp, char *file, int line) +{ + char tempbuf[256]; + char *basename; + + basename = strrchr(file, '/'); + /* skip the '/' */ + if (basename) + basename++; + + if (!basename) + basename = file; + +#ifdef BCMDBG_ASSERT + snprintf(tempbuf, 256, + "assertion \"%s\" failed: file \"%s\", line %d\n", exp, + basename, line); + + /* Print assert message and give it time to be written + to /var/log/messages */ + if (!in_interrupt()) { + const int delay = 3; + printk(KERN_ERR "%s", tempbuf); + printk(KERN_ERR "panic in %d seconds\n", delay); + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(delay * HZ); + } + + switch (g_assert_type) { + case 0: + panic(KERN_ERR "%s", tempbuf); + break; + case 1: + printk(KERN_ERR "%s", tempbuf); + BUG(); + break; + case 2: + printk(KERN_ERR "%s", tempbuf); + break; + default: + break; + } +#endif /* BCMDBG_ASSERT */ + +} +#endif /* defined(BCMDBG_ASSERT) */ + +void osl_delay(uint usec) +{ + uint d; + + while (usec > 0) { + d = MIN(usec, 1000); + udelay(d); + usec -= d; + } +} + +void *osl_pktdup(osl_t *osh, void *skb) +{ + void *p; + + if ((p = skb_clone((struct sk_buff *)skb, GFP_ATOMIC)) == NULL) + return NULL; + + if (osh->pub.pkttag) + bzero((void *)((struct sk_buff *)p)->cb, OSL_PKTTAG_SZ); + + osh->pub.pktalloced++; + return p; +} diff --git a/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c new file mode 100644 index 000000000000..1b23d84dfa9c --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.c @@ -0,0 +1,4020 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct sdio_func *cfg80211_sdio_func = NULL; +static struct wl_dev *wl_cfg80211_dev = NULL; + +uint32 wl_dbg_level = WL_DBG_ERR | WL_DBG_INFO; + +#define WL_4329_FW_FILE "brcm/bcm4329-fullmac-4-218-248-5.bin" +#define WL_4329_NVRAM_FILE "brcm/bcm4329-fullmac-4-218-248-5.txt" + +/* +** cfg80211_ops api/callback list +*/ +static int32 wl_cfg80211_change_iface(struct wiphy *wiphy, + struct net_device *ndev, + enum nl80211_iftype type, uint32 *flags, + struct vif_params *params); +static int32 __wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request, + struct cfg80211_ssid *this_ssid); +static int32 wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request); +static int32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, uint32 changed); +static int32 wl_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_ibss_params *params); +static int32 wl_cfg80211_leave_ibss(struct wiphy *wiphy, + struct net_device *dev); +static int32 wl_cfg80211_get_station(struct wiphy *wiphy, + struct net_device *dev, uint8 *mac, + struct station_info *sinfo); +static int32 wl_cfg80211_set_power_mgmt(struct wiphy *wiphy, + struct net_device *dev, bool enabled, + int32 timeout); +static int32 wl_cfg80211_set_bitrate_mask(struct wiphy *wiphy, + struct net_device *dev, + const uint8 *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 int32 wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + uint16 reason_code); +static int32 wl_cfg80211_set_tx_power(struct wiphy *wiphy, + enum nl80211_tx_power_setting type, + int32 dbm); +static int32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, int32 *dbm); +static int32 wl_cfg80211_config_default_key(struct wiphy *wiphy, + struct net_device *dev, + uint8 key_idx); +static int32 wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr, + struct key_params *params); +static int32 wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr); +static int32 wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr, + void *cookie, void (*callback) (void *cookie, + struct + key_params * + params)); +static int32 wl_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *dev, + uint8 key_idx); +static int32 wl_cfg80211_resume(struct wiphy *wiphy); +static int32 wl_cfg80211_suspend(struct wiphy *wiphy); +static int32 wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa); +static int32 wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa); +static int32 wl_cfg80211_flush_pmksa(struct wiphy *wiphy, + struct net_device *dev); +/* +** event & event Q handlers for cfg80211 interfaces +*/ +static int32 wl_create_event_handler(struct wl_priv *wl); +static void wl_destroy_event_handler(struct wl_priv *wl); +static int32 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 int32 wl_enq_event(struct wl_priv *wl, uint32 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 int32 wl_notify_connect_status(struct wl_priv *wl, + struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static int32 wl_notify_roaming_status(struct wl_priv *wl, + struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static int32 wl_notify_scan_status(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static int32 wl_bss_connect_done(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static int32 wl_bss_roaming_done(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); +static int32 wl_notify_mic_status(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data); + +/* +** register/deregister sdio function +*/ +static struct sdio_func *wl_sdio_func(void); +static void wl_clear_sdio_func(void); + +/* +** ioctl utilites +*/ +static int32 wl_dev_bufvar_get(struct net_device *dev, int8 *name, int8 *buf, + int32 buf_len); +static __used int32 wl_dev_bufvar_set(struct net_device *dev, int8 *name, + int8 *buf, int32 len); +static int32 wl_dev_intvar_set(struct net_device *dev, int8 *name, int32 val); +static int32 wl_dev_intvar_get(struct net_device *dev, int8 *name, + int32 *retval); +static int32 wl_dev_ioctl(struct net_device *dev, uint32 cmd, void *arg, + uint32 len); + +/* +** cfg80211 set_wiphy_params utilities +*/ +static int32 wl_set_frag(struct net_device *dev, uint32 frag_threshold); +static int32 wl_set_rts(struct net_device *dev, uint32 frag_threshold); +static int32 wl_set_retry(struct net_device *dev, uint32 retry, bool l); + +/* +** wl profile utilities +*/ +static int32 wl_update_prof(struct wl_priv *wl, const wl_event_msg_t *e, + void *data, int32 item); +static void *wl_read_prof(struct wl_priv *wl, int32 item); +static void wl_init_prof(struct wl_profile *prof); + +/* +** cfg80211 connect utilites +*/ +static int32 wl_set_wpa_version(struct net_device *dev, + struct cfg80211_connect_params *sme); +static int32 wl_set_auth_type(struct net_device *dev, + struct cfg80211_connect_params *sme); +static int32 wl_set_set_cipher(struct net_device *dev, + struct cfg80211_connect_params *sme); +static int32 wl_set_key_mgmt(struct net_device *dev, + struct cfg80211_connect_params *sme); +static int32 wl_set_set_sharedkey(struct net_device *dev, + struct cfg80211_connect_params *sme); +static int32 wl_get_assoc_ies(struct wl_priv *wl); + +/* +** information element utilities +*/ +static void wl_rst_ie(struct wl_priv *wl); +static int32 wl_add_ie(struct wl_priv *wl, uint8 t, uint8 l, uint8 *v); +static int32 wl_mrg_ie(struct wl_priv *wl, uint8 *ie_stream, uint16 ie_size); +static int32 wl_cp_ie(struct wl_priv *wl, uint8 *dst, uint16 dst_size); +static uint32 wl_get_ielen(struct wl_priv *wl); + +static int32 wl_mode_to_nl80211_iftype(int32 mode); + +static struct wireless_dev *wl_alloc_wdev(int32 sizeof_iface, + struct device *dev); +static void wl_free_wdev(struct wl_priv *wl); + +static int32 wl_inform_bss(struct wl_priv *wl); +static int32 wl_inform_single_bss(struct wl_priv *wl, struct wl_bss_info *bi); +static int32 wl_update_bss_info(struct wl_priv *wl); + +static int32 wl_add_keyext(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *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 int32 wl_init_priv_mem(struct wl_priv *wl); +static void wl_deinit_priv_mem(struct wl_priv *wl); + +static void wl_delay(uint32 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 void wl_link_up(struct wl_priv *wl); +static void wl_link_down(struct wl_priv *wl); +static int32 wl_dongle_mode(struct net_device *ndev, int32 iftype); +static int32 __wl_cfg80211_up(struct wl_priv *wl); +static int32 __wl_cfg80211_down(struct wl_priv *wl); +static int32 wl_dongle_probecap(struct wl_priv *wl); +static void wl_init_conf(struct wl_conf *conf); + +/* +** dongle configuration utilities +*/ +#ifndef EMBEDDED_PLATFORM +static int32 wl_dongle_mode(struct net_device *ndev, int32 iftype); +static int32 wl_dongle_country(struct net_device *ndev, uint8 ccode); +static int32 wl_dongle_up(struct net_device *ndev, uint32 up); +static int32 wl_dongle_power(struct net_device *ndev, uint32 power_mode); +static int32 wl_dongle_glom(struct net_device *ndev, uint32 glom, + uint32 dongle_align); +static int32 wl_dongle_roam(struct net_device *ndev, uint32 roamvar, + uint32 bcn_timeout); +static int32 wl_dongle_eventmsg(struct net_device *ndev); +static int32 wl_dongle_scantime(struct net_device *ndev, int32 scan_assoc_time, + int32 scan_unassoc_time); +static int32 wl_dongle_offload(struct net_device *ndev, int32 arpoe, + int32 arp_ol); +static int32 wl_pattern_atoh(int8 *src, int8 *dst); +static int32 wl_dongle_filter(struct net_device *ndev, uint32 filter_mode); +static int32 wl_update_wiphybands(struct wl_priv *wl); +#endif /* !EMBEDDED_PLATFORM */ +static int32 wl_config_dongle(struct wl_priv *wl, bool need_lock); + +/* +** iscan handler +*/ +static void wl_iscan_timer(ulong data); +static void wl_term_iscan(struct wl_priv *wl); +static int32 wl_init_iscan(struct wl_priv *wl); +static int32 wl_iscan_thread(void *data); +static int32 wl_dev_iovar_setbuf(struct net_device *dev, int8 *iovar, + void *param, int32 paramlen, void *bufptr, + int32 buflen); +static int32 wl_dev_iovar_getbuf(struct net_device *dev, int8 *iovar, + void *param, int32 paramlen, void *bufptr, + int32 buflen); +static int32 wl_run_iscan(struct wl_iscan_ctrl *iscan, struct wlc_ssid *ssid, + uint16 action); +static int32 wl_do_iscan(struct wl_priv *wl); +static int32 wl_wakeup_iscan(struct wl_iscan_ctrl *iscan); +static int32 wl_invoke_iscan(struct wl_priv *wl); +static int32 wl_get_iscan_results(struct wl_iscan_ctrl *iscan, uint32 *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 int32 wl_iscan_done(struct wl_priv *wl); +static int32 wl_iscan_pending(struct wl_priv *wl); +static int32 wl_iscan_inprogress(struct wl_priv *wl); +static int32 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 uint32 wl_find_msb(uint16 bit16); + +/* +* update pmklist to dongle +*/ +static __used int32 wl_update_pmklist(struct net_device *dev, + struct wl_pmk_list *pmk_list, int32 err); + +#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 int8 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 uint32 __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 int32 +wl_dev_ioctl(struct net_device *dev, uint32 cmd, void *arg, uint32 len) +{ + struct ifreq ifr; + struct wl_ioctl ioc; + mm_segment_t fs; + int32 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 int32 +wl_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev, + enum nl80211_iftype type, uint32 *flags, + struct vif_params *params) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + struct wireless_dev *wdev; + int32 infra = 0; + int32 ap = 0; + int32 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)); + if (unlikely + ((err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra)))) + || + unlikely((err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap))))) { + WL_ERR(("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(¶ms->bssid, ðer_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(¶ms->ssid, ssid, sizeof(wlc_ssid_t)); + +} + +static int32 +wl_dev_iovar_setbuf(struct net_device *dev, int8 * iovar, void *param, + int32 paramlen, void *bufptr, int32 buflen) +{ + int32 iolen; + + iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen); + BUG_ON(unlikely(!iolen)); + + return wl_dev_ioctl(dev, WLC_SET_VAR, bufptr, iolen); +} + +static int32 +wl_dev_iovar_getbuf(struct net_device *dev, int8 * iovar, void *param, + int32 paramlen, void *bufptr, int32 buflen) +{ + int32 iolen; + + iolen = bcm_mkiovar(iovar, param, paramlen, bufptr, buflen); + BUG_ON(unlikely(!iolen)); + + return wl_dev_ioctl(dev, WLC_GET_VAR, bufptr, buflen); +} + +static int32 +wl_run_iscan(struct wl_iscan_ctrl *iscan, struct wlc_ssid *ssid, uint16 action) +{ + int32 params_size = + (WL_SCAN_PARAMS_FIXED_SIZE + OFFSETOF(wl_iscan_params_t, params)); + struct wl_iscan_params *params; + int32 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(¶ms->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); */ + if (unlikely + ((err = + wl_dev_iovar_setbuf(iscan->dev, "iscan", params, params_size, + iscan->ioctl_buf, WLC_IOCTL_SMLEN)))) { + if (err == -EBUSY) { + WL_INFO(("system busy : iscan canceled\n")); + } else { + WL_ERR(("error (%d)\n", err)); + } + } + kfree(params); + return err; +} + +static int32 wl_do_iscan(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl_to_iscan(wl); + struct wlc_ssid ssid; + int32 err = 0; + + /* Broadcast scan by default */ + memset(&ssid, 0, sizeof(ssid)); + + iscan->state = WL_ISCAN_STATE_SCANING; + + if (wl->active_scan) { + int32 passive_scan = 0; + /* make it active scan */ + if (unlikely + ((err = + wl_dev_ioctl(wl_to_ndev(wl), WLC_SET_PASSIVE_SCAN, + &passive_scan, sizeof(passive_scan))))) { + WL_DBG(("error (%d)\n", err)); + return err; + } + } + 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 int32 +__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); + uint32 n_ssids; + bool iscan_req; + bool spec_scan; + int32 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; + n_ssids = min(request->n_ssids, WL_NUM_SCAN_MAX); + if (wl->iscan_on && n_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; + n_ssids = 1; + } + wl->scan_request = request; + set_bit(WL_STATUS_SCANNING, &wl->status); + if (iscan_req) { + if (likely(!(err = wl_do_iscan(wl)))) + return err; + else + goto scan_out; + } else { + WL_DBG(("n_ssid (%d), ssid \"%s\", ssid_len (%d)\n", + n_ssids, ssids->ssid, ssids->ssid_len)); + memset(&sr->ssid, 0, sizeof(sr->ssid)); + if (n_ssids) { + sr->ssid.SSID_len = + MIN(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")); + } + } else { + /* broadcast scan */ + WL_DBG(("Broadcast scan\n")); + } + WL_DBG(("sr->ssid.SSID_len (%d)\n", sr->ssid.SSID_len)); + if (wl->active_scan) { + int32 pssive_scan = 0; + /* make it active scan */ + if (unlikely + ((err = + wl_dev_ioctl(ndev, WLC_SET_PASSIVE_SCAN, + &pssive_scan, + sizeof(pssive_scan))))) { + WL_ERR(("WLC_SET_PASSIVE_SCAN error (%d)\n", + err)); + goto scan_out; + } + } + if ((err = + wl_dev_ioctl(ndev, WLC_SCAN, &sr->ssid, + sizeof(sr->ssid)))) { + if (err == -EBUSY) { + WL_INFO(("system busy : scan for \"%s\" " + "canceled\n", sr->ssid.SSID)); + } else { + WL_ERR(("WLC_SCAN error (%d)\n", err)); + } + goto scan_out; + } + } + + return 0; + +scan_out: + clear_bit(WL_STATUS_SCANNING, &wl->status); + wl->scan_request = NULL; + return err; +} + +static int32 +wl_cfg80211_scan(struct wiphy *wiphy, struct net_device *ndev, + struct cfg80211_scan_request *request) +{ + int32 err = 0; + + CHECK_SYS_UP(); + if (unlikely((err = __wl_cfg80211_scan(wiphy, ndev, request, NULL)))) { + WL_DBG(("scan error (%d)\n", err)); + return err; + } + + return err; +} + +static int32 wl_dev_intvar_set(struct net_device *dev, int8 *name, int32 val) +{ + int8 buf[WLC_IOCTL_SMLEN]; + uint32 len; + int32 err = 0; + + val = htod32(val); + len = bcm_mkiovar(name, (char *)(&val), sizeof(val), buf, sizeof(buf)); + BUG_ON(unlikely(!len)); + + if (unlikely((err = wl_dev_ioctl(dev, WLC_SET_VAR, buf, len)))) { + WL_ERR(("error (%d)\n", err)); + } + + return err; +} + +static int32 +wl_dev_intvar_get(struct net_device *dev, int8 *name, int32 *retval) +{ + union { + int8 buf[WLC_IOCTL_SMLEN]; + int32 val; + } var; + uint32 len; + uint32 data_null; + int32 err = 0; + + len = + bcm_mkiovar(name, (char *)(&data_null), 0, (char *)(&var), + sizeof(var.buf)); + BUG_ON(unlikely(!len)); + if (unlikely((err = wl_dev_ioctl(dev, WLC_GET_VAR, &var, len)))) { + WL_ERR(("error (%d)\n", err)); + } + *retval = dtoh32(var.val); + + return err; +} + +static int32 wl_set_rts(struct net_device *dev, uint32 rts_threshold) +{ + int32 err = 0; + + if (unlikely + ((err = wl_dev_intvar_set(dev, "rtsthresh", rts_threshold)))) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static int32 wl_set_frag(struct net_device *dev, uint32 frag_threshold) +{ + int32 err = 0; + + if (unlikely + ((err = wl_dev_intvar_set(dev, "fragthresh", frag_threshold)))) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static int32 wl_set_retry(struct net_device *dev, uint32 retry, bool l) +{ + int32 err = 0; + uint32 cmd = (l ? WLC_SET_LRL : WLC_SET_SRL); + + retry = htod32(retry); + if (unlikely((err = wl_dev_ioctl(dev, cmd, &retry, sizeof(retry))))) { + WL_ERR(("cmd (%d) , error (%d)\n", cmd, err)); + return err; + } + return err; +} + +static int32 wl_cfg80211_set_wiphy_params(struct wiphy *wiphy, uint32 changed) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + int32 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; + if (!(err = wl_set_rts(ndev, wl->conf->rts_threshold))) + return err; + } + if (changed & WIPHY_PARAM_FRAG_THRESHOLD && + (wl->conf->frag_threshold != wiphy->frag_threshold)) { + wl->conf->frag_threshold = wiphy->frag_threshold; + if (!(err = wl_set_frag(ndev, wl->conf->frag_threshold))) + return err; + } + if (changed & WIPHY_PARAM_RETRY_LONG + && (wl->conf->retry_long != wiphy->retry_long)) { + wl->conf->retry_long = wiphy->retry_long; + if (!(err = wl_set_retry(ndev, wl->conf->retry_long, TRUE))) + return err; + } + if (changed & WIPHY_PARAM_RETRY_SHORT + && (wl->conf->retry_short != wiphy->retry_short)) { + wl->conf->retry_short = wiphy->retry_short; + if (!(err = wl_set_retry(ndev, wl->conf->retry_short, FALSE))) { + return err; + } + } + + return err; +} + +static int32 +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; + int32 scan_retry = 0; + int32 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; + } + if ((chan = params->channel)) + 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); + + if (unlikely + ((err = + wl_dev_ioctl(dev, WLC_SET_SSID, &join_params, + sizeof(join_params))))) { + WL_ERR(("Error (%d)\n", err)); + return err; + } + return err; +} + +static int32 wl_cfg80211_leave_ibss(struct wiphy *wiphy, struct net_device *dev) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + int32 err = 0; + + CHECK_SYS_UP(); + wl_link_down(wl); + + return err; +} + +static int32 +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; + int32 val = 0; + int32 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)); + if (unlikely((err = wl_dev_intvar_set(dev, "wpa_auth", val)))) { + 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 int32 +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; + int32 val = 0; + int32 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; + } + + if (unlikely((err = wl_dev_intvar_set(dev, "auth", val)))) { + 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 int32 +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; + int32 pval = 0; + int32 gval = 0; + int32 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)); + if (unlikely((err = wl_dev_intvar_set(dev, "wsec", pval | gval)))) { + 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 int32 +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; + int32 val = 0; + int32 err = 0; + + if (sme->crypto.n_akm_suites) { + if (unlikely((err = wl_dev_intvar_get(dev, "wpa_auth", &val)))) { + 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)); + if (unlikely((err = wl_dev_intvar_set(dev, "wpa_auth", val)))) { + 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 int32 +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; + int32 val; + int32 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 = (uint32) sme->key_len; + key.index = (uint32) 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); + if (unlikely + ((err = + wl_dev_ioctl(dev, WLC_SET_KEY, &key, + sizeof(key))))) { + 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 */ + if (unlikely + ((err = + wl_dev_intvar_set(dev, "auth", val)))) { + WL_ERR(("set auth failed (%d)\n", err)); + return err; + } + } + } + } + return err; +} + +static int32 +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 wlc_ssid ssid; + int32 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 (%d)\n", sme->ie, sme->ie_len)); + if (unlikely((err = wl_set_wpa_version(dev, sme)))) + return err; + + if (unlikely((err = wl_set_auth_type(dev, sme)))) + return err; + + if (unlikely((err = wl_set_set_cipher(dev, sme)))) + return err; + + if (unlikely((err = wl_set_key_mgmt(dev, sme)))) + return err; + + if (unlikely((err = wl_set_set_sharedkey(dev, sme)))) + 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(&ssid, 0, sizeof(ssid)); + ssid.SSID_len = MIN(sizeof(ssid.SSID), sme->ssid_len); + memcpy(ssid.SSID, sme->ssid, ssid.SSID_len); + ssid.SSID_len = htod32(ssid.SSID_len); + wl_update_prof(wl, NULL, &ssid, WL_PROF_SSID); + if (ssid.SSID_len < IEEE80211_MAX_SSID_LEN) { + WL_DBG(("ssid \"%s\", len (%d)\n", ssid.SSID, ssid.SSID_len)); + } + if (unlikely + ((err = wl_dev_ioctl(dev, WLC_SET_SSID, &ssid, sizeof(ssid))))) { + WL_ERR(("error (%d)\n", err)); + return err; + } + set_bit(WL_STATUS_CONNECTING, &wl->status); + + return err; +} + +static int32 +wl_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, + uint16 reason_code) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + scb_val_t scbval; + bool act = FALSE; + int32 err = 0; + + WL_DBG(("Reason %d\n", reason_code)); + CHECK_SYS_UP(); + if (likely((act = *(bool *) wl_read_prof(wl, WL_PROF_ACT)))) { + scbval.val = reason_code; + memcpy(&scbval.ea, &wl->bssid, ETHER_ADDR_LEN); + scbval.val = htod32(scbval.val); + if (unlikely((err = wl_dev_ioctl(dev, WLC_DISASSOC, &scbval, + sizeof(scb_val_t))))) { + WL_ERR(("error (%d)\n", err)); + return err; + } + } + + return err; +} + +static int32 +wl_cfg80211_set_tx_power(struct wiphy *wiphy, + enum nl80211_tx_power_setting type, int32 dbm) +{ + + struct wl_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + uint16 txpwrmw; + int32 err = 0; + int32 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); + if (unlikely + ((err = + wl_dev_ioctl(ndev, WLC_SET_RADIO, &disable, sizeof(disable))))) { + WL_ERR(("WLC_SET_RADIO error (%d)\n", err)); + return err; + } + + if (dbm > 0xffff) + txpwrmw = 0xffff; + else + txpwrmw = (uint16) dbm; + if (unlikely((err = wl_dev_intvar_set(ndev, "qtxpower", + (int32) (bcm_mw_to_qdbm + (txpwrmw)))))) { + WL_ERR(("qtxpower error (%d)\n", err)); + return err; + } + wl->conf->tx_power = dbm; + + return err; +} + +static int32 wl_cfg80211_get_tx_power(struct wiphy *wiphy, int32 *dbm) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + struct net_device *ndev = wl_to_ndev(wl); + int32 txpwrdbm; + uint8 result; + int32 err = 0; + + CHECK_SYS_UP(); + if (unlikely((err = wl_dev_intvar_get(ndev, "qtxpower", &txpwrdbm)))) { + WL_ERR(("error (%d)\n", err)); + return err; + } + result = (uint8) (txpwrdbm & ~WL_TXPWR_OVERRIDE); + *dbm = (int32) bcm_qdbm_to_mw(result); + + return err; +} + +static int32 +wl_cfg80211_config_default_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx) +{ + uint32 index; + int32 wsec; + int32 err = 0; + + WL_DBG(("key index (%d)\n", key_idx)); + CHECK_SYS_UP(); + + if (unlikely + (err = wl_dev_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec)))) { + 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 = (uint32) key_idx; + index = htod32(index); + if (unlikely((err = wl_dev_ioctl(dev, WLC_SET_KEY_PRIMARY, + &index, sizeof(index))))) { + WL_ERR(("error (%d)\n", err)); + } + } + return err; +} + +static int32 +wl_add_keyext(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr, struct key_params *params) +{ + struct wl_wsec_key key; + int32 err = 0; + + memset(&key, 0, sizeof(key)); + key.index = (uint32) 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 = (uint32) params->key_len; + /* check for key index change */ + if (key.len == 0) { + /* key delete */ + swap_key_from_BE(&key); + if (unlikely + ((err = + wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key))))) { + 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) { + uint8 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 */ + uint8 *ivptr; + ivptr = (uint8 *) 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); + if (unlikely + ((err = + wl_dev_ioctl(dev, WLC_SET_KEY, &key, sizeof(key))))) { + WL_ERR(("WLC_SET_KEY error (%d)\n", err)); + return err; + } + } + return err; +} + +static int32 +wl_cfg80211_add_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr, + struct key_params *params) +{ + struct wl_wsec_key key; + int32 val; + int32 wsec; + int32 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 = (uint32) params->key_len; + key.index = (uint32) 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); + if (unlikely((err = wl_dev_ioctl(dev, WLC_SET_KEY, + &key, sizeof(key))))) { + WL_ERR(("WLC_SET_KEY error (%d)\n", err)); + return err; + } + + val = WEP_ENABLED; + if (unlikely((err = wl_dev_intvar_get(dev, "wsec", &wsec)))) { + WL_ERR(("get wsec error (%d)\n", err)); + return err; + } + wsec &= ~(WEP_ENABLED); + wsec |= val; + if (unlikely((err = wl_dev_intvar_set(dev, "wsec", wsec)))) { + WL_ERR(("set wsec error (%d)\n", err)); + return err; + } + + val = 1; /* assume shared key. otherwise 0 */ + val = htod32(val); + if (unlikely + ((err = wl_dev_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val))))) { + WL_ERR(("WLC_SET_AUTH error (%d)\n", err)); + return err; + } + return err; +} + +static int32 +wl_cfg80211_del_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *mac_addr) +{ + struct wl_wsec_key key; + int32 err = 0; + int32 val; + int32 wsec; + + CHECK_SYS_UP(); + memset(&key, 0, sizeof(key)); + + key.index = (uint32) 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); + if (unlikely((err = wl_dev_ioctl(dev, WLC_SET_KEY, + &key, sizeof(key))))) { + 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; + if (unlikely((err = wl_dev_intvar_get(dev, "wsec", &wsec)))) { + WL_ERR(("get wsec error (%d)\n", err)); + return err; + } + wsec &= ~(WEP_ENABLED); + wsec |= val; + if (unlikely((err = wl_dev_intvar_set(dev, "wsec", wsec)))) { + WL_ERR(("set wsec error (%d)\n", err)); + return err; + } + + val = 0; /* assume open key. otherwise 1 */ + val = htod32(val); + if (unlikely + ((err = wl_dev_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val))))) { + WL_ERR(("WLC_SET_AUTH error (%d)\n", err)); + return err; + } + return err; +} + +static int32 +wl_cfg80211_get_key(struct wiphy *wiphy, struct net_device *dev, + uint8 key_idx, const uint8 *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; + int32 wsec; + int32 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(¶ms, 0, sizeof(params)); + params.key_len = (uint8) MIN(DOT11_MAX_KEY_SIZE, key.len); + memcpy(params.key, key.data, params.key_len); + + if (unlikely + (err = wl_dev_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec)))) { + 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, ¶ms); + return err; +} + +static int32 +wl_cfg80211_config_default_mgmt_key(struct wiphy *wiphy, + struct net_device *dev, uint8 key_idx) +{ + WL_INFO(("Not supported\n")); + CHECK_SYS_UP(); + return -EOPNOTSUPP; +} + +static int32 +wl_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, + uint8 *mac, struct station_info *sinfo) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + scb_val_t scb_val; + int rssi; + int32 rate; + int32 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 */ + if ((err = wl_dev_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate)))) { + 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; + if (unlikely + (err = + wl_dev_ioctl(dev, WLC_GET_RSSI, &scb_val, + sizeof(scb_val_t)))) { + 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 int32 +wl_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, + bool enabled, int32 timeout) +{ + int32 pm; + int32 err = 0; + + CHECK_SYS_UP(); + pm = enabled ? PM_FAST : PM_OFF; + pm = htod32(pm); + WL_DBG(("power save %s\n", (pm ? "enabled" : "disabled"))); + if (unlikely((err = wl_dev_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm))))) { + 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 uint32 wl_find_msb(uint16 bit16) +{ + uint32 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 int32 +wl_cfg80211_set_bitrate_mask(struct wiphy *wiphy, struct net_device *dev, + const uint8 *addr, + const struct cfg80211_bitrate_mask *mask) +{ + struct wl_rateset rateset; + int32 rate; + int32 val; + int32 err_bg; + int32 err_a; + uint32 legacy; + int32 err = 0; + + CHECK_SYS_UP(); + /* addr param is always NULL. ignore it */ + /* Get current rateset */ + if (unlikely((err = wl_dev_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, + sizeof(rateset))))) { + WL_ERR(("could not get current rateset (%d)\n", err)); + return err; + } + + rateset.count = dtoh32(rateset.count); + + if (!(legacy = wl_find_msb(mask->control[IEEE80211_BAND_2GHZ].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 int32 wl_cfg80211_resume(struct wiphy *wiphy) +{ + int32 err = 0; + + CHECK_SYS_UP(); + wl_invoke_iscan(wiphy_to_wl(wiphy)); + + return err; +} + +static int32 wl_cfg80211_suspend(struct wiphy *wiphy) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + int32 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->scan_request = NULL; + } + clear_bit(WL_STATUS_SCANNING, &wl->status); + clear_bit(WL_STATUS_SCAN_ABORTING, &wl->status); + + return err; +} + +static __used int32 +wl_update_pmklist(struct net_device *dev, struct wl_pmk_list *pmk_list, + int32 err) +{ + int8 eabuf[ETHER_ADDR_STR_LEN]; + int i, j; + + memset(eabuf, 0, ETHER_ADDR_STR_LEN); + + WL_DBG(("No of elements %d\n", pmk_list->pmkids.npmkid)); + for (i = 0; i < pmk_list->pmkids.npmkid; i++) { + WL_DBG(("PMKID[%d]: %s =\n", i, + bcm_ether_ntoa(&pmk_list->pmkids.pmkid[i].BSSID, + eabuf))); + 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 int32 +wl_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + int8 eabuf[ETHER_ADDR_STR_LEN]; + int32 err = 0; + int i; + + CHECK_SYS_UP(); + memset(eabuf, 0, ETHER_ADDR_STR_LEN); + 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: %s =\n", + bcm_ether_ntoa(&wl->pmk_list->pmkids. + pmkid[wl->pmk_list->pmkids.npmkid].BSSID, + eabuf))); + 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 int32 +wl_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *dev, + struct cfg80211_pmksa *pmksa) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + int8 eabuf[ETHER_ADDR_STR_LEN]; + struct _pmkid_list pmkid; + int32 err = 0; + int i; + + CHECK_SYS_UP(); + memset(eabuf, 0, ETHER_ADDR_STR_LEN); + 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: %s =\n", + bcm_ether_ntoa(&pmkid.pmkid[0].BSSID, eabuf))); + 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 int32 +wl_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *dev) +{ + struct wl_priv *wl = wiphy_to_wl(wiphy); + int32 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 int32 wl_mode_to_nl80211_iftype(int32 mode) +{ + int32 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(int32 sizeof_iface, + struct device *dev) +{ + struct wireless_dev *wdev; + int32 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 */ + if (unlikely(((err = wiphy_register(wdev->wiphy)) < 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 int32 wl_inform_bss(struct wl_priv *wl) +{ + struct wl_scan_results *bss_list; + struct wl_bss_info *bi = NULL; /* must be initialized */ + int32 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) { + if (unlikely(err = wl_inform_single_bss(wl, bi))) + break; + } + return err; +} + +static int32 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); + uint32 signal; + uint32 freq; + int32 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(uint8) + + 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 = 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); + if (!memcmp(bi->SSID, sr->ssid.SSID, bi->SSID_len)) { + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + } + mgmt->u.probe_resp.timestamp = 0; + mgmt->u.probe_resp.beacon_int = cpu_to_le16(bi->beacon_period); + mgmt->u.probe_resp.capab_info = cpu_to_le16(bi->capability); + wl_rst_ie(wl); + 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, ((uint8 *) bi) + bi->ie_offset, bi->ie_length); + wl_cp_ie(wl, mgmt->u.probe_resp.variable, WL_BSS_INFO_MAX - + offsetof(struct wl_cfg80211_bss_info, frame_buf)); + notif_bss_info->frame_len = + offsetof(struct ieee80211_mgmt, + u.probe_resp.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), capability : 0x04%x\n", bi->SSID, + notif_bss_info->rssi, mgmt->u.probe_resp.capab_info)); + + 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) +{ + uint32 event = ntoh32(e->event_type); + uint16 flags = ntoh16(e->flags); + + if (event == WLC_E_JOIN || event == WLC_E_ASSOC_IND + || event == WLC_E_REASSOC_IND) { + return TRUE; + } else if (event == WLC_E_LINK) { + if (flags & WLC_EVENT_MSG_LINK) { + if (wl_is_ibssmode(wl)) { + if (wl_is_ibssstarter(wl)) { + } + } else { + + } + } + } + + return FALSE; +} + +static bool wl_is_linkdown(struct wl_priv *wl, const wl_event_msg_t *e) +{ + uint32 event = ntoh32(e->event_type); + uint16 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 int32 +wl_notify_connect_status(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + bool act; + int32 err = 0; + + if (wl_is_linkup(wl, e)) { + wl_link_up(wl); + if (wl_is_ibssmode(wl)) { + cfg80211_ibss_joined(ndev, (int8 *)&e->addr, + GFP_KERNEL); + WL_DBG(("joined in IBSS network\n")); + } else { + wl_bss_connect_done(wl, ndev, e, data); + 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); + } + + return err; +} + +static int32 +wl_notify_roaming_status(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + bool act; + int32 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 int32 +wl_dev_bufvar_set(struct net_device *dev, int8 *name, int8 *buf, int32 len) +{ + struct wl_priv *wl = ndev_to_wl(dev); + uint32 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 int32 +wl_dev_bufvar_get(struct net_device *dev, int8 *name, int8 *buf, + int32 buf_len) +{ + struct wl_priv *wl = ndev_to_wl(dev); + uint32 len; + int32 err = 0; + + len = bcm_mkiovar(name, NULL, 0, wl->ioctl_buf, WL_IOCTL_LEN_MAX); + BUG_ON(unlikely(!len)); + if (unlikely + ((err = + wl_dev_ioctl(dev, WLC_GET_VAR, (void *)wl->ioctl_buf, + WL_IOCTL_LEN_MAX)))) { + WL_ERR(("error (%d)\n", err)); + return err; + } + memcpy(buf, wl->ioctl_buf, buf_len); + + return err; +} + +static int32 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); + uint32 req_len; + uint32 resp_len; + int32 err = 0; + + if (unlikely(err = wl_dev_bufvar_get(ndev, "assoc_info", wl->extra_buf, + WL_ASSOC_INFO_MAX))) { + 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) { + if (unlikely + (err = + wl_dev_bufvar_get(ndev, "assoc_req_ies", wl->extra_buf, + WL_ASSOC_INFO_MAX))) { + 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) { + if (unlikely + (err = + wl_dev_bufvar_get(ndev, "assoc_resp_ies", wl->extra_buf, + WL_ASSOC_INFO_MAX))) { + 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 int32 wl_update_bss_info(struct wl_priv *wl) +{ + struct cfg80211_bss *bss; + struct wl_bss_info *bi; + struct wlc_ssid *ssid; + int32 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, (int8 *)&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")); + *(uint32 *) wl->extra_buf = htod32(WL_EXTRA_BUF_MAX); + if (unlikely + (err = + wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_BSS_INFO, + wl->extra_buf, WL_EXTRA_BUF_MAX))) { + 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; + } + if (unlikely((err = wl_inform_single_bss(wl, bi)))) + goto update_bss_info_out; + } else { + WL_DBG(("Found the AP in the list - " + "BSSID %02x:%02x:%02x:%02x:%02x:%02x\n", + bss->bssid[0], bss->bssid[1], bss->bssid[2], + bss->bssid[3], bss->bssid[4], bss->bssid[5])); + cfg80211_put_bss(bss); + } + +update_bss_info_out: + rtnl_unlock(); + return err; +} + +static int32 +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); + int32 err = 0; + + wl_get_assoc_ies(wl); + memcpy(&wl->bssid, &e->addr, ETHER_ADDR_LEN); + wl_update_bss_info(wl); + cfg80211_roamed(ndev, + (uint8 *)&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 int32 +wl_bss_connect_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); + int32 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, + (uint8 *)&wl->bssid, + conn_info->req_ie, + conn_info->req_ie_len, + conn_info->resp_ie, + conn_info->resp_ie_len, + WLAN_STATUS_SUCCESS, GFP_KERNEL); + WL_DBG(("Report connect result\n")); + } else { + cfg80211_roamed(ndev, + (uint8 *)&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 int32 +wl_notify_mic_status(struct wl_priv *wl, struct net_device *ndev, + const wl_event_msg_t *e, void *data) +{ + uint16 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, (uint8 *)&e->addr, key_type, -1, + NULL, GFP_KERNEL); + rtnl_unlock(); + + return 0; +} + +static int32 +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; + uint32 len = WL_SCAN_BUF_MAX; + int32 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(); + if (unlikely((err = wl_dev_ioctl(ndev, WLC_GET_CHANNEL, &channel_inform, + sizeof(channel_inform))))) { + 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); + if (unlikely + ((err = wl_dev_ioctl(ndev, WLC_SCAN_RESULTS, bss_list, len)))) { + 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); + + if ((err = wl_inform_bss(wl))) + goto scan_done_out; + +scan_done_out: + if (wl->scan_request) { + cfg80211_scan_done(wl->scan_request, FALSE); + wl->scan_request = NULL; + } + rtnl_unlock(); + return err; +} + +static void wl_init_conf(struct wl_conf *conf) +{ + conf->mode = (uint32)-1; + conf->frag_threshold = (uint32)-1; + conf->rts_threshold = (uint32)-1; + conf->retry_short = (uint32)-1; + conf->retry_long = (uint32)-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; +} + +static int32 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 int32 wl_create_event_handler(struct wl_priv *wl) +{ + sema_init(&wl->event_sync, 0); + init_completion(&wl->event_exit); + if (unlikely + (((wl->event_pid = kernel_thread(wl_event_handler, wl, 0)) < 0))) { + WL_ERR(("failed to create event thread\n")); + return -ENOMEM; + } + WL_DBG(("pid %d\n", wl->event_pid)); + return 0; +} + +static void wl_destroy_event_handler(struct wl_priv *wl) +{ + if (wl->event_pid >= 0) { + KILL_PROC(wl->event_pid, SIGTERM); + wait_for_completion(&wl->event_exit); + } +} + +static void wl_term_iscan(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl_to_iscan(wl); + + if (wl->iscan_on && iscan->pid >= 0) { + iscan->state = WL_ISCAN_STATE_IDLE; + KILL_PROC(iscan->pid, SIGTERM); + wait_for_completion(&iscan->exited); + iscan->pid = -1; + } +} + +static void wl_notify_iscan_complete(struct wl_iscan_ctrl *iscan, bool aborted) +{ + struct wl_priv *wl = iscan_to_wl(iscan); + + 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->scan_request = NULL; + } + wl->iscan_kickstart = FALSE; +} + +static int32 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 int32 +wl_get_iscan_results(struct wl_iscan_ctrl *iscan, uint32 *status, + struct wl_scan_results **bss_list) +{ + struct wl_iscan_results list; + struct wl_scan_results *results; + struct wl_iscan_results *list_buf; + int32 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); + if (unlikely((err = wl_dev_iovar_getbuf(iscan->dev, + "iscanresults", + &list, + WL_ISCAN_RESULTS_FIXED_SIZE, + iscan->scan_buf, + WL_ISCAN_BUF_MAX)))) { + 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 int32 wl_iscan_done(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl->iscan; + int32 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 int32 wl_iscan_pending(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl->iscan; + int32 err = 0; + + /* Reschedule the timer */ + mod_timer(&iscan->timer, jiffies + iscan->timer_ms * HZ / 1000); + iscan->timer_on = 1; + + return err; +} + +static int32 wl_iscan_inprogress(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl->iscan; + int32 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 int32 wl_iscan_aborted(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl->iscan; + int32 err = 0; + + iscan->state = WL_ISCAN_STATE_IDLE; + rtnl_lock(); + wl_notify_iscan_complete(iscan, TRUE); + rtnl_unlock(); + + return err; +} + +static int32 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; + uint32 status; + int err = 0; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + status = WL_SCAN_RESULTS_PARTIAL; + while (likely(!down_interruptible(&iscan->sync))) { + if (iscan->timer_on) { + del_timer_sync(&iscan->timer); + iscan->timer_on = 0; + } + rtnl_lock(); + if (unlikely + ((err = + wl_get_iscan_results(iscan, &status, &wl->bss_list)))) { + 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; + } + complete_and_exit(&iscan->exited, 0); + + return 0; +} + +static void wl_iscan_timer(ulong 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 int32 wl_invoke_iscan(struct wl_priv *wl) +{ + struct wl_iscan_ctrl *iscan = wl_to_iscan(wl); + int err = 0; + + if (wl->iscan_on && iscan->pid < 0) { + iscan->state = WL_ISCAN_STATE_IDLE; + sema_init(&iscan->sync, 0); + init_completion(&iscan->exited); + iscan->pid = kernel_thread(wl_iscan_thread, iscan, 0); + if (unlikely(iscan->pid < 0)) { + WL_ERR(("Could not create iscan thread\n")); + 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 int32 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 = (ulong) iscan; + iscan->timer.function = wl_iscan_timer; + sema_init(&iscan->sync, 0); + init_completion(&iscan->exited); + iscan->pid = kernel_thread(wl_iscan_thread, iscan, 0); + if (unlikely(iscan->pid < 0)) { + WL_ERR(("Could not create iscan thread\n")); + 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 int32 wl_init_priv(struct wl_priv *wl) +{ + struct wiphy *wiphy = wl_to_wiphy(wl); + int32 err = 0; + + wl->scan_request = NULL; + wl->pwr_save = !!(wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT); +#ifndef WL_ISCAN_DISABLED + wl->iscan_on = TRUE; /* iscan on & off switch. + we enable iscan per default */ +#else + wl->iscan_on = FALSE; +#endif /* WL_ISCAN_DISABLED */ +#ifndef WL_ROAM_DISABLED + wl->roam_on = TRUE; /* roam on & off switch. + we enable roam per default */ +#else + wl->roam_on = FALSE; +#endif /* WL_ROAM_DISABLED */ + + 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); + if (unlikely((err = wl_init_priv_mem(wl)))) + return err; + if (unlikely(wl_create_event_handler(wl))) + return -ENOMEM; + wl_init_eloop_handler(&wl->el); + mutex_init(&wl->usr_sync); + if (unlikely((err = wl_init_iscan(wl)))) + 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); +} + +int32 wl_cfg80211_attach(struct net_device *ndev, void *data) +{ + struct wireless_dev *wdev; + struct wl_priv *wl; + struct wl_iface *ci; + int32 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_sdio_func())); + wdev = wl_alloc_wdev(sizeof(struct wl_iface), &wl_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; + if (unlikely((err = wl_init_priv(wl)))) { + 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 int32 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, ¶m); + while (likely(!down_interruptible(&wl->event_sync))) { + if (unlikely(!(e = wl_deq_event(wl)))) { + 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); + } + complete_and_exit(&wl->event_exit, 0); +} + +void +wl_cfg80211_event(struct net_device *ndev, const wl_event_msg_t * e, void *data) +{ + uint32 event_type = ntoh32(e->event_type); + struct wl_priv *wl = ndev_to_wl(ndev); +#if (WL_DBG_LEVEL > 0) + int8 *estr = (event_type <= sizeof(wl_dbg_estr) / WL_DBG_ESTR_MAX - 1) ? + wl_dbg_estr[event_type] : (int8 *) "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 int32 +wl_enq_event(struct wl_priv *wl, uint32 event, const wl_event_msg_t *msg, + void *data) +{ + struct wl_event_q *e; + int32 err = 0; + + if (unlikely(!(e = kzalloc(sizeof(struct wl_event_q), GFP_KERNEL)))) { + 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; +} + +static struct sdio_func *wl_sdio_func(void) +{ + return cfg80211_sdio_func; +} + +static int32 wl_dongle_mode(struct net_device *ndev, int32 iftype) +{ + int32 infra = 0; + int32 ap = 0; + int32 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)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_INFRA, &infra, sizeof(infra))) + || unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_AP, &ap, sizeof(ap)))) { + WL_ERR(("WLC_SET_INFRA error (%d)\n", err)); + return err; + } + + return -EINPROGRESS; +} + +#ifndef EMBEDDED_PLATFORM +static int32 wl_dongle_country(struct net_device *ndev, uint8 ccode) +{ + + int32 err = 0; + + return err; +} + +static int32 wl_dongle_up(struct net_device *ndev, uint32 up) +{ + int32 err = 0; + + if (unlikely(err = wl_dev_ioctl(ndev, WLC_UP, &up, sizeof(up)))) { + WL_ERR(("WLC_UP error (%d)\n", err)); + } + return err; +} + +static int32 wl_dongle_power(struct net_device *ndev, uint32 power_mode) +{ + int32 err = 0; + + if (unlikely + (err = + wl_dev_ioctl(ndev, WLC_SET_PM, &power_mode, sizeof(power_mode)))) { + WL_ERR(("WLC_SET_PM error (%d)\n", err)); + } + return err; +} + +static int32 +wl_dongle_glom(struct net_device *ndev, uint32 glom, uint32 dongle_align) +{ + int8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + + '\0' + bitvec */ + int32 err = 0; + + /* Match Host and Dongle rx alignment */ + bcm_mkiovar("bus:txglomalign", (char *)&dongle_align, 4, iovbuf, + sizeof(iovbuf)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + 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)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + WL_ERR(("txglom error (%d)\n", err)); + goto dongle_glom_out; + } +dongle_glom_out: + return err; +} + +static int32 +wl_dongle_roam(struct net_device *ndev, uint32 roamvar, uint32 bcn_timeout) +{ + int8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + + '\0' + bitvec */ + int32 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)); + if (unlikely + (err = + wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + 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)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + WL_ERR(("roam_off error (%d)\n", err)); + goto dongle_rom_out; + } +dongle_rom_out: + return err; +} + +static int32 wl_dongle_eventmsg(struct net_device *ndev) +{ + + int8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + + '\0' + bitvec */ + int8 eventmask[WL_EVENTING_MASK_LEN]; + int32 err = 0; + + /* Setup event_msgs */ + bcm_mkiovar("event_msgs", eventmask, WL_EVENTING_MASK_LEN, iovbuf, + sizeof(iovbuf)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_GET_VAR, iovbuf, sizeof(iovbuf)))) { + 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)); + if (unlikely + (err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + WL_ERR(("Set event_msgs error (%d)\n", err)); + goto dongle_eventmsg_out; + } + +dongle_eventmsg_out: + return err; +} + +static int32 +wl_dongle_scantime(struct net_device *ndev, int32 scan_assoc_time, + int32 scan_unassoc_time) +{ + int32 err = 0; + + if ((err = + wl_dev_ioctl(ndev, WLC_SET_SCAN_CHANNEL_TIME, &scan_assoc_time, + sizeof(scan_assoc_time)))) { + 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; + } + if ((err = + wl_dev_ioctl(ndev, WLC_SET_SCAN_UNASSOC_TIME, &scan_unassoc_time, + sizeof(scan_unassoc_time)))) { + 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 int32 +wl_dongle_offload(struct net_device *ndev, int32 arpoe, int32 arp_ol) +{ + int8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + + '\0' + bitvec */ + int32 err = 0; + + /* Set ARP offload */ + bcm_mkiovar("arpoe", (char *)&arpoe, 4, iovbuf, sizeof(iovbuf)); + if ((err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + 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)); + if ((err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + 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 int32 wl_pattern_atoh(int8 *src, int8 *dst) +{ +#define strtoul(nptr, endptr, base) bcm_strtoul((nptr), (endptr), (base)) + 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] = (uint8) strtoul(num, NULL, 16); + src += 2; + } + return i; +} + +static int32 wl_dongle_filter(struct net_device *ndev, uint32 filter_mode) +{ + int8 iovbuf[WL_EVENTING_MASK_LEN + 12]; /* Room for "event_msgs" + + '\0' + bitvec */ + const int8 *str; + struct wl_pkt_filter pkt_filter; + struct wl_pkt_filter *pkt_filterp; + int32 buf_len; + int32 str_len; + uint32 mask_size; + uint32 pattern_size; + int8 buf[256]; + int32 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); + + if ((err = wl_dev_ioctl(ndev, WLC_SET_VAR, buf, buf_len))) { + 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)); + if ((err = wl_dev_ioctl(ndev, WLC_SET_VAR, iovbuf, sizeof(iovbuf)))) { + 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 */ + +int32 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; + int32 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 + if (unlikely((err = wl_dongle_up(ndev, 0)))) + goto default_conf_out; + if (unlikely((err = wl_dongle_country(ndev, 0)))) + goto default_conf_out; + if (unlikely((err = wl_dongle_power(ndev, PM_FAST)))) + goto default_conf_out; + if (unlikely((err = wl_dongle_glom(ndev, 0, DHD_SDALIGN)))) + goto default_conf_out; + if (unlikely((err = wl_dongle_roam(ndev, (wl->roam_on ? 0 : 1), 3)))) + goto default_conf_out; + if (unlikely((err = wl_dongle_eventmsg(ndev)))) + 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; + if (unlikely((err = wl_dongle_probecap(wl)))) + goto default_conf_out; + + /* -EINPROGRESS: Call commit handler */ + +default_conf_out: + if (need_lock) + rtnl_unlock(); + + wl->dongle_up = TRUE; + + return err; + +} + +static int32 wl_update_wiphybands(struct wl_priv *wl) +{ + struct wiphy *wiphy; + int32 phy_list; + int8 phy; + int32 err = 0; + + if (unlikely + (err = + wl_dev_ioctl(wl_to_ndev(wl), WLC_GET_PHYLIST, &phy_list, + sizeof(phy_list)))) { + 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 int32 __wl_cfg80211_up(struct wl_priv *wl) +{ + int32 err = 0; + + if (unlikely(err = wl_config_dongle(wl, FALSE))) + return err; + + wl_invoke_iscan(wl); + set_bit(WL_STATUS_READY, &wl->status); + return err; +} + +static int32 __wl_cfg80211_down(struct wl_priv *wl) +{ + int32 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->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); + + return err; +} + +int32 wl_cfg80211_up(void) +{ + struct wl_priv *wl; + int32 err = 0; + + wl = WL_PRIV_GET(); + mutex_lock(&wl->usr_sync); + err = __wl_cfg80211_up(wl); + mutex_unlock(&wl->usr_sync); + + return err; +} + +int32 wl_cfg80211_down(void) +{ + struct wl_priv *wl; + int32 err = 0; + + wl = WL_PRIV_GET(); + mutex_lock(&wl->usr_sync); + err = __wl_cfg80211_down(wl); + mutex_unlock(&wl->usr_sync); + + return err; +} + +static int32 wl_dongle_probecap(struct wl_priv *wl) +{ + int32 err = 0; + + if (unlikely((err = wl_update_wiphybands(wl)))) + return err; + + return err; +} + +static void *wl_read_prof(struct wl_priv *wl, int32 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 int32 +wl_update_prof(struct wl_priv *wl, const wl_event_msg_t *e, void *data, + int32 item) +{ + int32 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; + default: + WL_ERR(("unsupported item (%d)\n", item)); + err = -EOPNOTSUPP; + break; + } + + return err; +} + +void wl_cfg80211_dbg_level(uint32 level) +{ + 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 int32 wl_add_ie(struct wl_priv *wl, uint8 t, uint8 l, uint8 *v) +{ + struct wl_ie *ie = wl_to_ie(wl); + int32 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 int32 wl_mrg_ie(struct wl_priv *wl, uint8 *ie_stream, uint16 ie_size) +{ + struct wl_ie *ie = wl_to_ie(wl); + int32 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 int32 wl_cp_ie(struct wl_priv *wl, uint8 *dst, uint16 dst_size) +{ + struct wl_ie *ie = wl_to_ie(wl); + int32 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 uint32 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(uint32 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; +} + +int32 wl_cfg80211_read_fw(int8 *buf, uint32 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(int8 *file_name) +{ + struct wl_priv *wl; + const struct firmware *fw_entry = NULL; + int32 err = 0; + + WL_DBG(("file name : \"%s\"\n", file_name)); + wl = WL_PRIV_GET(); + + if (!test_bit(WL_FW_LOADING_DONE, &wl->fw->status)) { + if (unlikely + (err = + request_firmware(&wl->fw->fw_entry, file_name, + &wl_sdio_func()->dev))) { + 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 (%d), data (%p)\n", fw_entry->size, + fw_entry->data)); + } + } else if (!test_bit(WL_NVRAM_LOADING_DONE, &wl->fw->status)) { + if (unlikely + (err = + request_firmware(&wl->fw->fw_entry, file_name, + &wl_sdio_func()->dev))) { + 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 (%d), 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; +} + +int8 *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; +} + +int8 *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; +} diff --git a/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h new file mode 100644 index 000000000000..3eeb423ca8b5 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/wl_cfg80211.h @@ -0,0 +1,383 @@ +/* + * 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 +#include +#include +#include +#include +#include + +struct wl_conf; +struct wl_iface; +struct wl_priv; +struct wl_security; +struct wl_ibss; + +#if defined(IL_BIGENDIAN) +#include +#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 +}; + +/* 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 +}; + +/* dongle configuration */ +struct wl_conf { + uint32 mode; /* adhoc , infrastructure or ap */ + uint32 frag_threshold; + uint32 rts_threshold; + uint32 retry_short; + uint32 retry_long; + int32 tx_power; + struct ieee80211_channel channel; +}; + +/* cfg80211 main event loop */ +struct wl_event_loop { + int32(*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 { + uint16 band; + uint16 channel; + int16 rssi; + uint16 frame_len; + uint8 frame_buf[1]; +}; + +/* basic structure of scan request */ +struct wl_scan_req { + struct wlc_ssid ssid; +}; + +/* basic structure of information element */ +struct wl_ie { + uint16 offset; + uint8 buf[WL_TLV_INFO_MAX]; +}; + +/* event queue for cfg80211 main event */ +struct wl_event_q { + struct list_head eq_list; + uint32 etype; + wl_event_msg_t emsg; + int8 edata[1]; +}; + +/* security information with currently associated ap */ +struct wl_security { + uint32 wpa_versions; + uint32 auth_type; + uint32 cipher_pairwise; + uint32 cipher_group; + uint32 wpa_auth; +}; + +/* ibss information for currently joined ibss network */ +struct wl_ibss { + uint8 beacon_interval; /* in millisecond */ + uint8 atim; /* in millisecond */ + int8 join_only; + uint8 band; + uint8 channel; +}; + +/* dongle profile */ +struct wl_profile { + uint32 mode; + struct wlc_ssid ssid; + uint8 bssid[ETHER_ADDR_LEN]; + struct wl_security sec; + struct wl_ibss ibss; + int32 band; + bool active; +}; + +/* dongle iscan event loop */ +struct wl_iscan_eloop { + int32(*handler[WL_SCAN_ERSULTS_LAST]) (struct wl_priv *wl); +}; + +/* dongle iscan controller */ +struct wl_iscan_ctrl { + struct net_device *dev; + struct timer_list timer; + uint32 timer_ms; + uint32 timer_on; + int32 state; + int32 pid; + struct semaphore sync; + struct completion exited; + struct wl_iscan_eloop el; + void *data; + int8 ioctl_buf[WLC_IOCTL_SMLEN]; + int8 scan_buf[WL_ISCAN_BUF_MAX]; +}; + +/* association inform */ +struct wl_connect_info { + uint8 *req_ie; + int32 req_ie_len; + uint8 *resp_ie; + int32 resp_ie_len; +}; + +/* firmware /nvram downloading controller */ +struct wl_fw_ctrl { + const struct firmware *fw_entry; + ulong status; + uint32 ptr; + int8 fw_name[WL_FILE_NAME_MAX]; + int8 nvram_name[WL_FILE_NAME_MAX]; +}; + +/* assoc ie length */ +struct wl_assoc_ielen { + uint32 req_len; + uint32 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 completion event_exit; + 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 */ + int32 event_pid; /* pid of main event handler thread */ + ulong status; /* current dongle status */ + void *pub; + uint32 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 */ + uint8 *ioctl_buf; /* ioctl buffer */ + uint8 *extra_buf; /* maily to grab assoc information */ + uint8 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 *)((uintptr) 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 int32 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 int32 wl_cfg80211_up(void); /* dongle up */ +extern int32 wl_cfg80211_down(void); /* dongle down */ +extern void wl_cfg80211_dbg_level(uint32 level); /* set dongle + debugging level */ +extern void *wl_cfg80211_request_fw(int8 *file_name); /* request fw /nvram + downloading */ +extern int32 wl_cfg80211_read_fw(int8 *buf, uint32 size); /* read fw + image */ +extern void wl_cfg80211_release_fw(void); /* release fw */ +extern int8 *wl_cfg80211_get_fwname(void); /* get firmware name for + the dongle */ +extern int8 *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..118a7a1c45a2 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/wl_iw.c @@ -0,0 +1,3728 @@ +/* + * 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 + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +typedef void wlc_info_t; +typedef void wl_info_t; +typedef const struct si_pub si_t; +#include + +#include +#include +#include +#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 + +#define IW_WSEC_ENABLED(wsec) ((wsec) & (WEP_ENABLED | \ + TKIP_ENABLED | AES_ENABLED)) + +#include + +#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(uint32 event_type, uint32 status, + uint32 reason, char *stringBuf, uint buflen); + +uint wl_msg_level = WL_ERROR_VAL; + +#define MAX_WLIW_IOCTL_LEN 1024 + +#if defined(IL_BIGENDIAN) +#include +#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 = NULL; +static volatile uint g_scan_specified_ssid; +static wlc_ssid_t g_specific_ssid; + +static wlc_ssid_t g_ssid; + +#define DAEMONIZE(a) daemonize(a); \ + allow_signal(SIGKILL); \ + allow_signal(SIGTERM); + +#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; + uint32 timer_ms; + uint32 timer_on; + int iscan_state; + iscan_buf_t *list_hdr; + iscan_buf_t *list_cur; + + long sysioc_pid; + struct semaphore sysioc_sem; + struct completion sysioc_exited; + +#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 = NULL; +static void wl_iw_timerfunc(ulong 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, uint16 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)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) + return error; + + ssid.SSID_len = dtoh32(ssid.SSID_len); + + if (!ssid.SSID_len) + return 0; + + bzero(&bssid, sizeof(struct sockaddr)); + if ((error = dev_wlc_ioctl(dev, WLC_REASSOC, &bssid, ETHER_ADDR_LEN))) { + 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); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_CHANNEL, &chan, sizeof(chan)))) + 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)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + 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); + + if ((error = dev_wlc_ioctl(dev, WLC_SET_INFRA, &infra, sizeof(infra))) + || (error = dev_wlc_ioctl(dev, WLC_SET_AP, &ap, sizeof(ap)))) + 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)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_INFRA, &infra, sizeof(infra))) + || (error = dev_wlc_ioctl(dev, WLC_GET_AP, &ap, sizeof(ap)))) + 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_uint32_list_t *list; + wl_rateset_t rateset; + int8 *channels; + int error, i, k; + uint sf, ch; + + int phytype; + int bw_cap = 0, sgi_tx = 0, nmode = 0; + channel_info_t ci; + uint8 nrate_list2copy = 0; + uint16 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_uint32_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); + if ((error = + dev_wlc_ioctl(dev, WLC_GET_VALID_CHANNELS, channels, + (MAXCHANNEL + 1) * 4))) { + 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 + + if ((error = + dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, + sizeof(rateset)))) { + 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; + } + } + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PHYTYPE, &i, sizeof(i)))) { + 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; +#ifdef BCMWPA2 + range->enc_capa |= IW_ENC_CAPA_WPA2; +#endif + + 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); +#ifdef BCMWPA2 + IW_EVENT_CAPA_SET(range->event_capa, IWEVPMKIDCAND); +#endif +#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(ARRAYSIZE(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); + + if ((error = + dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params, + join_params_size))) { + WL_ERROR(("%s Invalid ioctl data=%d\n", __func__, error)); + } + + if (g_ssid.SSID_len) { + WL_TRACE(("%s: join SSID=%s BSSID=" MACSTR " ch=%d\n", + __func__, g_ssid.SSID, + MAC2STR((u8 *) 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); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, buflen))) { + 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 *) ((uintptr) bi + + dtoh32(bi->length)) : list-> + bss_info; + ASSERT(((uintptr) bi + dtoh32(bi->length)) <= + ((uintptr) 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_pid < 0)) { + 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 *) ((uintptr) bi + + dtoh32(bi->length)) : + list->bss_info; + ASSERT(((uintptr) bi + dtoh32(bi->length)) <= + ((uintptr) 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(¶ms->bssid, ðer_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(¶ms->ssid, ssid, sizeof(wlc_ssid_t)); + + return err; +} + +static int wl_iw_iscan(iscan_info_t *iscan, wlc_ssid_t *ssid, uint16 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(ulong 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 uint32 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; + uint32 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) +{ + uint32 status; + iscan_info_t *iscan = (iscan_info_t *) data; + static bool iscan_pass_abort = FALSE; + DAEMONIZE("iscan_sysioc"); + + status = WL_SCAN_RESULTS_PARTIAL; + while (down_interruptible(&iscan->sysioc_sem) == 0) { + + 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; + } + complete_and_exit(&iscan->sysioc_exited, 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(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 */ + if ((error = + dev_wlc_ioctl(dev, WLC_SCAN, &g_specific_ssid, + sizeof(g_specific_ssid)))) { + 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_pid < 0)) + 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(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(uint8 **wpaie, uint8 **tlvs, int *tlvs_len) +{ + + uint8 *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(uint8 **wpsie, uint8 **tlvs, int *tlvs_len) +{ + + uint8 *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; + uint8 *ptr = ((uint8 *) bi) + sizeof(wl_bss_info_t); + int ptr_len = bi->ie_length; + +#ifdef BCMWPA2 + if ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_RSN_ID))) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie->len + 2; + event = + IWE_STREAM_ADD_POINT(info, event, end, &iwe, + (char *)ie); + } + ptr = ((uint8 *) bi) + sizeof(wl_bss_info_t); +#endif + + while ((ie = bcm_parse_tlvs(ptr, ptr_len, DOT11_MNG_WPA_ID))) { + if (ie_is_wps_ie(((uint8 **)&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 = ((uint8 *) 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(((uint8 **)&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 *) ((uintptr) 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) <= (uintptr) 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; + } + } + } + + if ((ret = (event - extra)) < 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; + } + + if ((error = dev_wlc_ioctl(dev, WLC_GET_CHANNEL, &ci, sizeof(ci)))) + 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); + if ((error = dev_wlc_ioctl(dev, WLC_SCAN_RESULTS, list, len))) { + 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; + uint32 counter = 0; + uint8 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_pid < 0)) { + WL_ERROR(("%ssysioc_pid\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 *) ((uintptr) bi + + dtoh32(bi->length)) : + list->bss_info; + ASSERT(((uintptr) bi + dtoh32(bi->length)) <= + ((uintptr) 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(sizeof(g_ssid.SSID), dwrq->length); +#else + g_ssid.SSID_len = MIN(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, ðer_bcast, ETHER_ADDR_LEN); + + wl_iw_ch_to_chanspec(g_wl_iw_params.target_channel, &join_params, + &join_params_size); + + if ((error = + dev_wlc_ioctl(dev, WLC_SET_SSID, &join_params, join_params_size))) + 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; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_SSID, &ssid, sizeof(ssid)))) { + 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)); + + if ((error = + dev_wlc_ioctl(dev, WLC_GET_CURR_RATESET, &rateset, + sizeof(rateset)))) + 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); + + if ((error = + dev_wlc_ioctl(dev, WLC_SET_RATESET, &rateset, + sizeof(rateset)))) + 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)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_RATE, &rate, sizeof(rate)))) + 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; + + if ((error = dev_wlc_intvar_set(dev, "rtsthresh", rts))) + 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)); + + if ((error = dev_wlc_intvar_get(dev, "rtsthresh", &rts))) + 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; + + if ((error = dev_wlc_intvar_set(dev, "fragthresh", frag))) + 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)); + + if ((error = dev_wlc_intvar_get(dev, "fragthresh", &fragthreshold))) + 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; + uint16 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); + if ((error = + dev_wlc_ioctl(dev, WLC_SET_RADIO, &disable, sizeof(disable)))) + 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 = (uint16) 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; + uint8 result; + + WL_TRACE(("%s: SIOCGIWTXPOW\n", dev->name)); + + if ((error = + dev_wlc_ioctl(dev, WLC_GET_RADIO, &disable, sizeof(disable))) + || (error = dev_wlc_intvar_get(dev, "qtxpower", &txpwrdbm))) + return error; + + disable = dtoh32(disable); + result = (uint8) (txpwrdbm & ~WL_TXPWR_OVERRIDE); + vwrq->value = (int32) 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); + if ((error = + dev_wlc_ioctl(dev, WLC_SET_LRL, &lrl, + sizeof(lrl)))) + 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); + if ((error = + dev_wlc_ioctl(dev, WLC_SET_SRL, &srl, + sizeof(srl)))) + 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; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_LRL, &lrl, sizeof(lrl))) || + (error = dev_wlc_ioctl(dev, WLC_GET_SRL, &srl, sizeof(srl)))) + 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); + if ((error = + dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, + sizeof(val)))) + 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); + if ((error = + dev_wlc_ioctl(dev, WLC_SET_KEY_PRIMARY, &val, + sizeof(val)))) + 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); + if ((error = + dev_wlc_ioctl(dev, WLC_SET_KEY, &key, sizeof(key)))) + return error; + } + + val = (dwrq->flags & IW_ENCODE_DISABLED) ? 0 : WEP_ENABLED; + + if ((error = dev_wlc_intvar_get(dev, "wsec", &wsec))) + return error; + + wsec &= ~(WEP_ENABLED); + wsec |= val; + + if ((error = dev_wlc_intvar_set(dev, "wsec", wsec))) + return error; + + val = (dwrq->flags & IW_ENCODE_RESTRICTED) ? 1 : 0; + val = htod32(val); + if ((error = dev_wlc_ioctl(dev, WLC_SET_AUTH, &val, sizeof(val)))) + 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; + if ((error = + dev_wlc_ioctl(dev, WLC_GET_KEY_PRIMARY, &val, + sizeof(val)))) + 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; + + if ((error = dev_wlc_ioctl(dev, WLC_GET_WSEC, &wsec, sizeof(wsec))) || + (error = dev_wlc_ioctl(dev, WLC_GET_AUTH, &auth, sizeof(auth)))) + return error; + + swap_key_to_BE(&key); + + wsec = dtoh32(wsec); + auth = dtoh32(auth); + dwrq->length = MIN(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); + if ((error = dev_wlc_ioctl(dev, WLC_SET_PM, &pm, sizeof(pm)))) + 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)); + + if ((error = dev_wlc_ioctl(dev, WLC_GET_PM, &pm, sizeof(pm)))) + 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) { + uint8 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) { + uchar *ivptr; + ivptr = (uchar *) 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 +#ifdef BCMWPA2 +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; + char eabuf[ETHER_ADDR_STR_LEN]; + + WL_WSEC(("%s: SIOCSIWPMKSA\n", dev->name)); + + CHECK_EXTRA_FOR_NULL(extra); + + iwpmksa = (struct iw_pmksa *)extra; + bzero((char *)eabuf, ETHER_ADDR_STR_LEN); + + 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: %s = ", + bcm_ether_ntoa(&pmkidptr->pmkid[0].BSSID, eabuf))); + 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: %s = ", + bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[k]. + BSSID, eabuf))); + 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]: %s = ", i, + bcm_ether_ntoa(&pmkid_list.pmkids.pmkid[i].BSSID, + eabuf))); + 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 /* BCMWPA2 */ +#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; +#ifdef BCMWPA2 + else if (paramval & IW_AUTH_WPA_VERSION_WPA2) + val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED; +#endif + WL_INFORM(("%s: %d: setting wpa_auth to 0x%0x\n", __func__, + __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) + 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__)); + if ((error = + dev_wlc_intvar_set(dev, "is_WPS_enrollee", + TRUE))) { + WL_WSEC(("Failed to set is_WPS_enrollee\n")); + return error; + } + } else if (val) { + if ((error = + dev_wlc_intvar_set(dev, "is_WPS_enrollee", + FALSE))) { + WL_WSEC(("Failed to clear is_WPS_enrollee\n")); + return error; + } + } + + if ((error = dev_wlc_intvar_set(dev, "wsec", val))) + return error; + + break; + + case IW_AUTH_KEY_MGMT: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + 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; + } +#ifdef BCMWPA2 + 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; + } +#endif + WL_INFORM(("%s: %d: setting wpa_auth to %d\n", __func__, + __LINE__, val)); + if ((error = dev_wlc_intvar_set(dev, "wpa_auth", val))) + return error; + + break; + case IW_AUTH_TKIP_COUNTERMEASURES: + dev_wlc_bufvar_set(dev, "tkip_countermeasures", + (char *)¶mval, 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))) + return error; + break; + + case IW_AUTH_WPA_ENABLED: + if (paramval == 0) { + iw->pwsec = 0; + iw->gwsec = 0; + if ((error = dev_wlc_intvar_get(dev, "wsec", &val))) + 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 *)¶mval, 1); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + dev_wlc_bufvar_set(dev, "rx_unencrypted_eapol", + (char *)¶mval, 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; + if ((error = + dev_wlc_intvar_set(dev, "is_WPS_enrollee", + FALSE))) { + WL_WSEC(("Failed to clear iovar " + "is_WPS_enrollee\n")); + return error; + } + } else { + iw->privacy_invoked = TRUE; + if ((error = + dev_wlc_intvar_get(dev, "wsec", &wsec))) + return error; + + if (!(IW_WSEC_ENABLED(wsec))) { + if ((error = + dev_wlc_intvar_set(dev, + "is_WPS_enrollee", TRUE))) { + WL_WSEC(("Failed to set iovar " + "is_WPS_enrollee\n")); + return error; + } + } else { + if ((error = + dev_wlc_intvar_set(dev, + "is_WPS_enrollee", FALSE))) { + WL_WSEC(("Failed to clear " + "is_WPS_enrollee\n")); + return error; + } + } + } + break; + } +#endif /* WIRELESS_EXT > 17 */ + default: + break; + } + return 0; +} + +#ifdef BCMWPA2 +#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK) || ((_val) & WPA2_AUTH_PSK)) +#else +#define VAL_PSK(_val) (((_val) & WPA_AUTH_PSK)) +#endif + +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: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + 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; +#ifdef BCMWPA2 + else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) + paramval = IW_AUTH_WPA_VERSION_WPA2; +#endif + 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: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + 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 *)¶mval, 1); + break; + + case IW_AUTH_DROP_UNENCRYPTED: + dev_wlc_bufvar_get(dev, "wsec_restrict", (char *)¶mval, 1); + break; + + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + dev_wlc_bufvar_get(dev, "rx_unencrypted_eapol", + (char *)¶mval, 1); + break; + + case IW_AUTH_80211_AUTH_ALG: + if ((error = dev_wlc_intvar_get(dev, "auth", &val))) + return error; + if (!val) + paramval = IW_AUTH_ALG_OPEN_SYSTEM; + else + paramval = IW_AUTH_ALG_SHARED_KEY; + break; + case IW_AUTH_WPA_ENABLED: + if ((error = dev_wlc_intvar_get(dev, "wpa_auth", &val))) + 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, +#ifdef BCMWPA2 + (iw_handler) wl_iw_set_pmksa, +#endif +#endif /* WIRELESS_EXT > 17 */ +}; + +#if WIRELESS_EXT > 12 + +const struct iw_handler_def wl_iw_handler_def = { + .num_standard = ARRAYSIZE(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) >= ARRAYSIZE(wl_iw_handler) + || !(handler = wl_iw_handler[IW_IOCTL_IDX(cmd)])) { + 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; + } + if (!(extra = kmalloc(max_tokens * token_size, GFP_KERNEL))) + 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(uint32 event_type, uint32 status, uint32 reason, + char *stringBuf, uint buflen) +{ + typedef struct conn_fail_event_map_t { + uint32 inEvent; + uint32 inStatus; + uint32 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) +{ + uint32 event = ntoh32(e->event_type); + uint32 status = ntoh32(e->status); + uint32 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; + uint32 event_type = ntoh32(e->event_type); + uint16 flags = ntoh16(e->flags); + uint32 datalen = ntoh32(e->datalen); + uint32 status = ntoh32(e->status); + wl_iw_t *iw; + uint32 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; + } +#ifdef BCMWPA2 + 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((uint8 *) & + 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 /* BCMWPA2 */ +#endif /* WIRELESS_EXT > 17 */ + + case WLC_E_SCAN_COMPLETE: +#if defined(WL_IW_USE_ISCAN) + if ((g_iscan) && (g_iscan->sysioc_pid > 0) && + (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; + if ((res = + dev_wlc_ioctl(dev, WLC_GET_PHY_NOISE, &phy_noise, + sizeof(phy_noise)))) + 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)); + if ((res = + dev_wlc_ioctl(dev, WLC_GET_RSSI, &scb_val, sizeof(scb_val_t)))) + 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(uint16)) + + 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_pid = -1; + + g_iscan = iscan; + iscan->dev = dev; + iscan->iscan_state = ISCAN_STATE_IDLE; + + iscan->timer_ms = 3000; + init_timer(&iscan->timer); + iscan->timer.data = (ulong) iscan; + iscan->timer.function = wl_iw_timerfunc; + + sema_init(&iscan->sysioc_sem, 0); + init_completion(&iscan->sysioc_exited); + iscan->sysioc_pid = kernel_thread(_iscan_sysioc_thread, iscan, 0); + if (iscan->sysioc_pid < 0) + 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_pid >= 0) { + KILL_PROC(iscan->sysioc_pid, SIGTERM); + wait_for_completion(&iscan->sysioc_exited); + } + 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..d92a8d0849a2 --- /dev/null +++ b/drivers/staging/brcm80211/brcmfmac/wl_iw.h @@ -0,0 +1,154 @@ +/* + * 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 + +#include +#include +#include + +#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" + +#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +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 isprint(c) bcm_isprint(c) +#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; + uint32 pwsec; + uint32 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 +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, + uchar 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, uchar 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_ */ diff --git a/drivers/staging/brcm80211/include/bcmcdc.h b/drivers/staging/brcm80211/include/bcmcdc.h new file mode 100644 index 000000000000..d0b05c64b4d9 --- /dev/null +++ b/drivers/staging/brcm80211/include/bcmcdc.h @@ -0,0 +1,98 @@ +/* + * 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 + +typedef struct cdc_ioctl { + uint32 cmd; /* ioctl command value */ + uint32 len; /* lower 16: output buflen; upper 16: + input buflen (excludes header) */ + uint32 flags; /* flag defns given below */ + uint32 status; /* status code returned from the device */ +} cdc_ioctl_t; + +/* Max valid buffer size that can be sent to the dongle */ +#define CDC_MAX_MSG_SIZE ETHER_MAX_LEN + +/* len field is divided into input and output buffer lengths */ +#define CDCL_IOC_OUTLEN_MASK 0x0000FFFF /* maximum or expected + response length, */ + /* excluding IOCTL header */ +#define CDCL_IOC_OUTLEN_SHIFT 0 +#define CDCL_IOC_INLEN_MASK 0xFFFF0000 /* input buffer length, + excluding IOCTL header */ +#define CDCL_IOC_INLEN_SHIFT 16 + +/* CDC flag definitions */ +#define CDCF_IOC_ERROR 0x01 /* 0=success, 1=ioctl cmd failed */ +#define CDCF_IOC_SET 0x02 /* 0=get, 1=set cmd */ +#define CDCF_IOC_IF_MASK 0xF000 /* I/F index */ +#define CDCF_IOC_IF_SHIFT 12 +#define CDCF_IOC_ID_MASK 0xFFFF0000 /* used to uniquely id an ioctl + req/resp pairing */ +#define CDCF_IOC_ID_SHIFT 16 /* # of bits of shift for ID Mask */ + +#define CDC_IOC_IF_IDX(flags) \ + (((flags) & CDCF_IOC_IF_MASK) >> CDCF_IOC_IF_SHIFT) +#define CDC_IOC_ID(flags) \ + (((flags) & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT) + +#define CDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags) & CDCF_IOC_IF_MASK) >> CDCF_IOC_IF_SHIFT)) +#define CDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags = (((hdr)->flags & ~CDCF_IOC_IF_MASK) | \ + ((idx) << CDCF_IOC_IF_SHIFT))) + +/* + * BDC header + * + * The BDC header is used on data packets to convey priority across USB. + */ + +#define BDC_HEADER_LEN 4 + +#define BDC_PROTO_VER 1 /* Protocol version */ + +#define BDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */ +#define BDC_FLAG_VER_SHIFT 4 /* Protocol version shift */ + +#define BDC_FLAG__UNUSED 0x03 /* Unassigned */ +#define BDC_FLAG_SUM_GOOD 0x04 /* Dongle has verified good + RX checksums */ +#define BDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */ + +#define BDC_PRIORITY_MASK 0x7 + +#define BDC_FLAG2_FC_FLAG 0x10 /* flag to indicate if pkt contains */ + /* FLOW CONTROL info only */ +#define BDC_PRIORITY_FC_SHIFT 4 /* flow control info shift */ + +#define BDC_FLAG2_IF_MASK 0x0f /* APSTA: interface on which the + packet was received */ +#define BDC_FLAG2_IF_SHIFT 0 + +#define BDC_GET_IF_IDX(hdr) \ + ((int)((((hdr)->flags2) & BDC_FLAG2_IF_MASK) >> BDC_FLAG2_IF_SHIFT)) +#define BDC_SET_IF_IDX(hdr, idx) \ + ((hdr)->flags2 = (((hdr)->flags2 & ~BDC_FLAG2_IF_MASK) | \ + ((idx) << BDC_FLAG2_IF_SHIFT))) + +struct bdc_header { + uint8 flags; /* Flags */ + uint8 priority; /* 802.1d Priority 0:2 bits, 4:7 flow + control info for usb */ + uint8 flags2; + uint8 rssi; +}; diff --git a/drivers/staging/brcm80211/include/bcmdefs.h b/drivers/staging/brcm80211/include/bcmdefs.h index caa96b1823a1..e807d462598f 100644 --- a/drivers/staging/brcm80211/include/bcmdefs.h +++ b/drivers/staging/brcm80211/include/bcmdefs.h @@ -17,6 +17,13 @@ #ifndef _bcmdefs_h_ #define _bcmdefs_h_ +#define SI_BUS 0 +#define PCI_BUS 1 +#define PCMCIA_BUS 2 +#define SDIO_BUS 3 +#define JTAG_BUS 4 +#define USB_BUS 5 +#define SPI_BUS 6 /* * One doesn't need to include this file explicitly, gets included automatically if * typedefs.h is included. diff --git a/drivers/staging/brcm80211/include/bcmdevs.h b/drivers/staging/brcm80211/include/bcmdevs.h index 7d3a97a3fec1..075883a93529 100644 --- a/drivers/staging/brcm80211/include/bcmdevs.h +++ b/drivers/staging/brcm80211/include/bcmdevs.h @@ -24,9 +24,13 @@ #define BCM_DNGL_VID 0x0a5c #define BCM_DNGL_BDC_PID 0x0bdc +#define BCM4325_D11DUAL_ID 0x431b +#define BCM4325_D11G_ID 0x431c +#define BCM4325_D11A_ID 0x431d #define BCM4329_D11N_ID 0x432e /* 4329 802.11n dualband device */ #define BCM4329_D11N2G_ID 0x432f /* 4329 802.11n 2.4G device */ #define BCM4329_D11N5G_ID 0x4330 /* 4329 802.11n 5G device */ +#define BCM4329_D11NDUAL_ID 0x432e #define BCM4319_D11N_ID 0x4337 /* 4319 802.11n dualband device */ #define BCM4319_D11N2G_ID 0x4338 /* 4319 802.11n 2.4G device */ @@ -62,6 +66,7 @@ #define BCM43236_CHIP_ID 43236 /* 43236 chipcommon chipid */ #define BCM43238_CHIP_ID 43238 /* 43238 chipcommon chipid */ #define BCM4329_CHIP_ID 0x4329 /* 4329 chipcommon chipid */ +#define BCM4325_CHIP_ID 0x4325 /* 4325 chipcommon chipid */ #define BCM4331_CHIP_ID 0x4331 /* 4331 chipcommon chipid */ #define BCM4336_CHIP_ID 0x4336 /* 4336 chipcommon chipid */ #define BCM4330_CHIP_ID 0x4330 /* 4330 chipcommon chipid */ diff --git a/drivers/staging/brcm80211/include/bcmsdh_sdmmc.h b/drivers/staging/brcm80211/include/bcmsdh_sdmmc.h index e32c909d8b47..47eb2657377c 100644 --- a/drivers/staging/brcm80211/include/bcmsdh_sdmmc.h +++ b/drivers/staging/brcm80211/include/bcmsdh_sdmmc.h @@ -39,6 +39,8 @@ extern void sdioh_sdmmc_osfree(sdioh_info_t *sd); #define BLOCK_SIZE_64 64 #define BLOCK_SIZE_512 512 +#define BLOCK_SIZE_4318 64 +#define BLOCK_SIZE_4328 512 /* internal return code */ #define SUCCESS 0 diff --git a/drivers/staging/brcm80211/include/bcmutils.h b/drivers/staging/brcm80211/include/bcmutils.h index 4aa380016c7f..ddd4df286bad 100644 --- a/drivers/staging/brcm80211/include/bcmutils.h +++ b/drivers/staging/brcm80211/include/bcmutils.h @@ -21,6 +21,36 @@ extern "C" { #endif +#ifdef BRCM_FULLMAC +/* ctype replacement */ +#define _BCM_U 0x01 /* upper */ +#define _BCM_L 0x02 /* lower */ +#define _BCM_D 0x04 /* digit */ +#define _BCM_C 0x08 /* cntrl */ +#define _BCM_P 0x10 /* punct */ +#define _BCM_S 0x20 /* white space (space/lf/tab) */ +#define _BCM_X 0x40 /* hex digit */ +#define _BCM_SP 0x80 /* hard space (0x20) */ + + extern const unsigned char bcm_ctype[]; +#define bcm_ismask(x) (bcm_ctype[(int)(unsigned char)(x)]) + +#define bcm_isalnum(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_isalpha(c) ((bcm_ismask(c)&(_BCM_U|_BCM_L)) != 0) +#define bcm_iscntrl(c) ((bcm_ismask(c)&(_BCM_C)) != 0) +#define bcm_isdigit(c) ((bcm_ismask(c)&(_BCM_D)) != 0) +#define bcm_isgraph(c) ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D)) != 0) +#define bcm_islower(c) ((bcm_ismask(c)&(_BCM_L)) != 0) +#define bcm_isprint(c) \ + ((bcm_ismask(c)&(_BCM_P|_BCM_U|_BCM_L|_BCM_D|_BCM_SP)) != 0) +#define bcm_ispunct(c) ((bcm_ismask(c)&(_BCM_P)) != 0) +#define bcm_isspace(c) ((bcm_ismask(c)&(_BCM_S)) != 0) +#define bcm_isupper(c) ((bcm_ismask(c)&(_BCM_U)) != 0) +#define bcm_isxdigit(c) ((bcm_ismask(c)&(_BCM_D|_BCM_X)) != 0) +#define bcm_tolower(c) (bcm_isupper((c)) ? ((c) + 'a' - 'A') : (c)) +#define bcm_toupper(c) (bcm_islower((c)) ? ((c) + 'A' - 'a') : (c)) +#endif /* BRCM_FULLMAC */ + /* Buffer structure for collecting string-formatted data * using bcm_bprintf() API. * Use bcm_binit() to initialize before use @@ -114,8 +144,13 @@ extern "C" { extern void *pktq_pdeq(struct pktq *pq, int prec); extern void *pktq_pdeq_tail(struct pktq *pq, int prec); /* Empty the queue at particular precedence level */ +#ifdef BRCM_FULLMAC + extern void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, + bool dir); +#else extern void pktq_pflush(osl_t *osh, struct pktq *pq, int prec, - bool dir, ifpkt_cb_t fn, int arg); + bool dir, ifpkt_cb_t fn, int arg); +#endif /* Remove a specified packet from its queue */ extern bool pktq_pdel(struct pktq *pq, void *p, int prec); @@ -145,8 +180,12 @@ extern "C" { extern void *pktq_deq_tail(struct pktq *pq, int *prec_out); extern void *pktq_peek(struct pktq *pq, int *prec_out); extern void *pktq_peek_tail(struct pktq *pq, int *prec_out); +#ifdef BRCM_FULLMAC + extern void pktq_flush(osl_t *osh, struct pktq *pq, bool dir); +#else extern void pktq_flush(osl_t *osh, struct pktq *pq, bool dir, - ifpkt_cb_t fn, int arg); + ifpkt_cb_t fn, int arg); +#endif /* externs */ /* packet */ @@ -165,6 +204,20 @@ extern "C" { #define PKTPRIO_UPD 0x400 /* DSCP used to update VLAN prio */ #define PKTPRIO_DSCP 0x800 /* DSCP prio found */ +#ifdef BRCM_FULLMAC +/* string */ + extern int BCMROMFN(bcm_atoi) (char *s); + extern ulong BCMROMFN(bcm_strtoul) (char *cp, char **endp, uint base); + extern char *BCMROMFN(bcmstrstr) (char *haystack, char *needle); + extern char *BCMROMFN(bcmstrcat) (char *dest, const char *src); + extern char *BCMROMFN(bcmstrncat) (char *dest, const char *src, + uint size); + extern ulong wchar2ascii(char *abuf, ushort *wbuf, ushort wbuflen, + ulong abuflen); + char *bcmstrtok(char **string, const char *delimiters, char *tokdelim); + int bcmstricmp(const char *s1, const char *s2); + int bcmstrnicmp(const char *s1, const char *s2, int cnt); +#endif /* ethernet address */ extern char *bcm_ether_ntoa(const struct ether_addr *ea, char *buf); extern int BCMROMFN(bcm_ether_atoe) (char *p, struct ether_addr *ea); diff --git a/drivers/staging/brcm80211/include/dhdioctl.h b/drivers/staging/brcm80211/include/dhdioctl.h new file mode 100644 index 000000000000..78fd02de1376 --- /dev/null +++ b/drivers/staging/brcm80211/include/dhdioctl.h @@ -0,0 +1,109 @@ +/* + * 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 _dhdioctl_h_ +#define _dhdioctl_h_ + +#include + +/* require default structure packing */ +#define BWL_DEFAULT_PACKING +#include + +/* Linux network driver ioctl encoding */ +typedef struct dhd_ioctl { + uint cmd; /* common ioctl definition */ + void *buf; /* pointer to user buffer */ + uint len; /* length of user buffer */ + bool set; /* get or set request (optional) */ + uint used; /* bytes read or written (optional) */ + uint needed; /* bytes needed (optional) */ + uint driver; /* to identify target driver */ +} dhd_ioctl_t; + +/* per-driver magic numbers */ +#define DHD_IOCTL_MAGIC 0x00444944 + +/* bump this number if you change the ioctl interface */ +#define DHD_IOCTL_VERSION 1 + +#define DHD_IOCTL_MAXLEN 8192 /* max length ioctl buffer required */ +#define DHD_IOCTL_SMLEN 256 /* "small" length ioctl buffer required */ + +/* common ioctl definitions */ +#define DHD_GET_MAGIC 0 +#define DHD_GET_VERSION 1 +#define DHD_GET_VAR 2 +#define DHD_SET_VAR 3 + +/* message levels */ +#define DHD_ERROR_VAL 0x0001 +#define DHD_TRACE_VAL 0x0002 +#define DHD_INFO_VAL 0x0004 +#define DHD_DATA_VAL 0x0008 +#define DHD_CTL_VAL 0x0010 +#define DHD_TIMER_VAL 0x0020 +#define DHD_HDRS_VAL 0x0040 +#define DHD_BYTES_VAL 0x0080 +#define DHD_INTR_VAL 0x0100 +#define DHD_LOG_VAL 0x0200 +#define DHD_GLOM_VAL 0x0400 +#define DHD_EVENT_VAL 0x0800 +#define DHD_BTA_VAL 0x1000 +#define DHD_ISCAN_VAL 0x2000 + +#ifdef SDTEST +/* For pktgen iovar */ +typedef struct dhd_pktgen { + uint version; /* To allow structure change tracking */ + uint freq; /* Max ticks between tx/rx attempts */ + uint count; /* Test packets to send/rcv each attempt */ + uint print; /* Print counts every attempts */ + uint total; /* Total packets (or bursts) */ + uint minlen; /* Minimum length of packets to send */ + uint maxlen; /* Maximum length of packets to send */ + uint numsent; /* Count of test packets sent */ + uint numrcvd; /* Count of test packets received */ + uint numfail; /* Count of test send failures */ + uint mode; /* Test mode (type of test packets) */ + uint stop; /* Stop after this many tx failures */ +} dhd_pktgen_t; + +/* Version in case structure changes */ +#define DHD_PKTGEN_VERSION 2 + +/* Type of test packets to use */ +#define DHD_PKTGEN_ECHO 1 /* Send echo requests */ +#define DHD_PKTGEN_SEND 2 /* Send discard packets */ +#define DHD_PKTGEN_RXBURST 3 /* Request dongle send N packets */ +#define DHD_PKTGEN_RECV 4 /* Continuous rx from continuous + tx dongle */ +#endif /* SDTEST */ + +/* Enter idle immediately (no timeout) */ +#define DHD_IDLE_IMMEDIATE (-1) + +/* Values for idleclock iovar: other values are the sd_divisor to use + when idle */ +#define DHD_IDLE_ACTIVE 0 /* Do not request any SD clock change + when idle */ +#define DHD_IDLE_STOP (-1) /* Request SD clock be stopped + (and use SD1 mode) */ + +/* require default structure packing */ +#include + +#endif /* _dhdioctl_h_ */ diff --git a/drivers/staging/brcm80211/include/hndrte_armtrap.h b/drivers/staging/brcm80211/include/hndrte_armtrap.h new file mode 100644 index 000000000000..5d00dabebb30 --- /dev/null +++ b/drivers/staging/brcm80211/include/hndrte_armtrap.h @@ -0,0 +1,77 @@ +/* + * 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 _hndrte_armtrap_h +#define _hndrte_armtrap_h + +/* ARM trap handling */ + +/* Trap types defined by ARM (see arminc.h) */ + +/* Trap locations in lo memory */ +#define TRAP_STRIDE 4 +#define FIRST_TRAP TR_RST +#define LAST_TRAP (TR_FIQ * TRAP_STRIDE) + +#if defined(__ARM_ARCH_4T__) +#define MAX_TRAP_TYPE (TR_FIQ + 1) +#elif defined(__ARM_ARCH_7M__) +#define MAX_TRAP_TYPE (TR_ISR + ARMCM3_NUMINTS) +#endif /* __ARM_ARCH_7M__ */ + +/* The trap structure is defined here as offsets for assembly */ +#define TR_TYPE 0x00 +#define TR_EPC 0x04 +#define TR_CPSR 0x08 +#define TR_SPSR 0x0c +#define TR_REGS 0x10 +#define TR_REG(n) (TR_REGS + (n) * 4) +#define TR_SP TR_REG(13) +#define TR_LR TR_REG(14) +#define TR_PC TR_REG(15) + +#define TRAP_T_SIZE 80 + +#ifndef _LANGUAGE_ASSEMBLY + +#include + +typedef struct _trap_struct { + uint32 type; + uint32 epc; + uint32 cpsr; + uint32 spsr; + uint32 r0; + uint32 r1; + uint32 r2; + uint32 r3; + uint32 r4; + uint32 r5; + uint32 r6; + uint32 r7; + uint32 r8; + uint32 r9; + uint32 r10; + uint32 r11; + uint32 r12; + uint32 r13; + uint32 r14; + uint32 pc; +} trap_t; + +#endif /* !_LANGUAGE_ASSEMBLY */ + +#endif /* _hndrte_armtrap_h */ diff --git a/drivers/staging/brcm80211/include/hndrte_cons.h b/drivers/staging/brcm80211/include/hndrte_cons.h new file mode 100644 index 000000000000..74a7f27d920f --- /dev/null +++ b/drivers/staging/brcm80211/include/hndrte_cons.h @@ -0,0 +1,59 @@ +/* + * 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 + +#define CBUF_LEN (128) + +#define LOG_BUF_LEN 1024 + +typedef struct { + uint32 buf; /* Can't be pointer on (64-bit) hosts */ + uint buf_size; + uint idx; + char *_buf_compat; /* Redundant pointer for backward compat. */ +} hndrte_log_t; + +typedef struct { + /* Virtual UART + * When there is no UART (e.g. Quickturn), + * the host should write a complete + * input line directly into cbuf and then write + * the length into vcons_in. + * This may also be used when there is a real UART + * (at risk of conflicting with + * the real UART). vcons_out is currently unused. + */ + volatile uint vcons_in; + volatile uint vcons_out; + + /* Output (logging) buffer + * Console output is written to a ring buffer log_buf at index log_idx. + * The host may read the output when it sees log_idx advance. + * Output will be lost if the output wraps around faster than the host + * polls. + */ + hndrte_log_t log; + + /* Console input line buffer + * Characters are read one at a time into cbuf + * until is received, then + * the buffer is processed as a command line. + * Also used for virtual UART. + */ + uint cbuf_idx; + char cbuf[CBUF_LEN]; +} hndrte_cons_t; diff --git a/drivers/staging/brcm80211/include/linux_osl.h b/drivers/staging/brcm80211/include/linux_osl.h index 0e61de1b5fc1..1e555531167c 100644 --- a/drivers/staging/brcm80211/include/linux_osl.h +++ b/drivers/staging/brcm80211/include/linux_osl.h @@ -79,7 +79,7 @@ typedef struct { bool mmbus; /* Bus supports memory-mapped register accesses */ pktfree_cb_fn_t tx_fn; /* Callback function for PKTFREE */ void *tx_ctx; /* Context to the callback function */ -#ifdef BCMSDIO +#if defined(BCMSDIO) && !defined(BRCM_FULLMAC) osl_rreg_fn_t rreg_fn; /* Read Register function */ osl_wreg_fn_t wreg_fn; /* Write Register function */ void *reg_ctx; /* Context to the reg callback functions */ @@ -92,7 +92,7 @@ typedef struct { ((osl_pubinfo_t *)osh)->tx_ctx = _tx_ctx; \ } while (0) -#ifdef BCMSDIO +#if defined(BCMSDIO) && !defined(BRCM_FULLMAC) #define REGOPSSET(osh, rreg, wreg, ctx) \ do { \ ((osl_pubinfo_t *)osh)->rreg_fn = rreg; \ @@ -115,15 +115,22 @@ extern uint osl_malloced(osl_t *osh); #define MALLOC_FAILED(osh) osl_malloc_failed((osh)) extern uint osl_malloc_failed(osl_t *osh); +#ifdef BRCM_FULLMAC +#define DMA_CONSISTENT_ALIGN PAGE_SIZE +#define DMA_ALLOC_CONSISTENT(osh, size, pap, dmah, alignbits) \ + osl_dma_alloc_consistent((osh), (size), (pap)) +extern void *osl_dma_alloc_consistent(osl_t *osh, uint size, ulong *pap); +#else /* allocate/free shared (dma-able) consistent memory */ #define DMA_CONSISTENT_ALIGN osl_dma_consistent_align() #define DMA_ALLOC_CONSISTENT(osh, size, align, tot, pap, dmah) \ osl_dma_alloc_consistent((osh), (size), (align), (tot), (pap)) -#define DMA_FREE_CONSISTENT(osh, va, size, pa, dmah) \ - osl_dma_free_consistent((osh), (void *)(va), (size), (pa)) extern uint osl_dma_consistent_align(void); extern void *osl_dma_alloc_consistent(osl_t *osh, uint size, uint16 align, uint *tot, ulong *pap); +#endif +#define DMA_FREE_CONSISTENT(osh, va, size, pa, dmah) \ + osl_dma_free_consistent((osh), (void *)(va), (size), (pa)) extern void osl_dma_free_consistent(osl_t *osh, void *va, uint size, ulong pa); /* map/unmap direction */ @@ -317,32 +324,73 @@ extern int osl_error(int bcmerror); /* packet primitives */ #define PKTGET(osh, len, send) osl_pktget((osh), (len)) -#define PKTDUP(osh, skb) osl_pktdup((osh), (skb)) #define PKTFREE(osh, skb, send) osl_pktfree((osh), (skb), (send)) -#define PKTDATA(skb) (((struct sk_buff *)(skb))->data) -#define PKTLEN(skb) (((struct sk_buff *)(skb))->len) +#define PKTDATA(skb) (((struct sk_buff *)(skb))->data) +#define PKTLEN(skb) (((struct sk_buff *)(skb))->len) #define PKTHEADROOM(skb) (PKTDATA(skb)-(((struct sk_buff *)(skb))->head)) #define PKTTAILROOM(skb) ((((struct sk_buff *)(skb))->end)-(((struct sk_buff *)(skb))->tail)) -#define PKTNEXT(skb) (((struct sk_buff *)(skb))->next) -#define PKTSETNEXT(skb, x) (((struct sk_buff *)(skb))->next = (struct sk_buff*)(x)) -#define PKTSETLEN(skb, len) __skb_trim((struct sk_buff *)(skb), (len)) -#define PKTPUSH(skb, bytes) skb_push((struct sk_buff *)(skb), (bytes)) -#define PKTPULL(skb, bytes) skb_pull((struct sk_buff *)(skb), (bytes)) -#define PKTTAG(skb) ((void *)(((struct sk_buff *)(skb))->cb)) -#define PKTALLOCED(osh) (((osl_pubinfo_t *)(osh))->pktalloced) +#define PKTNEXT(skb) (((struct sk_buff *)(skb))->next) +#define PKTSETNEXT(skb, x) \ + (((struct sk_buff *)(skb))->next = (struct sk_buff *)(x)) +#define PKTSETLEN(skb, len) __skb_trim((struct sk_buff *)(skb), (len)) +#define PKTPUSH(skb, bytes) skb_push((struct sk_buff *)(skb), (bytes)) +#define PKTPULL(skb, bytes) skb_pull((struct sk_buff *)(skb), (bytes)) +#define PKTDUP(osh, skb) osl_pktdup((osh), (skb)) +#define PKTTAG(skb) ((void *)(((struct sk_buff *)(skb))->cb)) +#define PKTALLOCED(osh) (((osl_pubinfo_t *)(osh))->pktalloced) #define PKTSETPOOL(osh, skb, x, y) do {} while (0) #define PKTPOOL(osh, skb) FALSE +extern void *osl_pktget(osl_t *osh, uint len); +extern void osl_pktfree(osl_t *osh, void *skb, bool send); +extern void *osl_pktdup(osl_t *osh, void *skb); + +#ifdef BRCM_FULLMAC +#ifdef DHD_USE_STATIC_BUF +#define PKTGET_STATIC(osh, len, send) osl_pktget_static((osh), (len)) +#define PKTFREE_STATIC(osh, skb, send) \ + osl_pktfree_static((osh), (skb), (send)) +#endif +extern void *osl_pktget_static(osl_t *osh, uint len); +extern void osl_pktfree_static(osl_t *osh, void *skb, bool send); + +static INLINE void * +osl_pkt_frmnative(osl_pubinfo_t *osh, struct sk_buff *skb) +{ + struct sk_buff *nskb; + if (osh->pkttag) + bzero((void *)skb->cb, OSL_PKTTAG_SZ); + + for (nskb = skb; nskb; nskb = nskb->next) + osh->pktalloced++; + + return (void *)skb; +} +#define PKTFRMNATIVE(osh, skb) \ + osl_pkt_frmnative(((osl_pubinfo_t *)osh), (struct sk_buff*)(skb)) + +static INLINE struct sk_buff * +osl_pkt_tonative(osl_pubinfo_t *osh, void *pkt) +{ + struct sk_buff *nskb; + + if (osh->pkttag) + bzero(((struct sk_buff *)pkt)->cb, OSL_PKTTAG_SZ); + + for (nskb = (struct sk_buff *)pkt; nskb; nskb = nskb->next) + osh->pktalloced--; + + return (struct sk_buff *)pkt; +} +#define PKTTONATIVE(osh, pkt) \ + osl_pkt_tonative((osl_pubinfo_t *)(osh), (pkt)) +#else /* !BRCM_FULLMAC */ #define PKTUNALLOC(osh) (((osl_pubinfo_t *)(osh))->pktalloced--) #define PKTSETSKIPCT(osh, skb) #define PKTCLRSKIPCT(osh, skb) #define PKTSKIPCT(osh, skb) - -extern void osl_pktfree(osl_t *osh, void *skb, bool send); - -extern void *osl_pktget(osl_t *osh, uint len); -extern void *osl_pktdup(osl_t *osh, void *skb); +#endif /* BRCM_FULLMAC */ #define PKTLINK(skb) (((struct sk_buff *)(skb))->prev) #define PKTSETLINK(skb, x) (((struct sk_buff *)(skb))->prev = (struct sk_buff*)(x)) @@ -354,7 +402,7 @@ extern void *osl_pktdup(osl_t *osh, void *skb); /* PKTSETSUMNEEDED and PKTSUMGOOD are not possible because skb->ip_summed is overloaded */ #define PKTSHARED(skb) (((struct sk_buff *)(skb))->cloned) -#ifdef BCMSDIO +#if defined(BCMSDIO) && !defined(BRCM_FULLMAC) #define RPC_READ_REG(osh, r) (\ sizeof(*(r)) == sizeof(uint8) ? osl_readb((osh), (volatile uint8*)(r)) : \ sizeof(*(r)) == sizeof(uint16) ? osl_readw((osh), (volatile uint16*)(r)) : \ diff --git a/drivers/staging/brcm80211/include/msgtrace.h b/drivers/staging/brcm80211/include/msgtrace.h new file mode 100644 index 000000000000..cfae9a69a651 --- /dev/null +++ b/drivers/staging/brcm80211/include/msgtrace.h @@ -0,0 +1,71 @@ +/* + * 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 _MSGTRACE_H +#define _MSGTRACE_H + +#ifndef _TYPEDEFS_H_ +#include +#endif + +/* This marks the start of a packed structure section. */ +#include + +#define MSGTRACE_VERSION 1 + +/* Message trace header */ +typedef BWL_PRE_PACKED_STRUCT struct msgtrace_hdr { + uint8 version; + uint8 spare; + uint16 len; /* Len of the trace */ + uint32 seqnum; /* Sequence number of message. Useful + * if the messsage has been lost + * because of DMA error or a bus reset + * (ex: SDIO Func2) + */ + uint32 discarded_bytes; /* Number of discarded bytes because of + trace overflow */ + uint32 discarded_printf; /* Number of discarded printf + because of trace overflow */ +} BWL_POST_PACKED_STRUCT msgtrace_hdr_t; + +#define MSGTRACE_HDRLEN sizeof(msgtrace_hdr_t) + +/* The hbus driver generates traces when sending a trace message. + * This causes endless traces. + * This flag must be set to TRUE in any hbus traces. + * The flag is reset in the function msgtrace_put. + * This prevents endless traces but generates hasardous + * lost of traces only in bus device code. + * It is recommendat to set this flag in macro SD_TRACE + * but not in SD_ERROR for avoiding missing + * hbus error traces. hbus error trace should not generates endless traces. + */ +extern bool msgtrace_hbus_trace; + +typedef void (*msgtrace_func_send_t) (void *hdl1, void *hdl2, uint8 *hdr, + uint16 hdrlen, uint8 *buf, + uint16 buflen); + +extern void msgtrace_sent(void); +extern void msgtrace_put(char *buf, int count); +extern void msgtrace_init(void *hdl1, void *hdl2, + msgtrace_func_send_t func_send); + +/* This marks the end of a packed structure section. */ +#include + +#endif /* _MSGTRACE_H */ diff --git a/drivers/staging/brcm80211/include/proto/802.11.h b/drivers/staging/brcm80211/include/proto/802.11.h index a0f7de969f23..04570e8040ff 100644 --- a/drivers/staging/brcm80211/include/proto/802.11.h +++ b/drivers/staging/brcm80211/include/proto/802.11.h @@ -1430,7 +1430,7 @@ typedef struct dot11_obss_ie dot11_obss_ie_t; #define WCN_OUI "\x00\x50\xf2" #define WCN_TYPE 4 - +#define BRCM_OUI "\x00\x10\x18" #include #endif /* _802_11_H_ */ diff --git a/drivers/staging/brcm80211/include/proto/bcmeth.h b/drivers/staging/brcm80211/include/proto/bcmeth.h new file mode 100644 index 000000000000..13653ed28ce0 --- /dev/null +++ b/drivers/staging/brcm80211/include/proto/bcmeth.h @@ -0,0 +1,52 @@ +/* + * 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 _BCMETH_H_ +#define _BCMETH_H_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#define BCMILCP_SUBTYPE_RATE 1 +#define BCMILCP_SUBTYPE_LINK 2 +#define BCMILCP_SUBTYPE_CSA 3 +#define BCMILCP_SUBTYPE_LARQ 4 +#define BCMILCP_SUBTYPE_VENDOR 5 +#define BCMILCP_SUBTYPE_FLH 17 +#define BCMILCP_SUBTYPE_VENDOR_LONG 32769 +#define BCMILCP_SUBTYPE_CERT 32770 +#define BCMILCP_SUBTYPE_SES 32771 +#define BCMILCP_BCM_SUBTYPE_RESERVED 0 +#define BCMILCP_BCM_SUBTYPE_EVENT 1 +#define BCMILCP_BCM_SUBTYPE_SES 2 +#define BCMILCP_BCM_SUBTYPE_DPT 4 +#define BCMILCP_BCM_SUBTYPEHDR_MINLENGTH 8 +#define BCMILCP_BCM_SUBTYPEHDR_VERSION 0 + +typedef BWL_PRE_PACKED_STRUCT struct bcmeth_hdr { + uint16 subtype; + uint16 length; + uint8 version; + uint8 oui[3]; + uint16 usr_subtype; +} BWL_POST_PACKED_STRUCT bcmeth_hdr_t; + +#include + +#endif /* _BCMETH_H_ */ diff --git a/drivers/staging/brcm80211/include/proto/bcmevent.h b/drivers/staging/brcm80211/include/proto/bcmevent.h index 37257f9727cb..b2dde61d18ef 100644 --- a/drivers/staging/brcm80211/include/proto/bcmevent.h +++ b/drivers/staging/brcm80211/include/proto/bcmevent.h @@ -42,6 +42,13 @@ typedef BWL_PRE_PACKED_STRUCT struct { char ifname[BCM_MSG_IFNAME_MAX]; } BWL_POST_PACKED_STRUCT wl_event_msg_t; +#ifdef BRCM_FULLMAC +typedef BWL_PRE_PACKED_STRUCT struct bcm_event { + struct ether_header eth; + bcmeth_hdr_t bcm_hdr; + wl_event_msg_t event; +} BWL_POST_PACKED_STRUCT bcm_event_t; +#endif #define BCM_MSG_LEN (sizeof(bcm_event_t) - sizeof(bcmeth_hdr_t) - \ sizeof(struct ether_header)) diff --git a/drivers/staging/brcm80211/include/proto/bcmip.h b/drivers/staging/brcm80211/include/proto/bcmip.h new file mode 100644 index 000000000000..24c01ca35c7e --- /dev/null +++ b/drivers/staging/brcm80211/include/proto/bcmip.h @@ -0,0 +1,136 @@ +/* + * 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 _bcmip_h_ +#define _bcmip_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#define IP_VER_OFFSET 0x0 +#define IP_VER_MASK 0xf0 +#define IP_VER_SHIFT 4 +#define IP_VER_4 4 +#define IP_VER_6 6 + +#define IP_VER(ip_body) \ + ((((uint8 *)(ip_body))[IP_VER_OFFSET] & IP_VER_MASK) >> IP_VER_SHIFT) + +#define IP_PROT_ICMP 0x1 +#define IP_PROT_TCP 0x6 +#define IP_PROT_UDP 0x11 + +#define IPV4_VER_HL_OFFSET 0 +#define IPV4_TOS_OFFSET 1 +#define IPV4_PKTLEN_OFFSET 2 +#define IPV4_PKTFLAG_OFFSET 6 +#define IPV4_PROT_OFFSET 9 +#define IPV4_CHKSUM_OFFSET 10 +#define IPV4_SRC_IP_OFFSET 12 +#define IPV4_DEST_IP_OFFSET 16 +#define IPV4_OPTIONS_OFFSET 20 + +#define IPV4_VER_MASK 0xf0 +#define IPV4_VER_SHIFT 4 + +#define IPV4_HLEN_MASK 0x0f +#define IPV4_HLEN(ipv4_body) \ + (4 * (((uint8 *)(ipv4_body))[IPV4_VER_HL_OFFSET] & IPV4_HLEN_MASK)) + +#define IPV4_ADDR_LEN 4 + +#define IPV4_ADDR_NULL(a) ((((uint8 *)(a))[0] | ((uint8 *)(a))[1] | \ + ((uint8 *)(a))[2] | ((uint8 *)(a))[3]) == 0) + +#define IPV4_ADDR_BCAST(a) ((((uint8 *)(a))[0] & ((uint8 *)(a))[1] & \ + ((uint8 *)(a))[2] & ((uint8 *)(a))[3]) == 0xff) + +#define IPV4_TOS_DSCP_MASK 0xfc +#define IPV4_TOS_DSCP_SHIFT 2 + +#define IPV4_TOS(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_TOS_OFFSET]) + +#define IPV4_TOS_PREC_MASK 0xe0 +#define IPV4_TOS_PREC_SHIFT 5 + +#define IPV4_TOS_LOWDELAY 0x10 +#define IPV4_TOS_THROUGHPUT 0x8 +#define IPV4_TOS_RELIABILITY 0x4 + +#define IPV4_PROT(ipv4_body) (((uint8 *)(ipv4_body))[IPV4_PROT_OFFSET]) + +#define IPV4_FRAG_RESV 0x8000 +#define IPV4_FRAG_DONT 0x4000 +#define IPV4_FRAG_MORE 0x2000 +#define IPV4_FRAG_OFFSET_MASK 0x1fff + +#define IPV4_ADDR_STR_LEN 16 + +BWL_PRE_PACKED_STRUCT struct ipv4_addr { + uint8 addr[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + +BWL_PRE_PACKED_STRUCT struct ipv4_hdr { + uint8 version_ihl; + uint8 tos; + uint16 tot_len; + uint16 id; + uint16 frag; + uint8 ttl; + uint8 prot; + uint16 hdr_chksum; + uint8 src_ip[IPV4_ADDR_LEN]; + uint8 dst_ip[IPV4_ADDR_LEN]; +} BWL_POST_PACKED_STRUCT; + +#define IPV6_PAYLOAD_LEN_OFFSET 4 +#define IPV6_NEXT_HDR_OFFSET 6 +#define IPV6_HOP_LIMIT_OFFSET 7 +#define IPV6_SRC_IP_OFFSET 8 +#define IPV6_DEST_IP_OFFSET 24 + +#define IPV6_TRAFFIC_CLASS(ipv6_body) \ + (((((uint8 *)(ipv6_body))[0] & 0x0f) << 4) | \ + ((((uint8 *)(ipv6_body))[1] & 0xf0) >> 4)) + +#define IPV6_FLOW_LABEL(ipv6_body) \ + (((((uint8 *)(ipv6_body))[1] & 0x0f) << 16) | \ + (((uint8 *)(ipv6_body))[2] << 8) | \ + (((uint8 *)(ipv6_body))[3])) + +#define IPV6_PAYLOAD_LEN(ipv6_body) \ + ((((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 0] << 8) | \ + ((uint8 *)(ipv6_body))[IPV6_PAYLOAD_LEN_OFFSET + 1]) + +#define IPV6_NEXT_HDR(ipv6_body) \ + (((uint8 *)(ipv6_body))[IPV6_NEXT_HDR_OFFSET]) + +#define IPV6_PROT(ipv6_body) IPV6_NEXT_HDR(ipv6_body) + +#define IPV6_ADDR_LEN 16 + +#ifndef IP_TOS +#define IP_TOS(ip_body) \ + (IP_VER(ip_body) == IP_VER_4 ? IPV4_TOS(ip_body) : \ + IP_VER(ip_body) == IP_VER_6 ? IPV6_TRAFFIC_CLASS(ip_body) : 0) +#endif + +#include + +#endif /* _bcmip_h_ */ diff --git a/drivers/staging/brcm80211/include/proto/vlan.h b/drivers/staging/brcm80211/include/proto/vlan.h new file mode 100644 index 000000000000..1dcd3f32582b --- /dev/null +++ b/drivers/staging/brcm80211/include/proto/vlan.h @@ -0,0 +1,49 @@ +/* + * 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 _vlan_h_ +#define _vlan_h_ + +#ifndef _TYPEDEFS_H_ +#include +#endif + +#include + +#define VLAN_VID_MASK 0xfff +#define VLAN_CFI_SHIFT 12 +#define VLAN_PRI_SHIFT 13 + +#define VLAN_PRI_MASK 7 + +#define VLAN_TAG_LEN 4 +#define VLAN_TAG_OFFSET (2 * ETHER_ADDR_LEN) + +#define VLAN_TPID 0x8100 + +struct ethervlan_header { + uint8 ether_dhost[ETHER_ADDR_LEN]; + uint8 ether_shost[ETHER_ADDR_LEN]; + uint16 vlan_type; + uint16 vlan_tag; + uint16 ether_type; +}; + +#define ETHERVLAN_HDR_LEN (ETHER_HDR_LEN + VLAN_TAG_LEN) + +#include + +#endif /* _vlan_h_ */ diff --git a/drivers/staging/brcm80211/include/wlioctl.h b/drivers/staging/brcm80211/include/wlioctl.h index 970573ba8aa8..554316cfe0fc 100644 --- a/drivers/staging/brcm80211/include/wlioctl.h +++ b/drivers/staging/brcm80211/include/wlioctl.h @@ -19,6 +19,9 @@ #include #include +#ifdef BRCM_FULLMAC +#include +#endif #include #include #include @@ -102,7 +105,11 @@ typedef struct wl_bss_info_108 { /* variable length Information Elements */ } wl_bss_info_108_t; -#define WL_BSS_INFO_VERSION 109 /* current version of wl_bss_info struct */ +#ifdef BRCM_FULLMAC +#define WL_BSS_INFO_VERSION 108 /* current ver of wl_bss_info struct */ +#else +#define WL_BSS_INFO_VERSION 109 /* current ver of wl_bss_info struct */ +#endif /* BSS info structure * Applications MUST CHECK ie_offset field and length field to access IEs and @@ -264,6 +271,7 @@ typedef struct wl_scan_results { #define WL_SCAN_RESULTS_PARTIAL 1 #define WL_SCAN_RESULTS_PENDING 2 #define WL_SCAN_RESULTS_ABORTED 3 +#define WL_SCAN_RESULTS_NO_MEM 4 #define ESCAN_REQ_VERSION 1 @@ -843,7 +851,11 @@ typedef struct wlc_iov_trx_s { /* bump this number if you change the ioctl interface */ #define WLC_IOCTL_VERSION 1 +#ifdef BRCM_FULLMAC +#define WLC_IOCTL_MAXLEN 8192 +#else #define WLC_IOCTL_MAXLEN 3072 /* max length ioctl buffer required */ +#endif #define WLC_IOCTL_SMLEN 256 /* "small" length ioctl buffer required */ #define WLC_IOCTL_MEDLEN 1536 /* "med" length ioctl buffer required */ #define WLC_SAMPLECOLLECT_MAXLEN 10240 /* Max Sample Collect buffer for two cores */ @@ -2004,4 +2016,30 @@ typedef struct wl_pkteng { #define WLFEATURE_DISABLE_11N_AMPDU_RX 0x00000040 #define WLFEATURE_DISABLE_11N_GF 0x00000080 +#define WL_EVENTING_MASK_LEN 16 + +#define TOE_TX_CSUM_OL 0x00000001 +#define TOE_RX_CSUM_OL 0x00000002 + +#define PM_OFF 0 +#define PM_MAX 1 +#define PM_FAST 2 + +typedef enum sup_auth_status { + WLC_SUP_DISCONNECTED = 0, + WLC_SUP_CONNECTING, + WLC_SUP_IDREQUIRED, + WLC_SUP_AUTHENTICATING, + WLC_SUP_AUTHENTICATED, + WLC_SUP_KEYXCHANGE, + WLC_SUP_KEYED, + WLC_SUP_TIMEOUT, + WLC_SUP_LAST_BASIC_STATE, + WLC_SUP_KEYXCHANGE_WAIT_M1 = WLC_SUP_AUTHENTICATED, + WLC_SUP_KEYXCHANGE_PREP_M2 = WLC_SUP_KEYXCHANGE, + WLC_SUP_KEYXCHANGE_WAIT_M3 = WLC_SUP_LAST_BASIC_STATE, + WLC_SUP_KEYXCHANGE_PREP_M4, + WLC_SUP_KEYXCHANGE_WAIT_G1, + WLC_SUP_KEYXCHANGE_PREP_G2 +} sup_auth_status_t; #endif /* _wlioctl_h_ */ diff --git a/drivers/staging/brcm80211/util/hndpmu.c b/drivers/staging/brcm80211/util/hndpmu.c index 1db1b16e3aa9..dfc3a3cf6c43 100644 --- a/drivers/staging/brcm80211/util/hndpmu.c +++ b/drivers/staging/brcm80211/util/hndpmu.c @@ -39,6 +39,96 @@ */ #define PMU_NONE(args) +#ifdef BRCM_FULLMAC +/* SDIO Pad drive strength to select value mappings */ +typedef struct { + uint8 strength; /* Pad Drive Strength in mA */ + uint8 sel; /* Chip-specific select value */ +} sdiod_drive_str_t; + +/* SDIO Drive Strength to sel value table for PMU Rev 1 */ +static const sdiod_drive_str_t sdiod_drive_strength_tab1[] = { + {4, 0x2}, + {2, 0x3}, + {1, 0x0}, + {0, 0x0} }; + +/* SDIO Drive Strength to sel value table for PMU Rev 2, 3 */ +static const sdiod_drive_str_t sdiod_drive_strength_tab2[] = { + {12, 0x7}, + {10, 0x6}, + {8, 0x5}, + {6, 0x4}, + {4, 0x2}, + {2, 0x1}, + {0, 0x0} }; + +#define SDIOD_DRVSTR_KEY(chip, pmu) (((chip) << 16) | (pmu)) + +void +si_sdiod_drive_strength_init(si_t *sih, osl_t *osh, uint32 drivestrength) +{ + chipcregs_t *cc; + uint origidx, intr_val = 0; + sdiod_drive_str_t *str_tab = NULL; + uint32 str_mask = 0; + uint32 str_shift = 0; + + if (!(sih->cccaps & CC_CAP_PMU)) { + return; + } + + /* Remember original core before switch to chipc */ + cc = (chipcregs_t *) si_switch_core(sih, CC_CORE_ID, &origidx, + &intr_val); + + switch (SDIOD_DRVSTR_KEY(sih->chip, sih->pmurev)) { + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 1): + str_tab = (sdiod_drive_str_t *)&sdiod_drive_strength_tab1; + str_mask = 0x30000000; + str_shift = 28; + break; + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 2): + case SDIOD_DRVSTR_KEY(BCM4325_CHIP_ID, 3): + str_tab = (sdiod_drive_str_t *)&sdiod_drive_strength_tab2; + str_mask = 0x00003800; + str_shift = 11; + break; + + default: + PMU_MSG(("No SDIO Drive strength init done for chip %x rev %d " + "pmurev %d\n", sih->chip, sih->chiprev, sih->pmurev)); + + break; + } + + if (str_tab != NULL) { + uint32 drivestrength_sel = 0; + uint32 cc_data_temp; + int i; + + for (i = 0; str_tab[i].strength != 0; i++) { + if (drivestrength >= str_tab[i].strength) { + drivestrength_sel = str_tab[i].sel; + break; + } + } + + W_REG(osh, &cc->chipcontrol_addr, 1); + cc_data_temp = R_REG(osh, &cc->chipcontrol_data); + cc_data_temp &= ~str_mask; + drivestrength_sel <<= str_shift; + cc_data_temp |= drivestrength_sel; + W_REG(osh, &cc->chipcontrol_data, cc_data_temp); + + PMU_MSG(("SDIO: %dmA drive strength selected, set to 0x%08x\n", + drivestrength, cc_data_temp)); + } + + /* Return to original core */ + si_restore_core(sih, origidx, intr_val); +} +#else /* BRCM_FULLMAC */ /* PLL controls/clocks */ static void si_pmu1_pllinit0(si_t *sih, osl_t *osh, chipcregs_t *cc, uint32 xtal); @@ -2682,3 +2772,4 @@ static void BCMATTACHFN(si_pmu_set_4330_plldivs) (si_t *sih) (m6div << PMU1_PLL0_PC2_M6DIV_SHIFT)); si_pmu_pllcontrol(sih, PMU1_PLL0_PLLCTL2, ~0, pllc2); } +#endif /* BRCM_FULLMAC */ diff --git a/drivers/staging/brcm80211/util/sbutils.c b/drivers/staging/brcm80211/util/sbutils.c new file mode 100644 index 000000000000..0bb487146a77 --- /dev/null +++ b/drivers/staging/brcm80211/util/sbutils.c @@ -0,0 +1,584 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "siutils_priv.h" + +/* local prototypes */ +static uint _sb_coreidx(si_info_t *sii, uint32 sba); +static uint _sb_scan(si_info_t *sii, uint32 sba, void *regs, uint bus, + uint32 sbba, uint ncores); +static uint32 _sb_coresba(si_info_t *sii); +static void *_sb_setcoreidx(si_info_t *sii, uint coreidx); + +#define SET_SBREG(sii, r, mask, val) \ + W_SBREG((sii), (r), ((R_SBREG((sii), (r)) & ~(mask)) | (val))) +#define REGS2SB(va) (sbconfig_t *) ((int8 *)(va) + SBCONFIGOFF) + +/* sonicsrev */ +#define SONICS_2_2 (SBIDL_RV_2_2 >> SBIDL_RV_SHIFT) +#define SONICS_2_3 (SBIDL_RV_2_3 >> SBIDL_RV_SHIFT) + +#define R_SBREG(sii, sbr) sb_read_sbreg((sii), (sbr)) +#define W_SBREG(sii, sbr, v) sb_write_sbreg((sii), (sbr), (v)) +#define AND_SBREG(sii, sbr, v) \ + W_SBREG((sii), (sbr), (R_SBREG((sii), (sbr)) & (v))) +#define OR_SBREG(sii, sbr, v) \ + W_SBREG((sii), (sbr), (R_SBREG((sii), (sbr)) | (v))) + +static uint32 sb_read_sbreg(si_info_t *sii, volatile uint32 *sbr) +{ + return R_REG(sii->osh, sbr); +} + +static void sb_write_sbreg(si_info_t *sii, volatile uint32 *sbr, uint32 v) +{ + W_REG(sii->osh, sbr, v); +} + +uint sb_coreid(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return (R_SBREG(sii, &sb->sbidhigh) & SBIDH_CC_MASK) >> + SBIDH_CC_SHIFT; +} + +/* return core index of the core with address 'sba' */ +static uint BCMATTACHFN(_sb_coreidx) (si_info_t *sii, uint32 sba) +{ + uint i; + + for (i = 0; i < sii->numcores; i++) + if (sba == sii->coresba[i]) + return i; + return BADIDX; +} + +/* return core address of the current core */ +static uint32 BCMATTACHFN(_sb_coresba) (si_info_t *sii) +{ + uint32 sbaddr = 0; + + switch (BUSTYPE(sii->pub.bustype)) { + case SPI_BUS: + case SDIO_BUS: + sbaddr = (uint32) (uintptr) sii->curmap; + break; + default: + ASSERT(0); + break; + } + + return sbaddr; +} + +uint sb_corerev(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + uint sbidh; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + sbidh = R_SBREG(sii, &sb->sbidhigh); + + return SBCOREREV(sbidh); +} + +bool sb_iscoreup(si_t *sih) +{ + si_info_t *sii; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + return (R_SBREG(sii, &sb->sbtmstatelow) & + (SBTML_RESET | SBTML_REJ_MASK | + (SICF_CLOCK_EN << SBTML_SICF_SHIFT))) == + (SICF_CLOCK_EN << SBTML_SICF_SHIFT); +} + +/* + * Switch to 'coreidx', issue a single arbitrary 32bit + * register mask&set operation, + * switch back to the original core, and return the new value. + * + * When using the silicon backplane, no fidleing with interrupts + * or core switches are needed. + * + * Also, when using pci/pcie, we can optimize away the core switching + * for pci registers + * and (on newer pci cores) chipcommon registers. + */ +uint sb_corereg(si_t *sih, uint coreidx, uint regoff, uint mask, uint val) +{ + uint origidx = 0; + uint32 *r = NULL; + uint w; + uint intr_val = 0; + bool fast = FALSE; + si_info_t *sii; + + sii = SI_INFO(sih); + + ASSERT(GOODIDX(coreidx)); + ASSERT(regoff < SI_CORE_SIZE); + ASSERT((val & ~mask) == 0); + + if (coreidx >= SI_MAXCORES) + return 0; + + if (!fast) { + INTR_OFF(sii, intr_val); + + /* save current core index */ + origidx = si_coreidx(&sii->pub); + + /* switch core */ + r = (uint32 *) ((uchar *) sb_setcoreidx(&sii->pub, coreidx) + + regoff); + } + ASSERT(r != NULL); + + /* mask and set */ + if (mask || val) { + if (regoff >= SBCONFIGOFF) { + w = (R_SBREG(sii, r) & ~mask) | val; + W_SBREG(sii, r, w); + } else { + w = (R_REG(sii->osh, r) & ~mask) | val; + W_REG(sii->osh, r, w); + } + } + + /* readback */ + if (regoff >= SBCONFIGOFF) + w = R_SBREG(sii, r); + else + w = R_REG(sii->osh, r); + + if (!fast) { + /* restore core index */ + if (origidx != coreidx) + sb_setcoreidx(&sii->pub, origidx); + + INTR_RESTORE(sii, intr_val); + } + + return w; +} + +/* Scan the enumeration space to find all cores starting from the given + * bus 'sbba'. Append coreid and other info to the lists in 'si'. 'sba' + * is the default core address at chip POR time and 'regs' is the virtual + * address that the default core is mapped at. 'ncores' is the number of + * cores expected on bus 'sbba'. It returns the total number of cores + * starting from bus 'sbba', inclusive. + */ +#define SB_MAXBUSES 2 +static uint +BCMATTACHFN(_sb_scan) (si_info_t *sii, uint32 sba, void *regs, uint bus, + uint32 sbba, uint numcores) { + uint next; + uint ncc = 0; + uint i; + + if (bus >= SB_MAXBUSES) { + SI_ERROR(("_sb_scan: bus 0x%08x at level %d is too deep to " + "scan\n", sbba, bus)); + return 0; + } + SI_MSG(("_sb_scan: scan bus 0x%08x assume %u cores\n", + sbba, numcores)); + + /* Scan all cores on the bus starting from core 0. + * Core addresses must be contiguous on each bus. + */ + for (i = 0, next = sii->numcores; + i < numcores && next < SB_BUS_MAXCORES; i++, next++) { + sii->coresba[next] = sbba + (i * SI_CORE_SIZE); + + /* change core to 'next' and read its coreid */ + sii->curmap = _sb_setcoreidx(sii, next); + sii->curidx = next; + + sii->coreid[next] = sb_coreid(&sii->pub); + + /* core specific processing... */ + /* chipc provides # cores */ + if (sii->coreid[next] == CC_CORE_ID) { + chipcregs_t *cc = (chipcregs_t *) sii->curmap; + uint32 ccrev = sb_corerev(&sii->pub); + + /* determine numcores - this is the + total # cores in the chip */ + if (((ccrev == 4) || (ccrev >= 6))) + numcores = + (R_REG(sii->osh, &cc->chipid) & CID_CC_MASK) + >> CID_CC_SHIFT; + else { + /* Older chips */ + SI_ERROR(("sb_chip2numcores: unsupported chip " + "0x%x\n", CHIPID(sii->pub.chip))); + ASSERT(0); + numcores = 1; + } + + SI_VMSG(("_sb_scan: %u cores in the chip %s\n", + numcores, sii->pub.issim ? "QT" : "")); + } + /* scan bridged SB(s) and add results to the end of the list */ + else if (sii->coreid[next] == OCP_CORE_ID) { + sbconfig_t *sb = REGS2SB(sii->curmap); + uint32 nsbba = R_SBREG(sii, &sb->sbadmatch1); + uint nsbcc; + + sii->numcores = next + 1; + + if ((nsbba & 0xfff00000) != SI_ENUM_BASE) + continue; + nsbba &= 0xfffff000; + if (_sb_coreidx(sii, nsbba) != BADIDX) + continue; + + nsbcc = + (R_SBREG(sii, &sb->sbtmstatehigh) & 0x000f0000) >> + 16; + nsbcc = _sb_scan(sii, sba, regs, bus + 1, nsbba, nsbcc); + if (sbba == SI_ENUM_BASE) + numcores -= nsbcc; + ncc += nsbcc; + } + } + + SI_MSG(("_sb_scan: found %u cores on bus 0x%08x\n", i, sbba)); + + sii->numcores = i + ncc; + return sii->numcores; +} + +/* scan the sb enumerated space to identify all cores */ +void BCMATTACHFN(sb_scan) (si_t *sih, void *regs, uint devid) +{ + si_info_t *sii; + uint32 origsba; + sbconfig_t *sb; + + sii = SI_INFO(sih); + sb = REGS2SB(sii->curmap); + + sii->pub.socirev = + (R_SBREG(sii, &sb->sbidlow) & SBIDL_RV_MASK) >> SBIDL_RV_SHIFT; + + /* Save the current core info and validate it later till we know + * for sure what is good and what is bad. + */ + origsba = _sb_coresba(sii); + + /* scan all SB(s) starting from SI_ENUM_BASE */ + sii->numcores = _sb_scan(sii, origsba, regs, 0, SI_ENUM_BASE, 1); +} + +/* + * This function changes logical "focus" to the indicated core; + * must be called with interrupts off. + * Moreover, callers should keep interrupts off during switching out of + * and back to d11 core + */ +void *sb_setcoreidx(si_t *sih, uint coreidx) +{ + si_info_t *sii; + + sii = SI_INFO(sih); + + if (coreidx >= sii->numcores) + return NULL; + + /* + * If the user has provided an interrupt mask enabled function, + * then assert interrupts are disabled before switching the core. + */ + ASSERT((sii->intrsenabled_fn == NULL) + || !(*(sii)->intrsenabled_fn) ((sii)->intr_arg)); + + sii->curmap = _sb_setcoreidx(sii, coreidx); + sii->curidx = coreidx; + + return sii->curmap; +} + +/* This function changes the logical "focus" to the indicated core. + * Return the current core's virtual address. + */ +static void *_sb_setcoreidx(si_info_t *sii, uint coreidx) +{ + uint32 sbaddr = sii->coresba[coreidx]; + void *regs; + + switch (BUSTYPE(sii->pub.bustype)) { +#ifdef BCMSDIO + case SPI_BUS: + case SDIO_BUS: + /* map new one */ + if (!sii->regs[coreidx]) { + sii->regs[coreidx] = (void *)(uintptr) sbaddr; + ASSERT(GOODREGS(sii->regs[coreidx])); + } + regs = sii->regs[coreidx]; + break; +#endif /* BCMSDIO */ + default: + ASSERT(0); + regs = NULL; + break; + } + + return regs; +} + +/* traverse all cores to find and clear source of serror */ +static void sb_serr_clear(si_info_t *sii) +{ + sbconfig_t *sb; + uint origidx; + uint i, intr_val = 0; + void *corereg = NULL; + + INTR_OFF(sii, intr_val); + origidx = si_coreidx(&sii->pub); + + for (i = 0; i < sii->numcores; i++) { + corereg = sb_setcoreidx(&sii->pub, i); + if (NULL != corereg) { + sb = REGS2SB(corereg); + if ((R_SBREG(sii, &sb->sbtmstatehigh)) & SBTMH_SERR) { + AND_SBREG(sii, &sb->sbtmstatehigh, ~SBTMH_SERR); + SI_ERROR(("sb_serr_clear: SError core 0x%x\n", + sb_coreid(&sii->pub))); + } + } + } + + sb_setcoreidx(&sii->pub, origidx); + INTR_RESTORE(sii, intr_val); +} + +/* + * Check if any inband, outband or timeout errors has happened and clear them. + * Must be called with chip clk on ! + */ +bool sb_taclear(si_t *sih, bool details) +{ + si_info_t *sii; + sbconfig_t *sb; + uint origidx; + uint intr_val = 0; + bool rc = FALSE; + uint32 inband = 0, serror = 0, timeout = 0; + void *corereg = NULL; + volatile uint32 imstate, tmstate; + + sii = SI_INFO(sih); + + if ((BUSTYPE(sii->pub.bustype) == SDIO_BUS) || + (BUSTYPE(sii->pub.bustype) == SPI_BUS)) { + + INTR_OFF(sii, intr_val); + origidx = si_coreidx(sih); + + corereg = si_setcore(sih, PCMCIA_CORE_ID, 0); + if (NULL == corereg) + corereg = si_setcore(sih, SDIOD_CORE_ID, 0); + if (NULL != corereg) { + sb = REGS2SB(corereg); + + imstate = R_SBREG(sii, &sb->sbimstate); + if ((imstate != 0xffffffff) + && (imstate & (SBIM_IBE | SBIM_TO))) { + AND_SBREG(sii, &sb->sbimstate, + ~(SBIM_IBE | SBIM_TO)); + /* inband = imstate & SBIM_IBE; cmd error */ + timeout = imstate & SBIM_TO; + } + tmstate = R_SBREG(sii, &sb->sbtmstatehigh); + if ((tmstate != 0xffffffff) + && (tmstate & SBTMH_INT_STATUS)) { + sb_serr_clear(sii); + serror = 1; + OR_SBREG(sii, &sb->sbtmstatelow, SBTML_INT_ACK); + AND_SBREG(sii, &sb->sbtmstatelow, + ~SBTML_INT_ACK); + } + } + + sb_setcoreidx(sih, origidx); + INTR_RESTORE(sii, intr_val); + } + + if (inband | timeout | serror) { + rc = TRUE; + SI_ERROR(("sb_taclear: inband 0x%x, serror 0x%x, timeout " + "0x%x!\n", inband, serror, timeout)); + } + + return rc; +} + +void sb_core_disable(si_t *sih, uint32 bits) +{ + si_info_t *sii; + volatile uint32 dummy; + sbconfig_t *sb; + + sii = SI_INFO(sih); + + ASSERT(GOODREGS(sii->curmap)); + sb = REGS2SB(sii->curmap); + + /* if core is already in reset, just return */ + if (R_SBREG(sii, &sb->sbtmstatelow) & SBTML_RESET) + return; + + /* if clocks are not enabled, put into reset and return */ + if ((R_SBREG(sii, &sb->sbtmstatelow) & + (SICF_CLOCK_EN << SBTML_SICF_SHIFT)) == 0) + goto disable; + + /* set target reject and spin until busy is clear + (preserve core-specific bits) */ + OR_SBREG(sii, &sb->sbtmstatelow, SBTML_REJ); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + SPINWAIT((R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_BUSY), 100000); + if (R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_BUSY) + SI_ERROR(("%s: target state still busy\n", __func__)); + + if (R_SBREG(sii, &sb->sbidlow) & SBIDL_INIT) { + OR_SBREG(sii, &sb->sbimstate, SBIM_RJ); + dummy = R_SBREG(sii, &sb->sbimstate); + OSL_DELAY(1); + SPINWAIT((R_SBREG(sii, &sb->sbimstate) & SBIM_BY), 100000); + } + + /* set reset and reject while enabling the clocks */ + W_SBREG(sii, &sb->sbtmstatelow, + (((bits | SICF_FGC | SICF_CLOCK_EN) << SBTML_SICF_SHIFT) | + SBTML_REJ | SBTML_RESET)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(10); + + /* don't forget to clear the initiator reject bit */ + if (R_SBREG(sii, &sb->sbidlow) & SBIDL_INIT) + AND_SBREG(sii, &sb->sbimstate, ~SBIM_RJ); + +disable: + /* leave reset and reject asserted */ + W_SBREG(sii, &sb->sbtmstatelow, + ((bits << SBTML_SICF_SHIFT) | SBTML_REJ | SBTML_RESET)); + OSL_DELAY(1); +} + +/* reset and re-enable a core + * inputs: + * bits - core specific bits that are set during and after reset sequence + * resetbits - core specific bits that are set only during reset sequence + */ +void sb_core_reset(si_t *sih, uint32 bits, uint32 resetbits) +{ + si_info_t *sii; + sbconfig_t *sb; + volatile uint32 dummy; + + sii = SI_INFO(sih); + ASSERT(GOODREGS(sii->curmap)); + sb = REGS2SB(sii->curmap); + + /* + * Must do the disable sequence first to work for + * arbitrary current core state. + */ + sb_core_disable(sih, (bits | resetbits)); + + /* + * Now do the initialization sequence. + */ + + /* set reset while enabling the clock and + forcing them on throughout the core */ + W_SBREG(sii, &sb->sbtmstatelow, + (((bits | resetbits | SICF_FGC | SICF_CLOCK_EN) << + SBTML_SICF_SHIFT) | SBTML_RESET)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + + if (R_SBREG(sii, &sb->sbtmstatehigh) & SBTMH_SERR) + W_SBREG(sii, &sb->sbtmstatehigh, 0); + + if ((dummy = R_SBREG(sii, &sb->sbimstate)) & (SBIM_IBE | SBIM_TO)) + AND_SBREG(sii, &sb->sbimstate, ~(SBIM_IBE | SBIM_TO)); + + /* clear reset and allow it to propagate throughout the core */ + W_SBREG(sii, &sb->sbtmstatelow, + ((bits | resetbits | SICF_FGC | SICF_CLOCK_EN) << + SBTML_SICF_SHIFT)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); + + /* leave clock enabled */ + W_SBREG(sii, &sb->sbtmstatelow, + ((bits | SICF_CLOCK_EN) << SBTML_SICF_SHIFT)); + dummy = R_SBREG(sii, &sb->sbtmstatelow); + OSL_DELAY(1); +} + +uint32 sb_base(uint32 admatch) +{ + uint32 base; + uint type; + + type = admatch & SBAM_TYPE_MASK; + ASSERT(type < 3); + + base = 0; + + if (type == 0) { + base = admatch & SBAM_BASE0_MASK; + } else if (type == 1) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE1_MASK; + } else if (type == 2) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE2_MASK; + } + + return base; +} diff --git a/drivers/staging/brcm80211/util/siutils.c b/drivers/staging/brcm80211/util/siutils.c index 08393ee1efaf..dff07cc6765a 100644 --- a/drivers/staging/brcm80211/util/siutils.c +++ b/drivers/staging/brcm80211/util/siutils.c @@ -59,12 +59,16 @@ static bool si_buscore_prep(si_info_t *sii, uint bustype, uint devid, void *sdh); static bool si_buscore_setup(si_info_t *sii, chipcregs_t *cc, uint bustype, uint32 savewin, uint *origidx, void *regs); +#ifndef BRCM_FULLMAC static void si_nvram_process(si_info_t *sii, char *pvars); +#endif /* dev path concatenation util */ +#ifndef BRCM_FULLMAC static char *si_devpathvar(si_t *sih, char *var, int len, const char *name); static bool _si_clkctl_cc(si_info_t *sii, uint mode); static bool si_ispcie(si_info_t *sii); +#endif static uint BCMINITFN(socram_banksize) (si_info_t *sii, sbsocramregs_t *r, uint8 idx, uint8 mtype); @@ -115,9 +119,11 @@ static bool BCMATTACHFN(si_buscore_prep) (si_info_t *sii, uint bustype, uint devid, void *sdh) { +#ifndef BRCM_FULLMAC /* kludge to enable the clock on the 4306 which lacks a slowclock */ if (BUSTYPE(bustype) == PCI_BUS && !si_ispcie(sii)) si_clkctl_xtal(&sii->pub, XTAL | PLL, ON); +#endif #if defined(BCMSDIO) if (BUSTYPE(bustype) == SDIO_BUS) { @@ -186,9 +192,10 @@ BCMATTACHFN(si_buscore_setup) (si_info_t *sii, chipcregs_t *cc, uint bustype, sii->pub.cccaps = R_REG(sii->osh, &cc->capabilities); /* get chipcommon extended capabilities */ +#ifndef BRCM_FULLMAC if (sii->pub.ccrev >= 35) sii->pub.cccaps_ext = R_REG(sii->osh, &cc->capabilities_ext); - +#endif /* get pmu rev and caps */ if (sii->pub.cccaps & CC_CAP_PMU) { sii->pub.pmucaps = R_REG(sii->osh, &cc->pmucapabilities); @@ -248,6 +255,20 @@ BCMATTACHFN(si_buscore_setup) (si_info_t *sii, chipcregs_t *cc, uint bustype, *origidx = i; } +#ifdef BRCM_FULLMAC + SI_MSG(("Buscore id/type/rev %d/0x%x/%d\n", sii->pub.buscoreidx, + sii->pub.buscoretype, sii->pub.buscorerev)); + + /* Make sure any on-chip ARM is off (in case strapping is wrong), + * or downloaded code was + * already running. + */ + if ((BUSTYPE(bustype) == SDIO_BUS) || (BUSTYPE(bustype) == SPI_BUS)) { + if (si_setcore(&sii->pub, ARM7S_CORE_ID, 0) || + si_setcore(&sii->pub, ARMCM3_CORE_ID, 0)) + si_core_disable(&sii->pub, 0); + } +#else if (pci && pcie) { if (si_ispcie(sii)) pci = FALSE; @@ -283,13 +304,14 @@ BCMATTACHFN(si_buscore_setup) (si_info_t *sii, chipcregs_t *cc, uint bustype, return FALSE; } } - +#endif /* return to the original core */ si_setcoreidx(&sii->pub, *origidx); return TRUE; } +#ifndef BRCM_FULLMAC static void BCMATTACHFN(si_nvram_process) (si_info_t *sii, char *pvars) { uint w = 0; @@ -347,6 +369,7 @@ static void BCMATTACHFN(si_nvram_process) (si_info_t *sii, char *pvars) sii->pub.boardflags = getintvar(pvars, "boardflags"); } +#endif /* !BRCM_FULLMAC */ /* this is will make Sonics calls directly, since Sonics is no longer supported in the Si abstraction */ /* this has been customized for the bcm 4329 ONLY */ @@ -401,10 +424,10 @@ static si_info_t *BCMATTACHFN(si_doattach) (si_info_t *sii, uint devid, sih->chiprev = (w & CID_REV_MASK) >> CID_REV_SHIFT; sih->chippkg = (w & CID_PKG_MASK) >> CID_PKG_SHIFT; - if ((CHIPID(sih->chip) == BCM4329_CHIP_ID) && (sih->chiprev == 0) && - (sih->chippkg != BCM4329_289PIN_PKG_ID)) { - sih->chippkg = BCM4329_182PIN_PKG_ID; - } + if ((CHIPID(sih->chip) == BCM4329_CHIP_ID) && + (sih->chippkg != BCM4329_289PIN_PKG_ID)) + sih->chippkg = BCM4329_182PIN_PKG_ID; + sih->issim = IS_SIM(sih->chippkg); /* scan for cores */ @@ -423,6 +446,9 @@ static si_info_t *BCMATTACHFN(si_doattach) (si_info_t *sii, uint devid, goto exit; } +#ifdef BRCM_FULLMAC + pvars = NULL; +#else /* Init nvram from flash if it exists */ nvram_init((void *)&(sii->pub)); @@ -434,14 +460,22 @@ static si_info_t *BCMATTACHFN(si_doattach) (si_info_t *sii, uint devid, } pvars = vars ? *vars : NULL; si_nvram_process(sii, pvars); +#endif /* === NVRAM, clock is ready === */ +#ifdef BRCM_FULLMAC + if (sii->pub.ccrev >= 20) { +#endif cc = (chipcregs_t *) si_setcore(sih, CC_CORE_ID, 0); W_REG(osh, &cc->gpiopullup, 0); W_REG(osh, &cc->gpiopulldown, 0); sb_setcoreidx(sih, origidx); +#ifdef BRCM_FULLMAC + } +#endif +#ifndef BRCM_FULLMAC /* PMU specific initializations */ if (PMUCTL_ENAB(sih)) { uint32 xtalfreq; @@ -466,6 +500,7 @@ static si_info_t *BCMATTACHFN(si_doattach) (si_info_t *sii, uint devid, /* clear any previous epidiag-induced target abort */ sb_taclear(sih, FALSE); #endif /* BCMDBG */ +#endif return sii; @@ -690,6 +725,7 @@ void BCMATTACHFN(si_detach) (si_t *sih) sii->regs[idx] = NULL; } +#ifndef BRCM_FULLMAC nvram_exit((void *)si_local); /* free up nvram buffers */ if (BUSTYPE(sih->bustype) == PCI_BUS) { @@ -697,6 +733,7 @@ void BCMATTACHFN(si_detach) (si_t *sih) pcicore_deinit(sii->pch); sii->pch = NULL; } +#endif #if !defined(BCMBUSTYPE) || (BCMBUSTYPE == SI_BUS) if (sii != &ksii) #endif /* !BCMBUSTYPE || (BCMBUSTYPE == SI_BUS) */ @@ -1219,6 +1256,7 @@ uint32 BCMINITFN(si_clock_rate) (uint32 pll_type, uint32 n, uint32 m) } } +#ifndef BRCM_FULLMAC uint32 BCMINITFN(si_clock) (si_t *sih) { si_info_t *sii; @@ -1277,8 +1315,35 @@ uint32 BCMINITFN(si_ilp_clock) (si_t *sih) return ILP_CLOCK; } +#endif /* set chip watchdog reset timer to fire in 'ticks' */ +#ifdef BRCM_FULLMAC +void +si_watchdog(si_t *sih, uint ticks) +{ + if (PMUCTL_ENAB(sih)) { + + if ((sih->chip == BCM4319_CHIP_ID) && (sih->chiprev == 0) && + (ticks != 0)) { + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, + clk_ctl_st), ~0, 0x2); + si_setcore(sih, USB20D_CORE_ID, 0); + si_core_disable(sih, 1); + si_setcore(sih, CC_CORE_ID, 0); + } + + if (ticks == 1) + ticks = 2; + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, pmuwatchdog), + ~0, ticks); + } else { + /* instant NMI */ + si_corereg(sih, SI_CC_IDX, OFFSETOF(chipcregs_t, watchdog), + ~0, ticks); + } +} +#else void si_watchdog(si_t *sih, uint ticks) { uint nb, maxt; @@ -1321,6 +1386,7 @@ void si_watchdog(si_t *sih, uint ticks) ticks); } } +#endif /* trigger watchdog reset after ms milliseconds */ void si_watchdog_ms(si_t *sih, uint32 ms) @@ -1328,6 +1394,7 @@ void si_watchdog_ms(si_t *sih, uint32 ms) si_watchdog(sih, wd_msticks * ms); } +#ifndef BRCM_FULLMAC uint16 BCMATTACHFN(si_d11_devid) (si_t *sih) { si_info_t *sii = SI_INFO(sih); @@ -1897,6 +1964,7 @@ void si_pci_pmeclr(si_t *sih) pcicore_pmeclr(sii->pch); } +#endif /* !BRCM_FULLMAC */ #ifdef BCMSDIO /* initialize the sdio core */ @@ -1937,6 +2005,7 @@ void si_sdio_init(si_t *sih) } #endif /* BCMSDIO */ +#ifndef BRCM_FULLMAC bool BCMATTACHFN(si_pci_war16165) (si_t *sih) { si_info_t *sii; @@ -2160,6 +2229,7 @@ int si_pci_fixcfg(si_t *sih) pcicore_hwup(sii->pch); return 0; } +#endif /* !BRCM_FULLMAC */ /* change logical "focus" to the gpio core for optimized access */ void *si_gpiosetcore(si_t *sih) @@ -2800,6 +2870,7 @@ bool si_deviceremoved(si_t *sih) return FALSE; } +#ifndef BRCM_FULLMAC bool si_is_sprom_available(si_t *sih) { if (sih->ccrev >= 31) { @@ -2959,3 +3030,4 @@ int si_cis_source(si_t *sih) return CIS_DEFAULT; } } +#endif /* BRCM_FULLMAC */ -- 2.30.2