summaryrefslogtreecommitdiffstats
path: root/src/wl/sys/wl_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/wl/sys/wl_linux.c')
-rw-r--r--src/wl/sys/wl_linux.c3371
1 files changed, 3371 insertions, 0 deletions
diff --git a/src/wl/sys/wl_linux.c b/src/wl/sys/wl_linux.c
new file mode 100644
index 0000000..0d05100
--- /dev/null
+++ b/src/wl/sys/wl_linux.c
@@ -0,0 +1,3371 @@
+/*
+ * Linux-specific portion of
+ * Broadcom 802.11abg Networking Device Driver
+ *
+ * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved.
+ *
+ * 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.
+ *
+ * $Id: wl_linux.c 580354 2015-08-18 23:42:37Z $
+ */
+
+#define LINUX_PORT
+
+#define __UNDEF_NO_VERSION__
+
+#include <typedefs.h>
+#include <linuxver.h>
+#include <osl.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
+#include <linux/module.h>
+#endif
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/ethtool.h>
+#include <linux/completion.h>
+#include <linux/usb.h>
+#include <linux/pci_ids.h>
+#define WLC_MAXBSSCFG 1
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0)
+#include <asm/switch_to.h>
+#else
+#include <asm/system.h>
+#endif
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/pgtable.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <proto/802.1d.h>
+
+#include <epivers.h>
+#include <bcmendian.h>
+#include <proto/ethernet.h>
+#include <bcmutils.h>
+#include <pcicfg.h>
+#include <wlioctl.h>
+#include <wlc_key.h>
+#include <siutils.h>
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 4, 5)
+#error "No support for Kernel Rev <= 2.4.5, As the older kernel revs doesn't support Tasklets"
+#endif
+
+#include <wlc_pub.h>
+#include <wl_dbg.h>
+#include <wlc_ethereal.h>
+#include <proto/ieee80211_radiotap.h>
+
+#include <wl_iw.h>
+#ifdef USE_IW
+struct iw_statistics *wl_get_wireless_stats(struct net_device *dev);
+#endif
+
+#include <wl_export.h>
+
+#include <wl_linux.h>
+
+#if defined(USE_CFG80211)
+#include <wl_cfg80211_hybrid.h>
+#endif
+
+#include <wlc_wowl.h>
+
+static void wl_timer(ulong data);
+static void _wl_timer(wl_timer_t *t);
+static struct net_device *wl_alloc_linux_if(wl_if_t *wlif);
+
+static int wl_monitor_start(struct sk_buff *skb, struct net_device *dev);
+
+static void wl_start_txqwork(wl_task_t *task);
+static void wl_txq_free(wl_info_t *wl);
+#define TXQ_LOCK(_wl) spin_lock_bh(&(_wl)->txq_lock)
+#define TXQ_UNLOCK(_wl) spin_unlock_bh(&(_wl)->txq_lock)
+
+static void wl_set_multicast_list_workitem(struct work_struct *work);
+
+static void wl_timer_task(wl_task_t *task);
+static void wl_dpc_rxwork(struct wl_task *task);
+
+static int wl_reg_proc_entry(wl_info_t *wl);
+
+static int wl_linux_watchdog(void *ctx);
+static
+int wl_found = 0;
+
+typedef struct priv_link {
+ wl_if_t *wlif;
+} priv_link_t;
+
+#define WL_DEV_IF(dev) ((wl_if_t*)((priv_link_t*)DEV_PRIV(dev))->wlif)
+
+#ifdef WL_INFO
+#undef WL_INFO
+#endif
+#define WL_INFO(dev) ((wl_info_t*)(WL_DEV_IF(dev)->wl))
+
+static int wl_open(struct net_device *dev);
+static int wl_close(struct net_device *dev);
+static int BCMFASTPATH wl_start(struct sk_buff *skb, struct net_device *dev);
+static int wl_start_int(wl_info_t *wl, wl_if_t *wlif, struct sk_buff *skb);
+
+static struct net_device_stats *wl_get_stats(struct net_device *dev);
+static int wl_set_mac_address(struct net_device *dev, void *addr);
+static void wl_set_multicast_list(struct net_device *dev);
+static void _wl_set_multicast_list(struct net_device *dev);
+static int wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif);
+static void wl_dpc(ulong data);
+static void wl_tx_tasklet(ulong data);
+static void wl_link_up(wl_info_t *wl, char * ifname);
+static void wl_link_down(wl_info_t *wl, char *ifname);
+static int wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *), void *context);
+#if defined(BCMDBG)
+static int wl_dump(wl_info_t *wl, struct bcmstrbuf *b);
+#endif
+static struct wl_if *wl_alloc_if(wl_info_t *wl, int iftype, uint unit, struct wlc_if* wlc_if);
+static void wl_free_if(wl_info_t *wl, wl_if_t *wlif);
+static void wl_get_driver_info(struct net_device *dev, struct ethtool_drvinfo *info);
+
+#if defined(WL_CONFIG_RFKILL)
+#include <linux/rfkill.h>
+static int wl_init_rfkill(wl_info_t *wl);
+static void wl_uninit_rfkill(wl_info_t *wl);
+static int wl_set_radio_block(void *data, bool blocked);
+static void wl_report_radio_state(wl_info_t *wl);
+#endif
+
+MODULE_LICENSE("MIXED/Proprietary");
+
+static struct pci_device_id wl_id_table[] =
+{
+ { PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, 0 },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, wl_id_table);
+
+static unsigned int online_cpus = 1;
+
+#ifdef BCMDBG
+static int msglevel = 0xdeadbeef;
+module_param(msglevel, int, 0);
+static int msglevel2 = 0xdeadbeef;
+module_param(msglevel2, int, 0);
+static int phymsglevel = 0xdeadbeef;
+module_param(phymsglevel, int, 0);
+#endif
+
+#ifdef BCMDBG_ASSERT
+static int assert_type = 0xdeadbeef;
+module_param(assert_type, int, 0);
+#endif
+
+static int passivemode = 0;
+module_param(passivemode, int, 0);
+
+#define WL_TXQ_THRESH 0
+static int wl_txq_thresh = WL_TXQ_THRESH;
+module_param(wl_txq_thresh, int, 0);
+
+static int oneonly = 0;
+module_param(oneonly, int, 0);
+
+static int piomode = 0;
+module_param(piomode, int, 0);
+
+static int instance_base = 0;
+module_param(instance_base, int, 0);
+
+#if defined(BCMDBG)
+static struct ether_addr local_ea;
+static char *macaddr = NULL;
+module_param(macaddr, charp, S_IRUGO);
+#endif
+
+static int nompc = 0;
+module_param(nompc, int, 0);
+
+#ifdef quote_str
+#undef quote_str
+#endif
+#ifdef to_str
+#undef to_str
+#endif
+#define to_str(s) #s
+#define quote_str(s) to_str(s)
+
+#define BRCM_WLAN_IFNAME eth%d
+
+static char intf_name[IFNAMSIZ] = quote_str(BRCM_WLAN_IFNAME);
+
+module_param_string(intf_name, intf_name, IFNAMSIZ, 0);
+
+static const u_int8_t brcm_oui[] = {0x00, 0x10, 0x18};
+
+#define WL_RADIOTAP_BRCM2_HT_SNS 0x01
+#define WL_RADIOTAP_BRCM2_HT_MCS 0x00000001
+
+#define WL_RADIOTAP_LEGACY_SNS 0x02
+#define WL_RADIOTAP_LEGACY_VHT 0x00000001
+
+#define IEEE80211_RADIOTAP_HTMOD_40 0x01
+#define IEEE80211_RADIOTAP_HTMOD_SGI 0x02
+#define IEEE80211_RADIOTAP_HTMOD_GF 0x04
+#define IEEE80211_RADIOTAP_HTMOD_LDPC 0x08
+#define IEEE80211_RADIOTAP_HTMOD_STBC_MASK 0x30
+#define IEEE80211_RADIOTAP_HTMOD_STBC_SHIFT 4
+
+#define WL_RADIOTAP_F_NONHT_VHT_DYN_BW 0x01
+
+#define WL_RADIOTAP_F_NONHT_VHT_BW 0x02
+
+struct wl_radiotap_nonht_vht {
+ u_int8_t len;
+ u_int8_t flags;
+ u_int8_t bw;
+} __attribute__ ((packed));
+
+typedef struct wl_radiotap_nonht_vht wl_radiotap_nonht_vht_t;
+
+struct wl_radiotap_legacy {
+ struct ieee80211_radiotap_header ieee_radiotap;
+ u_int32_t it_present_ext;
+ u_int32_t pad1;
+ uint32 tsft_l;
+ uint32 tsft_h;
+ uint8 flags;
+ uint8 rate;
+ uint16 channel_freq;
+ uint16 channel_flags;
+ uint8 signal;
+ uint8 noise;
+ int8 antenna;
+ uint8 pad2;
+ u_int8_t vend_oui[3];
+ u_int8_t vend_sns;
+ u_int16_t vend_skip_len;
+ wl_radiotap_nonht_vht_t nonht_vht;
+} __attribute__ ((__packed__));
+
+typedef struct wl_radiotap_legacy wl_radiotap_legacy_t;
+
+#define WL_RADIOTAP_LEGACY_SKIP_LEN htol16(sizeof(struct wl_radiotap_legacy) - \
+ offsetof(struct wl_radiotap_legacy, nonht_vht))
+
+#define WL_RADIOTAP_NONHT_VHT_LEN (sizeof(wl_radiotap_nonht_vht_t) - 1)
+
+struct wl_radiotap_ht_brcm_2 {
+ struct ieee80211_radiotap_header ieee_radiotap;
+ u_int32_t it_present_ext;
+ u_int32_t pad1;
+ uint32 tsft_l;
+ uint32 tsft_h;
+ u_int8_t flags;
+ u_int8_t pad2;
+ u_int16_t channel_freq;
+ u_int16_t channel_flags;
+ u_int8_t signal;
+ u_int8_t noise;
+ u_int8_t antenna;
+ u_int8_t pad3;
+ u_int8_t vend_oui[3];
+ u_int8_t vend_sns;
+ u_int16_t vend_skip_len;
+ u_int8_t mcs;
+ u_int8_t htflags;
+} __attribute__ ((packed));
+
+typedef struct wl_radiotap_ht_brcm_2 wl_radiotap_ht_brcm_2_t;
+
+#define WL_RADIOTAP_HT_BRCM2_SKIP_LEN htol16(sizeof(struct wl_radiotap_ht_brcm_2) - \
+ offsetof(struct wl_radiotap_ht_brcm_2, mcs))
+
+struct wl_radiotap_ht_brcm_3 {
+ struct ieee80211_radiotap_header ieee_radiotap;
+ u_int32_t it_present_ext;
+ u_int32_t pad1;
+ uint32 tsft_l;
+ uint32 tsft_h;
+ u_int8_t flags;
+ u_int8_t pad2;
+ u_int16_t channel_freq;
+ u_int16_t channel_flags;
+ u_int8_t signal;
+ u_int8_t noise;
+ u_int8_t antenna;
+ u_int8_t mcs_known;
+ u_int8_t mcs_flags;
+ u_int8_t mcs_index;
+ u_int8_t vend_oui[3];
+ u_int8_t vend_sns;
+ u_int16_t vend_skip_len;
+ wl_radiotap_nonht_vht_t nonht_vht;
+} __attribute__ ((packed));
+
+typedef struct wl_radiotap_ht_brcm_3 wl_radiotap_ht_brcm_3_t;
+
+struct wl_radiotap_ht {
+ struct ieee80211_radiotap_header ieee_radiotap;
+ uint32 tsft_l;
+ uint32 tsft_h;
+ u_int8_t flags;
+ u_int8_t pad1;
+ u_int16_t channel_freq;
+ u_int16_t channel_flags;
+ u_int8_t signal;
+ u_int8_t noise;
+ u_int8_t antenna;
+ u_int8_t mcs_known;
+ u_int8_t mcs_flags;
+ u_int8_t mcs_index;
+} __attribute__ ((packed));
+
+typedef struct wl_radiotap_ht wl_radiotap_ht_t;
+
+struct wl_radiotap_vht {
+ struct ieee80211_radiotap_header ieee_radiotap;
+ uint32 tsft_l;
+ uint32 tsft_h;
+ u_int8_t flags;
+ u_int8_t pad1;
+ u_int16_t channel_freq;
+ u_int16_t channel_flags;
+ u_int8_t signal;
+ u_int8_t noise;
+ u_int8_t antenna;
+ u_int8_t pad2;
+ u_int16_t pad3;
+ uint32 ampdu_ref_num;
+ u_int16_t ampdu_flags;
+ u_int8_t ampdu_delim_crc;
+ u_int8_t ampdu_reserved;
+ u_int16_t vht_known;
+ u_int8_t vht_flags;
+ u_int8_t vht_bw;
+ u_int8_t vht_mcs_nss[4];
+ u_int8_t vht_coding;
+ u_int8_t vht_group_id;
+ u_int16_t vht_partial_aid;
+} __attribute__ ((packed));
+
+typedef struct wl_radiotap_vht wl_radiotap_vht_t;
+
+#define WL_RADIOTAP_PRESENT_LEGACY \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | \
+ (1 << IEEE80211_RADIOTAP_EXT))
+
+#define WL_RADIOTAP_PRESENT_HT_BRCM2 \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_VENDOR_NAMESPACE) | \
+ (1 << IEEE80211_RADIOTAP_EXT))
+
+#define WL_RADIOTAP_PRESENT_HT \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_MCS))
+
+#define WL_RADIOTAP_PRESENT_VHT \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_AMPDU) | \
+ (1 << IEEE80211_RADIOTAP_VHT))
+
+#ifndef ARPHRD_IEEE80211_RADIOTAP
+#define ARPHRD_IEEE80211_RADIOTAP 803
+#endif
+
+#ifndef SRCBASE
+#define SRCBASE "."
+#endif
+
+#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+static struct ethtool_ops wl_ethtool_ops =
+#else
+static const struct ethtool_ops wl_ethtool_ops =
+#endif
+{
+ .get_drvinfo = wl_get_driver_info,
+};
+#endif
+
+#if defined(WL_USE_NETDEV_OPS)
+
+static const struct net_device_ops wl_netdev_ops =
+{
+ .ndo_open = wl_open,
+ .ndo_stop = wl_close,
+ .ndo_start_xmit = wl_start,
+ .ndo_get_stats = wl_get_stats,
+ .ndo_set_mac_address = wl_set_mac_address,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)
+ .ndo_set_rx_mode = wl_set_multicast_list,
+#else
+ .ndo_set_multicast_list = wl_set_multicast_list,
+#endif
+ .ndo_do_ioctl = wl_ioctl
+};
+
+static const struct net_device_ops wl_netdev_monitor_ops =
+{
+ .ndo_start_xmit = wl_monitor_start,
+ .ndo_get_stats = wl_get_stats,
+ .ndo_do_ioctl = wl_ioctl
+};
+#endif
+
+static void
+wl_if_setup(struct net_device *dev)
+{
+#if defined(WL_USE_NETDEV_OPS)
+ dev->netdev_ops = &wl_netdev_ops;
+#else
+ dev->open = wl_open;
+ dev->stop = wl_close;
+ dev->hard_start_xmit = wl_start;
+ dev->get_stats = wl_get_stats;
+ dev->set_mac_address = wl_set_mac_address;
+ dev->set_multicast_list = wl_set_multicast_list;
+ dev->do_ioctl = wl_ioctl;
+#endif
+
+#ifdef USE_IW
+#if WIRELESS_EXT < 19
+ dev->get_wireless_stats = wl_get_wireless_stats;
+#endif
+#if WIRELESS_EXT > 12
+ dev->wireless_handlers = (struct iw_handler_def *) &wl_iw_handler_def;
+#endif
+#endif
+
+#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
+ dev->ethtool_ops = &wl_ethtool_ops;
+#endif
+}
+
+static wl_info_t *
+wl_attach(uint16 vendor, uint16 device, ulong regs,
+ uint bustype, void *btparam, uint irq, uchar* bar1_addr, uint32 bar1_size)
+{
+ struct net_device *dev;
+ wl_if_t *wlif;
+ wl_info_t *wl;
+ osl_t *osh;
+ int unit, err;
+#if defined(USE_CFG80211)
+ struct device *parentdev;
+#endif
+
+ unit = wl_found + instance_base;
+ err = 0;
+
+ if (unit < 0) {
+ WL_ERROR(("wl%d: unit number overflow, exiting\n", unit));
+ return NULL;
+ }
+
+ if (oneonly && (unit != instance_base)) {
+ WL_ERROR(("wl%d: wl_attach: oneonly is set, exiting\n", unit));
+ return NULL;
+ }
+
+ osh = osl_attach(btparam, bustype, TRUE);
+ ASSERT(osh);
+
+ if ((wl = (wl_info_t*) MALLOC(osh, sizeof(wl_info_t))) == NULL) {
+ WL_ERROR(("wl%d: malloc wl_info_t, out of memory, malloced %d bytes\n", unit,
+ MALLOCED(osh)));
+ osl_detach(osh);
+ return NULL;
+ }
+ bzero(wl, sizeof(wl_info_t));
+
+ wl->osh = osh;
+ wl->unit = unit;
+ atomic_set(&wl->callbacks, 0);
+
+ wl->all_dispatch_mode = (passivemode == 0) ? TRUE : FALSE;
+ if (WL_ALL_PASSIVE_ENAB(wl)) {
+
+ MY_INIT_WORK(&wl->txq_task.work, (work_func_t)wl_start_txqwork);
+ wl->txq_task.context = wl;
+
+ MY_INIT_WORK(&wl->multicast_task.work, (work_func_t)wl_set_multicast_list_workitem);
+
+ MY_INIT_WORK(&wl->wl_dpc_task.work, (work_func_t)wl_dpc_rxwork);
+ wl->wl_dpc_task.context = wl;
+ }
+
+ wl->txq_dispatched = FALSE;
+ wl->txq_head = wl->txq_tail = NULL;
+ wl->txq_cnt = 0;
+
+ wlif = wl_alloc_if(wl, WL_IFTYPE_BSS, unit, NULL);
+ if (!wlif) {
+ WL_ERROR(("wl%d: %s: wl_alloc_if failed\n", unit, __FUNCTION__));
+ MFREE(osh, wl, sizeof(wl_info_t));
+ osl_detach(osh);
+ return NULL;
+ }
+
+ if (wl_alloc_linux_if(wlif) == NULL) {
+ WL_ERROR(("wl%d: %s: wl_alloc_linux_if failed\n", unit, __FUNCTION__));
+ MFREE(osh, wl, sizeof(wl_info_t));
+ osl_detach(osh);
+ return NULL;
+ }
+
+ dev = wlif->dev;
+ wl->dev = dev;
+ wl_if_setup(dev);
+
+ dev->base_addr = regs;
+
+ WL_TRACE(("wl%d: Bus: ", unit));
+ if (bustype == PCMCIA_BUS) {
+
+ wl->piomode = TRUE;
+ WL_TRACE(("PCMCIA\n"));
+ } else if (bustype == PCI_BUS) {
+
+ wl->piomode = piomode;
+ WL_TRACE(("PCI/%s\n", wl->piomode ? "PIO" : "DMA"));
+ }
+ else if (bustype == RPC_BUS) {
+
+ } else {
+ bustype = PCI_BUS;
+ WL_TRACE(("force to PCI\n"));
+ }
+ wl->bcm_bustype = bustype;
+
+ if ((wl->regsva = ioremap_nocache(dev->base_addr, PCI_BAR0_WINSZ)) == NULL) {
+ WL_ERROR(("wl%d: ioremap() failed\n", unit));
+ goto fail;
+ }
+
+ wl->bar1_addr = bar1_addr;
+ wl->bar1_size = bar1_size;
+
+ spin_lock_init(&wl->lock);
+ spin_lock_init(&wl->isr_lock);
+
+ if (WL_ALL_PASSIVE_ENAB(wl))
+ sema_init(&wl->sem, 1);
+
+ spin_lock_init(&wl->txq_lock);
+
+ if (!(wl->wlc = wlc_attach((void *) wl, vendor, device, unit, wl->piomode,
+ osh, wl->regsva, wl->bcm_bustype, btparam, &err))) {
+ printf("wl driver %s failed with code %d\n", EPI_VERSION_STR, err);
+ goto fail;
+ }
+ wl->pub = wlc_pub(wl->wlc);
+
+ wlif->wlcif = wlc_wlcif_get_by_index(wl->wlc, 0);
+
+ if (nompc) {
+ if (wlc_iovar_setint(wl->wlc, "mpc", 0)) {
+ WL_ERROR(("wl%d: Error setting MPC variable to 0\n", unit));
+ }
+ }
+
+ wlc_iovar_setint(wl->wlc, "scan_passive_time", 170);
+
+ wlc_iovar_setint(wl->wlc, "qtxpower", 23 * 4);
+
+#ifdef BCMDBG
+ if (macaddr != NULL) {
+ int dbg_err;
+
+ WL_ERROR(("wl%d: setting MAC ADDRESS %s\n", unit, macaddr));
+ bcm_ether_atoe(macaddr, &local_ea);
+
+ dbg_err = wlc_iovar_op(wl->wlc, "cur_etheraddr", NULL, 0, &local_ea,
+ ETHER_ADDR_LEN, IOV_SET, NULL);
+ if (dbg_err)
+ WL_ERROR(("wl%d: Error setting MAC ADDRESS\n", unit));
+ }
+#endif
+ bcopy(&wl->pub->cur_etheraddr, dev->dev_addr, ETHER_ADDR_LEN);
+
+ online_cpus = 1;
+
+ WL_ERROR(("wl%d: online cpus %d\n", unit, online_cpus));
+
+ tasklet_init(&wl->tasklet, wl_dpc, (ulong)wl);
+
+ tasklet_init(&wl->tx_tasklet, wl_tx_tasklet, (ulong)wl);
+
+ {
+ if (request_irq(irq, wl_isr, IRQF_SHARED, dev->name, wl)) {
+ WL_ERROR(("wl%d: request_irq() failed\n", unit));
+ goto fail;
+ }
+ dev->irq = irq;
+ }
+
+#if defined(USE_IW)
+ WL_ERROR(("Using Wireless Extension\n"));
+#endif
+
+#if defined(USE_CFG80211)
+ parentdev = NULL;
+ if (wl->bcm_bustype == PCI_BUS) {
+ parentdev = &((struct pci_dev *)btparam)->dev;
+ }
+ if (parentdev) {
+ if (wl_cfg80211_attach(dev, parentdev, WL_ALL_PASSIVE_ENAB(wl))) {
+ goto fail;
+ }
+ }
+ else {
+ WL_ERROR(("unsupported bus type\n"));
+ goto fail;
+ }
+#else
+
+ if (wl->bcm_bustype == PCI_BUS) {
+ struct pci_dev *pci_dev = (struct pci_dev *)btparam;
+ if (pci_dev != NULL)
+ SET_NETDEV_DEV(dev, &pci_dev->dev);
+ }
+#endif
+
+ if (register_netdev(dev)) {
+ WL_ERROR(("wl%d: register_netdev() failed\n", unit));
+ goto fail;
+ }
+ wlif->dev_registed = TRUE;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
+#endif
+#ifdef USE_IW
+ wlif->iw.wlinfo = (void *)wl;
+#endif
+
+#if defined(WL_CONFIG_RFKILL)
+ if (wl_init_rfkill(wl) < 0)
+ WL_ERROR(("%s: init_rfkill_failure\n", __FUNCTION__));
+#endif
+
+ if (wlc_iovar_setint(wl->wlc, "leddc", 0xa0000)) {
+ WL_ERROR(("wl%d: Error setting led duty-cycle\n", unit));
+ }
+ if (wlc_set(wl->wlc, WLC_SET_PM, PM_FAST)) {
+ WL_ERROR(("wl%d: Error setting PM variable to FAST PS\n", unit));
+ }
+
+ if (wlc_iovar_setint(wl->wlc, "vlan_mode", OFF)) {
+ WL_ERROR(("wl%d: Error setting vlan mode OFF\n", unit));
+ }
+
+ if (wlc_set(wl->wlc, WLC_SET_INFRA, 1)) {
+ WL_ERROR(("wl%d: Error setting infra_mode to infrastructure\n", unit));
+ }
+
+ if (wlc_module_register(wl->pub, NULL, "linux", wl, NULL, wl_linux_watchdog, NULL, NULL)) {
+ WL_ERROR(("wl%d: %s wlc_module_register() failed\n",
+ wl->pub->unit, __FUNCTION__));
+ goto fail;
+ }
+
+#ifdef BCMDBG
+ wlc_dump_register(wl->pub, "wl", (dump_fn_t)wl_dump, (void *)wl);
+#endif
+
+ wl_reg_proc_entry(wl);
+
+ printf("%s: Broadcom BCM%04x 802.11 Hybrid Wireless Controller%s %s",
+ dev->name, device,
+ WL_ALL_PASSIVE_ENAB(wl) ? ", Passive Mode" : "", EPI_VERSION_STR);
+
+#ifdef BCMDBG
+ printf(" (Compiled in " SRCBASE " at " __TIME__ " on " __DATE__ ")");
+#endif
+ printf("\n");
+
+ wl_found++;
+ return wl;
+
+fail:
+ wl_free(wl);
+ return NULL;
+}
+
+static void __devexit wl_remove(struct pci_dev *pdev);
+
+int __devinit
+wl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int rc;
+ wl_info_t *wl;
+ uint32 val;
+ uint32 bar1_size = 0;
+ void* bar1_addr = NULL;
+
+ WL_TRACE(("%s: bus %d slot %d func %d irq %d\n", __FUNCTION__,
+ pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), pdev->irq));
+
+ if ((pdev->vendor != PCI_VENDOR_ID_BROADCOM) ||
+ (((pdev->device & 0xff00) != 0x4300) &&
+ (pdev->device != 0x576) &&
+ ((pdev->device & 0xff00) != 0x4700) &&
+ ((pdev->device < 43000) || (pdev->device > 43999)))) {
+ WL_TRACE(("%s: unsupported vendor %x device %x\n", __FUNCTION__,
+ pdev->vendor, pdev->device));
+ return (-ENODEV);
+ }
+
+ rc = pci_enable_device(pdev);
+ if (rc) {
+ WL_ERROR(("%s: Cannot enable device %d-%d_%d\n", __FUNCTION__,
+ pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)));
+ return (-ENODEV);
+ }
+ pci_set_master(pdev);
+
+ pci_read_config_dword(pdev, 0x40, &val);
+ if ((val & 0x0000ff00) != 0)
+ pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
+ bar1_size = pci_resource_len(pdev, 2);
+ bar1_addr = (uchar *)ioremap_nocache(pci_resource_start(pdev, 2),
+ bar1_size);
+ wl = wl_attach(pdev->vendor, pdev->device, pci_resource_start(pdev, 0), PCI_BUS, pdev,
+ pdev->irq, bar1_addr, bar1_size);
+
+ if (!wl)
+ return -ENODEV;
+
+ pci_set_drvdata(pdev, wl);
+
+ return 0;
+}
+
+static int
+#if !defined(SIMPLE_DEV_PM_OPS)
+wl_suspend(struct pci_dev *pdev, DRV_SUSPEND_STATE_TYPE state)
+{
+#else
+wl_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+#endif
+ wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);
+ if (!wl) {
+ WL_ERROR(("wl: wl_suspend: pci_get_drvdata failed\n"));
+ return -ENODEV;
+ }
+ WL_ERROR(("%s: PCI Suspend handler\n", __FUNCTION__));
+
+ WL_LOCK(wl);
+ if (WLOFFLD_ENAB(wl->pub) && wlc_iovar_setint(wl->wlc, "wowl_activate", 1) == 0) {
+ WL_TRACE(("%s: Enabled WOWL OFFLOAD\n", __FUNCTION__));
+ } else {
+ WL_ERROR(("%s: Not WOWL capable\n", __FUNCTION__));
+ wl_down(wl);
+ wl->pub->hw_up = FALSE;
+ }
+ WL_UNLOCK(wl);
+
+ if (BUSTYPE(wl->pub->sih->bustype) == PCI_BUS)
+ si_pci_sleep(wl->pub->sih);
+
+ return 0;
+}
+
+static int
+#if !defined(SIMPLE_DEV_PM_OPS)
+wl_resume(struct pci_dev *pdev)
+{
+#else
+wl_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+#endif
+ int err = 0;
+ wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);
+ if (!wl) {
+ WL_ERROR(("wl: wl_resume: pci_get_drvdata failed\n"));
+ return -ENODEV;
+ }
+
+ WL_ERROR(("%s: PCI Resume handler\n", __FUNCTION__));
+ if (WLOFFLD_ENAB(wl->pub)) {
+ wlc_iovar_setint(wl->wlc, "wowl_activate", 0);
+ wlc_wowl_wake_reason_process(wl->wlc);
+
+ if (WOWL_ACTIVE(wl->pub)) {
+ if (BUSTYPE(wl->pub->sih->bustype) == PCI_BUS) {
+ si_pci_pmeclr(wl->pub->sih);
+ }
+ }
+ }
+
+ WL_LOCK(wl);
+ err = wl_up(wl);
+ WL_UNLOCK(wl);
+
+ return (err);
+}
+
+static void __devexit
+wl_remove(struct pci_dev *pdev)
+{
+ wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);
+
+ if (!wl) {
+ WL_ERROR(("wl: wl_remove: pci_get_drvdata failed\n"));
+ return;
+ }
+ if (!wlc_chipmatch(pdev->vendor, pdev->device)) {
+ WL_ERROR(("wl: wl_remove: wlc_chipmatch failed\n"));
+ return;
+ }
+
+ WL_LOCK(wl);
+ WL_APSTA_UPDN(("wl%d (%s): wl_remove() -> wl_down()\n", wl->pub->unit, wl->dev->name));
+ wl_down(wl);
+ WL_UNLOCK(wl);
+
+ wl_free(wl);
+ pci_disable_device(pdev);
+ pci_set_drvdata(pdev, NULL);
+}
+
+#if defined(SIMPLE_DEV_PM_OPS)
+static SIMPLE_DEV_PM_OPS(wl_pm_ops, wl_suspend, wl_resume);
+#endif
+
+static struct pci_driver wl_pci_driver __refdata = {
+ .name = "wl",
+ .probe = wl_pci_probe,
+ .remove = __devexit_p(wl_remove),
+ .id_table = wl_id_table,
+#ifdef SIMPLE_DEV_PM_OPS
+ .driver.pm = &wl_pm_ops,
+#else
+ .suspend = wl_suspend,
+ .resume = wl_resume,
+#endif
+};
+
+static int __init
+wl_module_init(void)
+{
+ int error = -ENODEV;
+
+#ifdef BCMDBG
+ if (msglevel != 0xdeadbeef)
+ wl_msg_level = msglevel;
+ else {
+ const char *var = getvar(NULL, "wl_msglevel");
+ if (var)
+ wl_msg_level = bcm_strtoul(var, NULL, 0);
+ }
+ printf("%s: msglevel set to 0x%x\n", __FUNCTION__, wl_msg_level);
+ if (msglevel2 != 0xdeadbeef)
+ wl_msg_level2 = msglevel2;
+ else {
+ const char *var = getvar(NULL, "wl_msglevel2");
+ if (var)
+ wl_msg_level2 = bcm_strtoul(var, NULL, 0);
+ }
+ printf("%s: msglevel2 set to 0x%x\n", __FUNCTION__, wl_msg_level2);
+ {
+ extern uint32 phyhal_msg_level;
+
+ if (phymsglevel != 0xdeadbeef)
+ phyhal_msg_level = phymsglevel;
+ else {
+ const char *var = getvar(NULL, "phy_msglevel");
+ if (var)
+ phyhal_msg_level = bcm_strtoul(var, NULL, 0);
+ }
+ printf("%s: phymsglevel set to 0x%x\n", __FUNCTION__, phyhal_msg_level);
+ }
+#endif
+
+ {
+ const char *var = getvar(NULL, "wl_dispatch_mode");
+ if (var)
+ passivemode = bcm_strtoul(var, NULL, 0);
+ if (passivemode)
+ printf("%s: passivemode enabled\n", __FUNCTION__);
+ }
+
+#ifdef BCMDBG_ASSERT
+
+ if (assert_type != 0xdeadbeef)
+ g_assert_type = assert_type;
+#endif
+
+ {
+ char *var = getvar(NULL, "wl_txq_thresh");
+ if (var)
+ wl_txq_thresh = bcm_strtoul(var, NULL, 0);
+#ifdef BCMDBG
+ WL_INFORM(("%s: wl_txq_thresh set to 0x%x\n",
+ __FUNCTION__, wl_txq_thresh));
+#endif
+ }
+
+ if (!(error = pci_module_init(&wl_pci_driver)))
+ return (0);
+
+ return (error);
+}
+
+static void __exit
+wl_module_exit(void)
+{
+
+ pci_unregister_driver(&wl_pci_driver);
+
+}
+
+module_init(wl_module_init);
+module_exit(wl_module_exit);
+
+void
+wl_free(wl_info_t *wl)
+{
+ wl_timer_t *t, *next;
+ osl_t *osh;
+
+ WL_TRACE(("wl: wl_free\n"));
+ {
+ if (wl->dev && wl->dev->irq)
+ free_irq(wl->dev->irq, wl);
+ }
+
+#if defined(WL_CONFIG_RFKILL)
+ wl_uninit_rfkill(wl);
+#endif
+
+ if (wl->dev) {
+ wl_free_if(wl, WL_DEV_IF(wl->dev));
+ wl->dev = NULL;
+ }
+
+ tasklet_kill(&wl->tasklet);
+
+ tasklet_kill(&wl->tx_tasklet);
+
+ if (wl->pub) {
+ wlc_module_unregister(wl->pub, "linux", wl);
+ }
+
+ if (wl->wlc) {
+ {
+ char tmp1[128];
+ sprintf(tmp1, "%s%d", HYBRID_PROC, wl->pub->unit);
+ remove_proc_entry(tmp1, 0);
+ }
+ wlc_detach(wl->wlc);
+ wl->wlc = NULL;
+ wl->pub = NULL;
+ }
+
+ while (atomic_read(&wl->callbacks) > 0)
+ schedule();
+
+ for (t = wl->timers; t; t = next) {
+ next = t->next;
+#ifdef BCMDBG
+ if (t->name)
+ MFREE(wl->osh, t->name, strlen(t->name) + 1);
+#endif
+ MFREE(wl->osh, t, sizeof(wl_timer_t));
+ }
+
+ osh = wl->osh;
+
+ if (wl->regsva && BUSTYPE(wl->bcm_bustype) != SDIO_BUS &&
+ BUSTYPE(wl->bcm_bustype) != JTAG_BUS) {
+ iounmap((void*)wl->regsva);
+ }
+ wl->regsva = NULL;
+
+ if (wl->bar1_addr) {
+ iounmap(wl->bar1_addr);
+ wl->bar1_addr = NULL;
+ }
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
+#endif
+
+ wl_txq_free(wl);
+
+ MFREE(osh, wl, sizeof(wl_info_t));
+
+ if (MALLOCED(osh)) {
+ printf("Memory leak of bytes %d\n", MALLOCED(osh));
+#ifndef BCMDBG_MEM
+ ASSERT(0);
+#endif
+ }
+
+#ifdef BCMDBG_MEM
+
+ MALLOC_DUMP(osh, NULL);
+#endif
+
+ osl_detach(osh);
+}
+
+static int
+wl_open(struct net_device *dev)
+{
+ wl_info_t *wl;
+ int error = 0;
+
+ if (!dev)
+ return -ENETDOWN;
+
+ wl = WL_INFO(dev);
+
+ WL_TRACE(("wl%d: wl_open\n", wl->pub->unit));
+
+ WL_LOCK(wl);
+ WL_APSTA_UPDN(("wl%d: (%s): wl_open() -> wl_up()\n",
+ wl->pub->unit, wl->dev->name));
+
+ error = wl_up(wl);
+ if (!error) {
+ error = wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC));
+ }
+ WL_UNLOCK(wl);
+
+ if (!error)
+ OLD_MOD_INC_USE_COUNT;
+
+#if defined(USE_CFG80211)
+ if (wl_cfg80211_up(dev)) {
+ WL_ERROR(("%s: failed to bring up cfg80211\n", __func__));
+ return -1;
+ }
+#endif
+ return (error? -ENODEV : 0);
+}
+
+static int
+wl_close(struct net_device *dev)
+{
+ wl_info_t *wl;
+
+ if (!dev)
+ return -ENETDOWN;
+
+#if defined(USE_CFG80211)
+ wl_cfg80211_down(dev);
+#endif
+ wl = WL_INFO(dev);
+
+ WL_TRACE(("wl%d: wl_close\n", wl->pub->unit));
+
+ WL_LOCK(wl);
+ WL_APSTA_UPDN(("wl%d (%s): wl_close() -> wl_down()\n",
+ wl->pub->unit, wl->dev->name));
+
+ if (wl->if_list == NULL) {
+ wl_down(wl);
+ }
+ WL_UNLOCK(wl);
+
+ OLD_MOD_DEC_USE_COUNT;
+
+ return (0);
+}
+
+void * BCMFASTPATH
+wl_get_ifctx(struct wl_info *wl, int ctx_id, wl_if_t *wlif)
+{
+ void *ifctx = NULL;
+
+ switch (ctx_id) {
+ case IFCTX_NETDEV:
+ ifctx = (void *)((wlif == NULL) ? wl->dev : wlif->dev);
+ break;
+
+ default:
+ break;
+ }
+
+ return ifctx;
+}
+
+static int BCMFASTPATH
+wl_start_int(wl_info_t *wl, wl_if_t *wlif, struct sk_buff *skb)
+{
+ void *pkt;
+
+ WL_TRACE(("wl%d: wl_start: len %d data_len %d summed %d csum: 0x%x\n",
+ wl->pub->unit, skb->len, skb->data_len, skb->ip_summed, (uint32)skb->csum));
+
+ WL_LOCK(wl);
+
+ pkt = PKTFRMNATIVE(wl->osh, skb);
+ ASSERT(pkt != NULL);
+
+ if (WME_ENAB(wl->pub) && (PKTPRIO(pkt) == 0))
+ pktsetprio(pkt, FALSE);
+
+ wlc_sendpkt(wl->wlc, pkt, wlif->wlcif);
+
+ WL_UNLOCK(wl);
+
+ return (0);
+}
+
+void
+wl_txflowcontrol(wl_info_t *wl, struct wl_if *wlif, bool state, int prio)
+{
+ struct net_device *dev;
+
+ ASSERT(prio == ALLPRIO);
+
+ if (wlif == NULL)
+ dev = wl->dev;
+ else if (!wlif->dev_registed)
+ return;
+ else
+ dev = wlif->dev;
+
+ if (state == ON)
+ netif_stop_queue(dev);
+ else
+ netif_wake_queue(dev);
+}
+
+static int
+wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *task), void *context)
+{
+ wl_task_t *task;
+
+ WL_TRACE(("wl%d: wl_schedule_task\n", wl->pub->unit));
+
+ if (!(task = MALLOC(wl->osh, sizeof(wl_task_t)))) {
+ WL_ERROR(("wl%d: wl_schedule_task: out of memory, malloced %d bytes\n",
+ wl->pub->unit, MALLOCED(wl->osh)));
+ return -ENOMEM;
+ }
+
+ MY_INIT_WORK(&task->work, (work_func_t)fn);
+ task->context = context;
+
+ if (!schedule_work(&task->work)) {
+ WL_ERROR(("wl%d: schedule_work() failed\n", wl->pub->unit));
+ MFREE(wl->osh, task, sizeof(wl_task_t));
+ return -ENOMEM;
+ }
+
+ atomic_inc(&wl->callbacks);
+
+ return 0;
+}
+
+static struct wl_if *
+wl_alloc_if(wl_info_t *wl, int iftype, uint subunit, struct wlc_if *wlcif)
+{
+ wl_if_t *wlif;
+ wl_if_t *p;
+
+ if (!(wlif = MALLOC(wl->osh, sizeof(wl_if_t)))) {
+ WL_ERROR(("wl%d: wl_alloc_if: out of memory, malloced %d bytes\n",
+ (wl->pub)?wl->pub->unit:subunit, MALLOCED(wl->osh)));
+ return NULL;
+ }
+ bzero(wlif, sizeof(wl_if_t));
+ wlif->wl = wl;
+ wlif->wlcif = wlcif;
+ wlif->subunit = subunit;
+ wlif->if_type = iftype;
+
+ if (wl->if_list == NULL)
+ wl->if_list = wlif;
+ else {
+ p = wl->if_list;
+ while (p->next != NULL)
+ p = p->next;
+ p->next = wlif;
+ }
+
+ return wlif;
+}
+
+static void
+wl_free_if(wl_info_t *wl, wl_if_t *wlif)
+{
+ wl_if_t *p;
+ ASSERT(wlif);
+ ASSERT(wl);
+
+ WL_TRACE(("%s\n", __FUNCTION__));
+
+ if (wlif->dev_registed) {
+ ASSERT(wlif->dev);
+ unregister_netdev(wlif->dev);
+ wlif->dev_registed = FALSE;
+ }
+
+#if defined(USE_CFG80211)
+ wl_cfg80211_detach(wlif->dev);
+#endif
+
+ p = wl->if_list;
+ if (p == wlif)
+ wl->if_list = p->next;
+ else {
+ while (p != NULL && p->next != wlif)
+ p = p->next;
+ if (p != NULL)
+ p->next = p->next->next;
+ }
+
+ if (wlif->dev) {
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24))
+ MFREE(wl->osh, wlif->dev->priv, sizeof(priv_link_t));
+ MFREE(wl->osh, wlif->dev, sizeof(struct net_device));
+#else
+ free_netdev(wlif->dev);
+ wlif->dev = NULL;
+#endif
+ }
+
+ MFREE(wl->osh, wlif, sizeof(wl_if_t));
+}
+
+static struct net_device *
+wl_alloc_linux_if(wl_if_t *wlif)
+{
+ wl_info_t *wl = wlif->wl;
+ struct net_device *dev;
+ priv_link_t *priv_link;
+
+ WL_TRACE(("%s\n", __FUNCTION__));
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24))
+ dev = MALLOC(wl->osh, sizeof(struct net_device));
+ if (!dev) {
+ WL_ERROR(("wl%d: %s: malloc of net_device failed\n",
+ (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__));
+ return NULL;
+ }
+ bzero(dev, sizeof(struct net_device));
+ ether_setup(dev);
+
+ strncpy(dev->name, intf_name, IFNAMSIZ-1);
+ dev->name[IFNAMSIZ-1] = '\0';
+
+ priv_link = MALLOC(wl->osh, sizeof(priv_link_t));
+ if (!priv_link) {
+ WL_ERROR(("wl%d: %s: malloc of priv_link failed\n",
+ (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__));
+ MFREE(wl->osh, dev, sizeof(struct net_device));
+ return NULL;
+ }
+ dev->priv = priv_link;
+#else
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
+ dev = alloc_netdev(sizeof(priv_link_t), intf_name, ether_setup);
+#else
+ dev = alloc_netdev(sizeof(priv_link_t), intf_name, NET_NAME_UNKNOWN, ether_setup);
+#endif
+
+ if (!dev) {
+ WL_ERROR(("wl%d: %s: alloc_netdev failed\n",
+ (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__));
+ return NULL;
+ }
+ priv_link = netdev_priv(dev);
+ if (!priv_link) {
+ WL_ERROR(("wl%d: %s: cannot get netdev_priv\n",
+ (wl->pub)?wl->pub->unit:wlif->subunit, __FUNCTION__));
+ return NULL;
+ }
+#endif
+
+ priv_link->wlif = wlif;
+ wlif->dev = dev;
+
+ if (wlif->if_type != WL_IFTYPE_MON && wl->dev && netif_queue_stopped(wl->dev))
+ netif_stop_queue(dev);
+
+ return dev;
+}
+
+char *
+wl_ifname(wl_info_t *wl, wl_if_t *wlif)
+{
+ if (wlif) {
+ return wlif->name;
+ } else {
+ return wl->dev->name;
+ }
+}
+
+void
+wl_init(wl_info_t *wl)
+{
+ WL_TRACE(("wl%d: wl_init\n", wl->pub->unit));
+
+ wl_reset(wl);
+
+ wlc_init(wl->wlc);
+}
+
+uint
+wl_reset(wl_info_t *wl)
+{
+ uint32 macintmask;
+
+ WL_TRACE(("wl%d: wl_reset\n", wl->pub->unit));
+
+ macintmask = wl_intrsoff(wl);
+
+ wlc_reset(wl->wlc);
+
+ wl_intrsrestore(wl, macintmask);
+
+ wl->resched = 0;
+
+ return (0);
+}
+
+void BCMFASTPATH
+wl_intrson(wl_info_t *wl)
+{
+ unsigned long flags = 0;
+
+ INT_LOCK(wl, flags);
+ wlc_intrson(wl->wlc);
+ INT_UNLOCK(wl, flags);
+}
+
+bool
+wl_alloc_dma_resources(wl_info_t *wl, uint addrwidth)
+{
+ return TRUE;
+}
+
+uint32 BCMFASTPATH
+wl_intrsoff(wl_info_t *wl)
+{
+ unsigned long flags = 0;
+ uint32 status;
+
+ INT_LOCK(wl, flags);
+ status = wlc_intrsoff(wl->wlc);
+ INT_UNLOCK(wl, flags);
+ return status;
+}
+
+void
+wl_intrsrestore(wl_info_t *wl, uint32 macintmask)
+{
+ unsigned long flags = 0;
+
+ INT_LOCK(wl, flags);
+ wlc_intrsrestore(wl->wlc, macintmask);
+ INT_UNLOCK(wl, flags);
+}
+
+int
+wl_up(wl_info_t *wl)
+{
+ int error = 0;
+ wl_if_t *wlif;
+
+ WL_TRACE(("wl%d: wl_up\n", wl->pub->unit));
+
+ if (wl->pub->up)
+ return (0);
+
+ error = wlc_up(wl->wlc);
+
+ if (!error) {
+ for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) {
+ wl_txflowcontrol(wl, wlif, OFF, ALLPRIO);
+ }
+ }
+
+ return (error);
+}
+
+void
+wl_down(wl_info_t *wl)
+{
+ wl_if_t *wlif;
+ int monitor = 0;
+ uint callbacks, ret_val = 0;
+
+ WL_TRACE(("wl%d: wl_down\n", wl->pub->unit));
+
+ for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) {
+ if (wlif->dev) {
+ netif_down(wlif->dev);
+ netif_stop_queue(wlif->dev);
+ }
+ }
+
+ if (wl->monitor_dev) {
+ ret_val = wlc_ioctl(wl->wlc, WLC_SET_MONITOR, &monitor, sizeof(int), NULL);
+ if (ret_val != BCME_OK) {
+ WL_ERROR(("%s: Disabling MONITOR failed %d\n", __FUNCTION__, ret_val));
+ }
+ }
+
+ if (wl->wlc)
+ ret_val = wlc_down(wl->wlc);
+
+ callbacks = atomic_read(&wl->callbacks) - ret_val;
+ BCM_REFERENCE(callbacks);
+
+ WL_UNLOCK(wl);
+
+ if (WL_ALL_PASSIVE_ENAB(wl)) {
+ int i = 0;
+ for (i = 0; (atomic_read(&wl->callbacks) > callbacks) && i < 10000; i++) {
+ schedule();
+ flush_scheduled_work();
+ }
+ }
+ else
+ {
+
+ SPINWAIT((atomic_read(&wl->callbacks) > callbacks), 100 * 1000);
+ }
+
+ WL_LOCK(wl);
+}
+
+static int
+wl_toe_get(wl_info_t *wl, uint32 *toe_ol)
+{
+ if (wlc_iovar_getint(wl->wlc, "toe_ol", toe_ol) != 0)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int
+wl_toe_set(wl_info_t *wl, uint32 toe_ol)
+{
+ if (wlc_iovar_setint(wl->wlc, "toe_ol", toe_ol) != 0)
+ return -EOPNOTSUPP;
+
+ if (wlc_iovar_setint(wl->wlc, "toe", (toe_ol != 0)) != 0)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static void
+wl_get_driver_info(struct net_device *dev, struct ethtool_drvinfo *info)
+{
+ wl_info_t *wl = WL_INFO(dev);
+
+#if WIRELESS_EXT >= 19 || LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
+ if (!wl || !wl->pub || !wl->wlc || !wl->dev)
+ return;
+#endif
+ bzero(info, sizeof(struct ethtool_drvinfo));
+ snprintf(info->driver, sizeof(info->driver), "wl%d", wl->pub->unit);
+ strncpy(info->version, EPI_VERSION_STR, sizeof(info->version));
+ info->version[(sizeof(info->version))-1] = '\0';
+}
+
+static int
+wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif)
+{
+ struct ethtool_drvinfo info;
+ struct ethtool_value edata;
+ uint32 cmd;
+ uint32 toe_cmpnt = 0, csum_dir;
+ int ret;
+
+ if (!wl || !wl->pub || !wl->wlc)
+ return -ENODEV;
+
+ WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__));
+
+ if (copy_from_user(&cmd, uaddr, sizeof(uint32)))
+ return (-EFAULT);
+
+ switch (cmd) {
+ case ETHTOOL_GDRVINFO:
+ if (!wl->dev)
+ return -ENETDOWN;
+
+ wl_get_driver_info(wl->dev, &info);
+ info.cmd = cmd;
+ if (copy_to_user(uaddr, &info, sizeof(info)))
+ return (-EFAULT);
+ break;
+
+ case ETHTOOL_GRXCSUM:
+ case ETHTOOL_GTXCSUM:
+ if ((ret = wl_toe_get(wl, &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;
+
+ case ETHTOOL_SRXCSUM:
+ case ETHTOOL_STXCSUM:
+ if (copy_from_user(&edata, uaddr, sizeof(edata)))
+ return (-EFAULT);
+
+ if ((ret = wl_toe_get(wl, &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 = wl_toe_set(wl, toe_cmpnt)) < 0)
+ return ret;
+
+ if (cmd == ETHTOOL_STXCSUM) {
+ if (!wl->dev)
+ return -ENETDOWN;
+ if (edata.data)
+ wl->dev->features |= NETIF_F_IP_CSUM;
+ else
+ wl->dev->features &= ~NETIF_F_IP_CSUM;
+ }
+
+ break;
+
+ default:
+ return (-EOPNOTSUPP);
+
+ }
+
+ return (0);
+}
+
+int
+wl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ wl_info_t *wl;
+ wl_if_t *wlif;
+ void *buf = NULL;
+ wl_ioctl_t ioc;
+ int bcmerror;
+
+ if (!dev)
+ return -ENETDOWN;
+
+ wl = WL_INFO(dev);
+ wlif = WL_DEV_IF(dev);
+ if (wlif == NULL || wl == NULL || wl->dev == NULL)
+ return -ENETDOWN;
+
+ bcmerror = 0;
+
+ WL_TRACE(("wl%d: wl_ioctl: cmd 0x%x\n", wl->pub->unit, cmd));
+
+#ifdef USE_IW
+
+ if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST)) {
+
+ return wl_iw_ioctl(dev, ifr, cmd);
+ }
+#endif
+
+ if (cmd == SIOCETHTOOL)
+ return (wl_ethtool(wl, (void*)ifr->ifr_data, wlif));
+
+ switch (cmd) {
+ case SIOCDEVPRIVATE :
+ break;
+ default:
+ bcmerror = BCME_UNSUPPORTED;
+ goto done2;
+ }
+
+ if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t))) {
+ bcmerror = BCME_BADADDR;
+ goto done2;
+ }
+
+ if (segment_eq(get_fs(), KERNEL_DS))
+ buf = ioc.buf;
+
+ else if (ioc.buf) {
+ if (!(buf = (void *) MALLOC(wl->osh, MAX(ioc.len, WLC_IOCTL_MAXLEN)))) {
+ bcmerror = BCME_NORESOURCE;
+ goto done2;
+ }
+
+ if (copy_from_user(buf, ioc.buf, ioc.len)) {
+ bcmerror = BCME_BADADDR;
+ goto done1;
+ }
+ }
+
+ WL_LOCK(wl);
+ if (!capable(CAP_NET_ADMIN)) {
+ bcmerror = BCME_EPERM;
+ } else {
+ bcmerror = wlc_ioctl(wl->wlc, ioc.cmd, buf, ioc.len, wlif->wlcif);
+ }
+ WL_UNLOCK(wl);
+
+done1:
+ if (ioc.buf && (ioc.buf != buf)) {
+ if (copy_to_user(ioc.buf, buf, ioc.len))
+ bcmerror = BCME_BADADDR;
+ MFREE(wl->osh, buf, MAX(ioc.len, WLC_IOCTL_MAXLEN));
+ }
+
+done2:
+ ASSERT(VALID_BCMERROR(bcmerror));
+ if (bcmerror != 0)
+ wl->pub->bcmerror = bcmerror;
+ return (OSL_ERROR(bcmerror));
+}
+
+static struct net_device_stats*
+wl_get_stats(struct net_device *dev)
+{
+ struct net_device_stats *stats_watchdog = NULL;
+ struct net_device_stats *stats = NULL;
+ wl_info_t *wl;
+ wl_if_t *wlif;
+
+ if (!dev)
+ return NULL;
+
+ if ((wl = WL_INFO(dev)) == NULL)
+ return NULL;
+
+ if ((wlif = WL_DEV_IF(dev)) == NULL)
+ return NULL;
+
+ if ((stats = &wlif->stats) == NULL)
+ return NULL;
+
+ WL_TRACE(("wl%d: wl_get_stats\n", wl->pub->unit));
+
+ ASSERT(wlif->stats_id < 2);
+ stats_watchdog = &wlif->stats_watchdog[wlif->stats_id];
+ memcpy(stats, stats_watchdog, sizeof(struct net_device_stats));
+ return (stats);
+}
+
+#ifdef USE_IW
+struct iw_statistics *
+wl_get_wireless_stats(struct net_device *dev)
+{
+ int res = 0;
+ wl_info_t *wl;
+ wl_if_t *wlif;
+ struct iw_statistics *wstats = NULL;
+ struct iw_statistics *wstats_watchdog = NULL;
+ int phy_noise, rssi;
+
+ if (!dev)
+ return NULL;
+
+ if ((wl = WL_INFO(dev)) == NULL)
+ return NULL;
+
+ if ((wlif = WL_DEV_IF(dev)) == NULL)
+ return NULL;
+
+ if ((wstats = &wlif->wstats) == NULL)
+ return NULL;
+
+ WL_TRACE(("wl%d: wl_get_wireless_stats\n", wl->pub->unit));
+
+ ASSERT(wlif->stats_id < 2);
+ wstats_watchdog = &wlif->wstats_watchdog[wlif->stats_id];
+
+ phy_noise = wlif->phy_noise;
+#if WIRELESS_EXT > 11
+ wstats->discard.nwid = 0;
+ wstats->discard.code = wstats_watchdog->discard.code;
+ wstats->discard.fragment = wstats_watchdog->discard.fragment;
+ wstats->discard.retries = wstats_watchdog->discard.retries;
+ wstats->discard.misc = wstats_watchdog->discard.misc;
+
+ wstats->miss.beacon = 0;
+#endif
+
+ if (AP_ENAB(wl->pub))
+ rssi = 0;
+ else {
+ scb_val_t scb;
+ res = wlc_ioctl(wl->wlc, WLC_GET_RSSI, &scb, sizeof(int), wlif->wlcif);
+ if (res) {
+ WL_ERROR(("wl%d: %s: WLC_GET_RSSI failed (%d)\n",
+ wl->pub->unit, __FUNCTION__, res));
+ return NULL;
+ }
+ rssi = scb.val;
+ }
+
+ if (rssi <= WLC_RSSI_NO_SIGNAL)
+ wstats->qual.qual = 0;
+ else if (rssi <= WLC_RSSI_VERY_LOW)
+ wstats->qual.qual = 1;
+ else if (rssi <= WLC_RSSI_LOW)
+ wstats->qual.qual = 2;
+ else if (rssi <= WLC_RSSI_GOOD)
+ wstats->qual.qual = 3;
+ else if (rssi <= WLC_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
+
+ return wstats;
+}
+#endif
+
+static int
+wl_set_mac_address(struct net_device *dev, void *addr)
+{
+ int err = 0;
+ wl_info_t *wl;
+ struct sockaddr *sa = (struct sockaddr *) addr;
+
+ if (!dev)
+ return -ENETDOWN;
+
+ wl = WL_INFO(dev);
+
+ WL_TRACE(("wl%d: wl_set_mac_address\n", wl->pub->unit));
+
+ WL_LOCK(wl);
+
+ bcopy(sa->sa_data, dev->dev_addr, ETHER_ADDR_LEN);
+ err = wlc_iovar_op(wl->wlc, "cur_etheraddr", NULL, 0, sa->sa_data, ETHER_ADDR_LEN,
+ IOV_SET, (WL_DEV_IF(dev))->wlcif);
+ WL_UNLOCK(wl);
+ if (err)
+ WL_ERROR(("wl%d: wl_set_mac_address: error setting MAC addr override\n",
+ wl->pub->unit));
+ return err;
+}
+
+static void
+wl_set_multicast_list(struct net_device *dev)
+{
+ if (!WL_ALL_PASSIVE_ENAB((wl_info_t *)WL_INFO(dev)))
+ _wl_set_multicast_list(dev);
+ else {
+ wl_info_t *wl = WL_INFO(dev);
+ wl->multicast_task.context = dev;
+
+ if (schedule_work(&wl->multicast_task.work)) {
+
+ atomic_inc(&wl->callbacks);
+ }
+ }
+}
+
+static void
+_wl_set_multicast_list(struct net_device *dev)
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 34)
+ struct dev_mc_list *mclist;
+#else
+ struct netdev_hw_addr *ha;
+#endif
+ wl_info_t *wl;
+ int i, buflen;
+ struct maclist *maclist;
+ int allmulti;
+
+ if (!dev)
+ return;
+ wl = WL_INFO(dev);
+
+ WL_TRACE(("wl%d: wl_set_multicast_list\n", wl->pub->unit));
+
+ if (wl->pub->up) {
+ allmulti = (dev->flags & IFF_ALLMULTI)? TRUE: FALSE;
+
+ buflen = sizeof(struct maclist) + (MAXMULTILIST * ETHER_ADDR_LEN);
+
+ if ((maclist = MALLOC(wl->pub->osh, buflen)) == NULL) {
+ return;
+ }
+
+ i = 0;
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 34)
+ for (mclist = dev->mc_list; mclist && (i < dev->mc_count); mclist = mclist->next) {
+ if (i >= MAXMULTILIST) {
+ allmulti = TRUE;
+ i = 0;
+ break;
+ }
+ bcopy(mclist->dmi_addr, &maclist->ea[i++], ETHER_ADDR_LEN);
+ }
+#else
+ netdev_for_each_mc_addr(ha, dev) {
+ if (i >= MAXMULTILIST) {
+ allmulti = TRUE;
+ i = 0;
+ break;
+ }
+ bcopy(ha->addr, &maclist->ea[i++], ETHER_ADDR_LEN);
+ }
+#endif
+ maclist->count = i;
+
+ WL_LOCK(wl);
+
+ wlc_iovar_op(wl->wlc, "allmulti", NULL, 0, &allmulti, sizeof(allmulti), IOV_SET,
+ (WL_DEV_IF(dev))->wlcif);
+ wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC));
+
+ wlc_iovar_op(wl->wlc, "mcast_list", NULL, 0, maclist, buflen, IOV_SET,
+ (WL_DEV_IF(dev))->wlcif);
+
+ WL_UNLOCK(wl);
+ MFREE(wl->pub->osh, maclist, buflen);
+ }
+
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
+irqreturn_t BCMFASTPATH
+wl_isr(int irq, void *dev_id)
+#else
+irqreturn_t BCMFASTPATH
+wl_isr(int irq, void *dev_id, struct pt_regs *ptregs)
+#endif
+{
+ wl_info_t *wl;
+ bool ours, wantdpc;
+ unsigned long flags;
+
+ wl = (wl_info_t*) dev_id;
+
+ WL_ISRLOCK(wl, flags);
+
+ if ((ours = wlc_isr(wl->wlc, &wantdpc))) {
+
+ if (wantdpc) {
+
+ ASSERT(wl->resched == FALSE);
+ if (WL_ALL_PASSIVE_ENAB(wl)) {
+ if (schedule_work(&wl->wl_dpc_task.work))
+ atomic_inc(&wl->callbacks);
+ else
+ ASSERT(0);
+ } else
+ tasklet_schedule(&wl->tasklet);
+ }
+ }
+
+ WL_ISRUNLOCK(wl, flags);
+
+ return IRQ_RETVAL(ours);
+}
+
+static void BCMFASTPATH
+wl_dpc(ulong data)
+{
+ wl_info_t *wl;
+
+ wl = (wl_info_t *)data;
+
+ WL_LOCK(wl);
+
+ if (wl->pub->up) {
+ wlc_dpc_info_t dpci = {0};
+
+ if (wl->resched) {
+ unsigned long flags = 0;
+ INT_LOCK(wl, flags);
+ wlc_intrsupd(wl->wlc);
+ INT_UNLOCK(wl, flags);
+ }
+
+ wl->resched = wlc_dpc(wl->wlc, TRUE, &dpci);
+
+ wl->processed = dpci.processed;
+ }
+
+ if (!wl->pub->up) {
+
+ if ((WL_ALL_PASSIVE_ENAB(wl))) {
+ atomic_dec(&wl->callbacks);
+ }
+ goto done;
+ }
+
+ if (wl->resched) {
+ if (!(WL_ALL_PASSIVE_ENAB(wl)))
+ tasklet_schedule(&wl->tasklet);
+ else
+ if (!schedule_work(&wl->wl_dpc_task.work)) {
+
+ ASSERT(0);
+ }
+ }
+ else {
+
+ if (WL_ALL_PASSIVE_ENAB(wl))
+ atomic_dec(&wl->callbacks);
+ wl_intrson(wl);
+ }
+
+done:
+ WL_UNLOCK(wl);
+ return;
+}
+
+static void BCMFASTPATH
+wl_dpc_rxwork(struct wl_task *task)
+{
+ wl_info_t *wl = (wl_info_t *)task->context;
+ WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__));
+
+ wl_dpc((unsigned long)wl);
+ return;
+}
+
+void BCMFASTPATH
+wl_sendup(wl_info_t *wl, wl_if_t *wlif, void *p, int numpkt)
+{
+ struct sk_buff *skb;
+ bool brcm_specialpkt;
+
+ WL_TRACE(("wl%d: wl_sendup: %d bytes\n", wl->pub->unit, PKTLEN(wl->osh, p)));
+
+ brcm_specialpkt =
+ (ntoh16_ua(PKTDATA(wl->pub->osh, p) + ETHER_TYPE_OFFSET) == ETHER_TYPE_BRCM);
+
+ if (!brcm_specialpkt) {
+
+ }
+
+ if (wlif) {
+
+ if (!wlif->dev || !netif_device_present(wlif->dev)) {
+ WL_ERROR(("wl%d: wl_sendup: interface not ready\n", wl->pub->unit));
+ PKTFREE(wl->osh, p, FALSE);
+ return;
+ }
+
+ skb = PKTTONATIVE(wl->osh, p);
+ skb->dev = wlif->dev;
+ } else {
+
+ skb = PKTTONATIVE(wl->osh, p);
+ skb->dev = wl->dev;
+
+ }
+
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
+ if (!brcm_specialpkt && !ISALIGNED(skb->data, 4)) {
+ WL_ERROR(("Unaligned assert. skb %p. skb->data %p.\n", skb, skb->data));
+ if (wlif) {
+ WL_ERROR(("wl_sendup: dev name is %s (wlif) \n", wlif->dev->name));
+ WL_ERROR(("wl_sendup: hard header len %d (wlif) \n",
+ wlif->dev->hard_header_len));
+ }
+ WL_ERROR(("wl_sendup: dev name is %s (wl) \n", wl->dev->name));
+ WL_ERROR(("wl_sendup: hard header len %d (wl) \n", wl->dev->hard_header_len));
+ ASSERT(ISALIGNED(skb->data, 4));
+ }
+
+ WL_APSTA_RX(("wl%d: wl_sendup(): pkt %p summed %d on interface %p (%s)\n",
+ wl->pub->unit, p, skb->ip_summed, wlif, skb->dev->name));
+
+ netif_rx(skb);
+
+}
+
+int
+wl_osl_pcie_rc(struct wl_info *wl, uint op, int param)
+{
+ return 0;
+}
+
+void
+wl_dump_ver(wl_info_t *wl, struct bcmstrbuf *b)
+{
+ bcm_bprintf(b, "wl%d: %s %s version %s\n", wl->pub->unit,
+ __DATE__, __TIME__, EPI_VERSION_STR);
+}
+
+#if defined(BCMDBG)
+static int
+wl_dump(wl_info_t *wl, struct bcmstrbuf *b)
+{
+ wl_if_t *p;
+ int i;
+
+ wl_dump_ver(wl, b);
+
+ bcm_bprintf(b, "name %s dev %p tbusy %d callbacks %d malloced %d\n",
+ wl->dev->name, wl->dev, (uint)netif_queue_stopped(wl->dev),
+ atomic_read(&wl->callbacks), MALLOCED(wl->osh));
+
+ p = wl->if_list;
+ if (p)
+ p = p->next;
+ for (i = 0; p != NULL; p = p->next, i++) {
+ if ((i % 4) == 0) {
+ if (i != 0)
+ bcm_bprintf(b, "\n");
+ bcm_bprintf(b, "Interfaces:");
+ }
+ bcm_bprintf(b, " name %s dev %p", p->dev->name, p->dev);
+ }
+ if (i)
+ bcm_bprintf(b, "\n");
+
+ return 0;
+}
+#endif
+
+static void
+wl_link_up(wl_info_t *wl, char *ifname)
+{
+ WL_ERROR(("wl%d: link up (%s)\n", wl->pub->unit, ifname));
+}
+
+static void
+wl_link_down(wl_info_t *wl, char *ifname)
+{
+ WL_ERROR(("wl%d: link down (%s)\n", wl->pub->unit, ifname));
+}
+
+void
+wl_event(wl_info_t *wl, char *ifname, wlc_event_t *e)
+{
+#ifdef USE_IW
+ wl_iw_event(wl->dev, &(e->event), e->data);
+#endif
+
+#if defined(USE_CFG80211)
+ wl_cfg80211_event(wl->dev, &(e->event), e->data);
+#endif
+ switch (e->event.event_type) {
+ case WLC_E_LINK:
+ case WLC_E_NDIS_LINK:
+ if (e->event.flags&WLC_EVENT_MSG_LINK)
+ wl_link_up(wl, ifname);
+ else
+ wl_link_down(wl, ifname);
+ break;
+#if defined(WL_CONFIG_RFKILL)
+ case WLC_E_RADIO: {
+ mbool i;
+ if (wlc_get(wl->wlc, WLC_GET_RADIO, &i) < 0)
+ WL_ERROR(("%s: WLC_GET_RADIO failed\n", __FUNCTION__));
+ if (wl->last_phyind == (mbool)(i & WL_RADIO_HW_DISABLE))
+ break;
+
+ wl->last_phyind = (mbool)(i & WL_RADIO_HW_DISABLE);
+
+ WL_ERROR(("wl%d: Radio hardware state changed to %d\n", wl->pub->unit, i));
+ wl_report_radio_state(wl);
+ break;
+ }
+#else
+ case WLC_E_RADIO:
+ break;
+#endif
+ }
+}
+
+void
+wl_event_sync(wl_info_t *wl, char *ifname, wlc_event_t *e)
+{
+}
+
+static void BCMFASTPATH
+wl_sched_tx_tasklet(void *t)
+{
+ wl_info_t *wl = (wl_info_t *)t;
+ tasklet_schedule(&wl->tx_tasklet);
+}
+
+#define WL_CONFIG_SMP() FALSE
+
+static int BCMFASTPATH
+wl_start(struct sk_buff *skb, struct net_device *dev)
+{
+ wl_if_t *wlif;
+ wl_info_t *wl;
+
+ if (!dev)
+ return -ENETDOWN;
+
+ wlif = WL_DEV_IF(dev);
+ wl = WL_INFO(dev);
+
+ if (WL_ALL_PASSIVE_ENAB(wl) || (WL_RTR() && WL_CONFIG_SMP())) {
+ skb->prev = NULL;
+
+ TXQ_LOCK(wl);
+
+ if ((wl_txq_thresh > 0) && (wl->txq_cnt >= wl_txq_thresh)) {
+ PKTFRMNATIVE(wl->osh, skb);
+ PKTCFREE(wl->osh, skb, TRUE);
+ TXQ_UNLOCK(wl);
+ return 0;
+ }
+
+ if (wl->txq_head == NULL)
+ wl->txq_head = skb;
+ else
+ wl->txq_tail->prev = skb;
+ wl->txq_tail = skb;
+ wl->txq_cnt++;
+
+ if (!wl->txq_dispatched) {
+ int32 err = 0;
+
+ if (!WL_ALL_PASSIVE_ENAB(wl))
+ wl_sched_tx_tasklet(wl);
+ else
+ err = (int32)(schedule_work(&wl->txq_task.work) == 0);
+
+ if (!err) {
+ atomic_inc(&wl->callbacks);
+ wl->txq_dispatched = TRUE;
+ } else
+ WL_ERROR(("wl%d: wl_start/schedule_work failed\n",
+ wl->pub->unit));
+ }
+
+ TXQ_UNLOCK(wl);
+ } else
+ return wl_start_int(wl, wlif, skb);
+
+ return (0);
+}
+
+static void BCMFASTPATH
+wl_start_txqwork(wl_task_t *task)
+{
+ wl_info_t *wl = (wl_info_t *)task->context;
+ struct sk_buff *skb;
+
+ WL_TRACE(("wl%d: %s txq_cnt %d\n", wl->pub->unit, __FUNCTION__, wl->txq_cnt));
+
+#ifdef BCMDBG
+ if (wl->txq_cnt >= 500)
+ WL_ERROR(("wl%d: WARNING dispatching over 500 packets in txqwork(%d)\n",
+ wl->pub->unit, wl->txq_cnt));
+#endif
+
+ TXQ_LOCK(wl);
+ while (wl->txq_head) {
+ skb = wl->txq_head;
+ wl->txq_head = skb->prev;
+ skb->prev = NULL;
+ if (wl->txq_head == NULL)
+ wl->txq_tail = NULL;
+ wl->txq_cnt--;
+ TXQ_UNLOCK(wl);
+
+ wl_start_int(wl, WL_DEV_IF(skb->dev), skb);
+
+ TXQ_LOCK(wl);
+ }
+
+ wl->txq_dispatched = FALSE;
+ atomic_dec(&wl->callbacks);
+ TXQ_UNLOCK(wl);
+
+ return;
+}
+
+static void BCMFASTPATH
+wl_tx_tasklet(ulong data)
+{
+ wl_task_t task;
+ task.context = (void *)data;
+ wl_start_txqwork(&task);
+}
+
+static void
+wl_txq_free(wl_info_t *wl)
+{
+ struct sk_buff *skb;
+
+ if (wl->txq_head == NULL) {
+ ASSERT(wl->txq_tail == NULL);
+ return;
+ }
+
+ while (wl->txq_head) {
+ skb = wl->txq_head;
+ wl->txq_head = skb->prev;
+ wl->txq_cnt--;
+ PKTFRMNATIVE(wl->osh, skb);
+ PKTCFREE(wl->osh, skb, TRUE);
+ }
+
+ wl->txq_tail = NULL;
+}
+
+static void
+wl_set_multicast_list_workitem(struct work_struct *work)
+{
+ wl_task_t *task = (wl_task_t *)work;
+ struct net_device *dev = (struct net_device*)task->context;
+ wl_info_t *wl;
+
+ wl = WL_INFO(dev);
+
+ atomic_dec(&wl->callbacks);
+
+ _wl_set_multicast_list(dev);
+}
+
+static void
+wl_timer_task(wl_task_t *task)
+{
+ wl_timer_t *t = (wl_timer_t *)task->context;
+
+ _wl_timer(t);
+ MFREE(t->wl->osh, task, sizeof(wl_task_t));
+
+ atomic_dec(&t->wl->callbacks);
+}
+
+static void
+wl_timer(ulong data)
+{
+ wl_timer_t *t = (wl_timer_t *)data;
+
+ if (!WL_ALL_PASSIVE_ENAB(t->wl))
+ _wl_timer(t);
+ else
+ wl_schedule_task(t->wl, wl_timer_task, t);
+}
+
+static void
+_wl_timer(wl_timer_t *t)
+{
+ wl_info_t *wl = t->wl;
+
+ WL_LOCK(wl);
+
+ if (t->set && (!timer_pending(&t->timer))) {
+ if (t->periodic) {
+ t->timer.expires = jiffies + t->ms*HZ/1000;
+ atomic_inc(&wl->callbacks);
+ add_timer(&t->timer);
+ t->set = TRUE;
+ } else
+ t->set = FALSE;
+
+ t->fn(t->arg);
+#ifdef BCMDBG
+ wlc_update_perf_stats(wl->wlc, WLC_PERF_STATS_TMR_DPC);
+ t->ticks++;
+#endif
+
+ }
+
+ atomic_dec(&wl->callbacks);
+
+ WL_UNLOCK(wl);
+}
+
+wl_timer_t *
+wl_init_timer(wl_info_t *wl, void (*fn)(void *arg), void *arg, const char *tname)
+{
+ wl_timer_t *t;
+
+ t = (wl_timer_t*)MALLOC(wl->osh, sizeof(wl_timer_t));
+
+ if (t == NULL) {
+ WL_ERROR(("wl%d: wl_init_timer: out of memory, malloced %d bytes\n",
+ wl->unit, MALLOCED(wl->osh)));
+ return 0;
+ }
+
+ bzero(t, sizeof(wl_timer_t));
+
+ init_timer(&t->timer);
+ t->timer.data = (ulong) t;
+ t->timer.function = wl_timer;
+ t->wl = wl;
+ t->fn = fn;
+ t->arg = arg;
+ t->next = wl->timers;
+ wl->timers = t;
+
+#ifdef BCMDBG
+ if ((t->name = MALLOC(wl->osh, strlen(tname) + 1)))
+ strcpy(t->name, tname);
+#endif
+
+ return t;
+}
+
+void
+wl_add_timer(wl_info_t *wl, wl_timer_t *t, uint ms, int periodic)
+{
+#ifdef BCMDBG
+ if (t->set) {
+ WL_ERROR(("%s: Already set. Name: %s, per %d\n",
+ __FUNCTION__, t->name, periodic));
+ }
+#endif
+
+ t->ms = ms;
+ t->periodic = (bool) periodic;
+
+ if (t->set)
+ return;
+
+ t->set = TRUE;
+ t->timer.expires = jiffies + ms*HZ/1000;
+
+ atomic_inc(&wl->callbacks);
+ add_timer(&t->timer);
+}
+
+bool
+wl_del_timer(wl_info_t *wl, wl_timer_t *t)
+{
+ ASSERT(t);
+ if (t->set) {
+ t->set = FALSE;
+ if (!del_timer(&t->timer)) {
+#ifdef BCMDBG
+ WL_INFORM(("wl%d: Failed to delete timer %s\n", wl->unit, t->name));
+#endif
+ return TRUE;
+ }
+ atomic_dec(&wl->callbacks);
+ }
+
+ return TRUE;
+}
+
+void
+wl_free_timer(wl_info_t *wl, wl_timer_t *t)
+{
+ wl_timer_t *tmp;
+
+ wl_del_timer(wl, t);
+
+ if (wl->timers == t) {
+ wl->timers = wl->timers->next;
+#ifdef BCMDBG
+ if (t->name)
+ MFREE(wl->osh, t->name, strlen(t->name) + 1);
+#endif
+ MFREE(wl->osh, t, sizeof(wl_timer_t));
+ return;
+
+ }
+
+ tmp = wl->timers;
+ while (tmp) {
+ if (tmp->next == t) {
+ tmp->next = t->next;
+#ifdef BCMDBG
+ if (t->name)
+ MFREE(wl->osh, t->name, strlen(t->name) + 1);
+#endif
+ MFREE(wl->osh, t, sizeof(wl_timer_t));
+ return;
+ }
+ tmp = tmp->next;
+ }
+
+}
+
+void
+wl_monitor(wl_info_t *wl, wl_rxsts_t *rxsts, void *p)
+{
+ struct sk_buff *oskb = (struct sk_buff *)p;
+ struct sk_buff *skb;
+ uchar *pdata;
+ uint len;
+
+ len = 0;
+ skb = NULL;
+ WL_TRACE(("wl%d: wl_monitor\n", wl->pub->unit));
+
+ if (!wl->monitor_dev)
+ return;
+
+ if (wl->monitor_type == 1) {
+ p80211msg_t *phdr;
+
+ len = sizeof(p80211msg_t) + oskb->len - D11_PHY_HDR_LEN;
+ if ((skb = dev_alloc_skb(len)) == NULL) {
+ WL_ERROR(("%s: dev_alloc_skb() failure, mon type 1", __FUNCTION__));
+ return;
+ }
+
+ skb_put(skb, len);
+ phdr = (p80211msg_t*)skb->data;
+
+ phdr->msgcode = WL_MON_FRAME;
+ phdr->msglen = sizeof(p80211msg_t);
+ strcpy(phdr->devname, wl->dev->name);
+
+ phdr->hosttime.did = WL_MON_FRAME_HOSTTIME;
+ phdr->hosttime.status = P80211ITEM_OK;
+ phdr->hosttime.len = 4;
+ phdr->hosttime.data = jiffies;
+
+ phdr->channel.did = WL_MON_FRAME_CHANNEL;
+ phdr->channel.status = P80211ITEM_NO_VALUE;
+ phdr->channel.len = 4;
+ phdr->channel.data = 0;
+
+ phdr->signal.did = WL_MON_FRAME_SIGNAL;
+ phdr->signal.status = P80211ITEM_OK;
+ phdr->signal.len = 4;
+
+ phdr->signal.data = rxsts->preamble;
+
+ phdr->noise.did = WL_MON_FRAME_NOISE;
+ phdr->noise.status = P80211ITEM_NO_VALUE;
+ phdr->noise.len = 4;
+ phdr->noise.data = 0;
+
+ phdr->rate.did = WL_MON_FRAME_RATE;
+ phdr->rate.status = P80211ITEM_OK;
+ phdr->rate.len = 4;
+ phdr->rate.data = rxsts->datarate;
+
+ phdr->istx.did = WL_MON_FRAME_ISTX;
+ phdr->istx.status = P80211ITEM_NO_VALUE;
+ phdr->istx.len = 4;
+ phdr->istx.data = 0;
+
+ phdr->mactime.did = WL_MON_FRAME_MACTIME;
+ phdr->mactime.status = P80211ITEM_OK;
+ phdr->mactime.len = 4;
+ phdr->mactime.data = rxsts->mactime;
+
+ phdr->rssi.did = WL_MON_FRAME_RSSI;
+ phdr->rssi.status = P80211ITEM_OK;
+ phdr->rssi.len = 4;
+ phdr->rssi.data = rxsts->signal;
+
+ phdr->sq.did = WL_MON_FRAME_SQ;
+ phdr->sq.status = P80211ITEM_OK;
+ phdr->sq.len = 4;
+ phdr->sq.data = rxsts->sq;
+
+ phdr->frmlen.did = WL_MON_FRAME_FRMLEN;
+ phdr->frmlen.status = P80211ITEM_OK;
+ phdr->frmlen.status = P80211ITEM_OK;
+ phdr->frmlen.len = 4;
+ phdr->frmlen.data = rxsts->pktlength;
+
+ pdata = skb->data + sizeof(p80211msg_t);
+ bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN);
+
+ }
+ else if (wl->monitor_type == 2) {
+ int channel_frequency;
+ uint16 channel_flags;
+ uint8 flags;
+ uint16 rtap_len;
+ struct dot11_header *mac_header;
+ uint16 fc;
+
+ if (rxsts->phytype != WL_RXS_PHY_N)
+ rtap_len = sizeof(wl_radiotap_legacy_t);
+ else
+ rtap_len = sizeof(wl_radiotap_ht_brcm_2_t);
+
+ len = rtap_len + (oskb->len - D11_PHY_HDR_LEN);
+ if ((skb = dev_alloc_skb(len)) == NULL) {
+ WL_ERROR(("%s: dev_alloc_skb() failure, mon type 2", __FUNCTION__));
+ return;
+ }
+
+ skb_put(skb, len);
+
+ if (CHSPEC_IS2G(rxsts->chanspec)) {
+ channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN;
+ channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec),
+ WF_CHAN_FACTOR_2_4_G);
+ } else {
+ channel_flags = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM;
+ channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec),
+ WF_CHAN_FACTOR_5_G);
+ }
+
+ mac_header = (struct dot11_header *)(oskb->data + D11_PHY_HDR_LEN);
+ fc = ltoh16(mac_header->fc);
+
+ flags = IEEE80211_RADIOTAP_F_FCS;
+
+ if (rxsts->preamble == WL_RXS_PREAMBLE_SHORT)
+ flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+
+ if (fc & FC_WEP)
+ flags |= IEEE80211_RADIOTAP_F_WEP;
+
+ if (fc & FC_MOREFRAG)
+ flags |= IEEE80211_RADIOTAP_F_FRAG;
+
+ if (rxsts->pkterror & WL_RXS_CRC_ERROR)
+ flags |= IEEE80211_RADIOTAP_F_BADFCS;
+
+ if (rxsts->phytype != WL_RXS_PHY_N) {
+ wl_radiotap_legacy_t *rtl = (wl_radiotap_legacy_t *)skb->data;
+
+ rtl->ieee_radiotap.it_version = 0;
+ rtl->ieee_radiotap.it_pad = 0;
+ rtl->ieee_radiotap.it_len = HTOL16(rtap_len);
+ rtl->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_LEGACY);
+
+ rtl->tsft_l = htol32(rxsts->mactime);
+ rtl->tsft_h = 0;
+ rtl->flags = flags;
+ rtl->rate = rxsts->datarate;
+ rtl->channel_freq = HTOL16(channel_frequency);
+ rtl->channel_flags = HTOL16(channel_flags);
+ rtl->signal = (int8)rxsts->signal;
+ rtl->noise = (int8)rxsts->noise;
+ rtl->antenna = rxsts->antenna;
+
+ memcpy(rtl->vend_oui, brcm_oui, sizeof(brcm_oui));
+ rtl->vend_skip_len = WL_RADIOTAP_LEGACY_SKIP_LEN;
+ rtl->vend_sns = 0;
+
+ memset(&rtl->nonht_vht, 0, sizeof(rtl->nonht_vht));
+ rtl->nonht_vht.len = WL_RADIOTAP_NONHT_VHT_LEN;
+ } else {
+ wl_radiotap_ht_brcm_2_t *rtht = (wl_radiotap_ht_brcm_2_t *)skb->data;
+
+ rtht->ieee_radiotap.it_version = 0;
+ rtht->ieee_radiotap.it_pad = 0;
+ rtht->ieee_radiotap.it_len = HTOL16(rtap_len);
+ rtht->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_HT_BRCM2);
+ rtht->it_present_ext = HTOL32(WL_RADIOTAP_BRCM2_HT_MCS);
+ rtht->pad1 = 0;
+
+ rtht->tsft_l = htol32(rxsts->mactime);
+ rtht->tsft_h = 0;
+ rtht->flags = flags;
+ rtht->pad2 = 0;
+ rtht->channel_freq = HTOL16(channel_frequency);
+ rtht->channel_flags = HTOL16(channel_flags);
+ rtht->signal = (int8)rxsts->signal;
+ rtht->noise = (int8)rxsts->noise;
+ rtht->antenna = rxsts->antenna;
+ rtht->pad3 = 0;
+
+ memcpy(rtht->vend_oui, brcm_oui, sizeof(brcm_oui));
+ rtht->vend_sns = WL_RADIOTAP_BRCM2_HT_SNS;
+ rtht->vend_skip_len = WL_RADIOTAP_HT_BRCM2_SKIP_LEN;
+ rtht->mcs = rxsts->mcs;
+ rtht->htflags = 0;
+ if (rxsts->htflags & WL_RXS_HTF_40)
+ rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_40;
+ if (rxsts->htflags & WL_RXS_HTF_SGI)
+ rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_SGI;
+ if (rxsts->preamble & WL_RXS_PREAMBLE_HT_GF)
+ rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_GF;
+ if (rxsts->htflags & WL_RXS_HTF_LDPC)
+ rtht->htflags |= IEEE80211_RADIOTAP_HTMOD_LDPC;
+ rtht->htflags |=
+ (rxsts->htflags & WL_RXS_HTF_STBC_MASK) <<
+ IEEE80211_RADIOTAP_HTMOD_STBC_SHIFT;
+ }
+
+ pdata = skb->data + rtap_len;
+ bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN);
+ }
+ else if (wl->monitor_type == 3) {
+ int channel_frequency;
+ uint16 channel_flags;
+ uint8 flags;
+ uint16 rtap_len;
+ struct dot11_header * mac_header;
+ uint16 fc;
+
+ if (rxsts->phytype == WL_RXS_PHY_N) {
+ if (rxsts->encoding == WL_RXS_ENCODING_HT)
+ rtap_len = sizeof(wl_radiotap_ht_t);
+ else if (rxsts->encoding == WL_RXS_ENCODING_VHT)
+ rtap_len = sizeof(wl_radiotap_vht_t);
+ else
+ rtap_len = sizeof(wl_radiotap_legacy_t);
+ } else {
+ rtap_len = sizeof(wl_radiotap_legacy_t);
+ }
+
+ len = rtap_len + (oskb->len - D11_PHY_HDR_LEN);
+
+ if (oskb->next) {
+ struct sk_buff *amsdu_p = oskb->next;
+ uint amsdu_len = 0;
+ while (amsdu_p) {
+ amsdu_len += amsdu_p->len;
+ amsdu_p = amsdu_p->next;
+ }
+ len += amsdu_len;
+ }
+
+ if ((skb = dev_alloc_skb(len)) == NULL) {
+ WL_ERROR(("%s: dev_alloc_skb() failure, mon type 3", __FUNCTION__));
+ return;
+ }
+
+ skb_put(skb, len);
+
+ if (CHSPEC_IS2G(rxsts->chanspec)) {
+ channel_flags = IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_DYN;
+ channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec),
+ WF_CHAN_FACTOR_2_4_G);
+ } else {
+ channel_flags = IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_OFDM;
+ channel_frequency = wf_channel2mhz(wf_chspec_ctlchan(rxsts->chanspec),
+ WF_CHAN_FACTOR_5_G);
+ }
+
+ mac_header = (struct dot11_header *)(oskb->data + D11_PHY_HDR_LEN);
+ fc = ltoh16(mac_header->fc);
+
+ flags = IEEE80211_RADIOTAP_F_FCS;
+
+ if (rxsts->preamble == WL_RXS_PREAMBLE_SHORT)
+ flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+
+ if (fc & FC_WEP)
+ flags |= IEEE80211_RADIOTAP_F_WEP;
+
+ if (fc & FC_MOREFRAG)
+ flags |= IEEE80211_RADIOTAP_F_FRAG;
+
+ if (rxsts->pkterror & WL_RXS_CRC_ERROR)
+ flags |= IEEE80211_RADIOTAP_F_BADFCS;
+
+ if ((rxsts->phytype != WL_RXS_PHY_N) ||
+ ((rxsts->encoding != WL_RXS_ENCODING_HT) &&
+ (rxsts->encoding != WL_RXS_ENCODING_VHT))) {
+ wl_radiotap_legacy_t *rtl = (wl_radiotap_legacy_t *)skb->data;
+
+ rtl->ieee_radiotap.it_version = 0;
+ rtl->ieee_radiotap.it_pad = 0;
+ rtl->ieee_radiotap.it_len = HTOL16(rtap_len);
+ rtl->ieee_radiotap.it_present = HTOL32(WL_RADIOTAP_PRESENT_LEGACY);
+
+ rtl->it_present_ext = HTOL32(WL_RADIOTAP_LEGACY_VHT);
+ rtl->tsft_l = htol32(rxsts->mactime);
+ rtl->tsft_h = 0;
+ rtl->flags = flags;
+ rtl->rate = rxsts->datarate;
+ rtl->channel_freq = HTOL16(channel_frequency);
+ rtl->channel_flags = HTOL16(channel_flags);
+ rtl->signal = (int8)rxsts->signal;
+ rtl->noise = (int8)rxsts->noise;
+ rtl->antenna = rxsts->antenna;
+
+ memcpy(rtl->vend_oui, brcm_oui, sizeof(brcm_oui));
+ rtl->vend_skip_len = WL_RADIOTAP_LEGACY_SKIP_LEN;
+ rtl->vend_sns = 0;
+
+ memset(&rtl->nonht_vht, 0, sizeof(rtl->nonht_vht));
+ rtl->nonht_vht.len = WL_RADIOTAP_NONHT_VHT_LEN;
+ if (((fc & FC_KIND_MASK) == FC_RTS) ||
+ ((fc & FC_KIND_MASK) == FC_CTS)) {
+ rtl->nonht_vht.flags |= WL_RADIOTAP_F_NONHT_VHT_BW;
+ rtl->nonht_vht.bw = rxsts->bw_nonht;
+ rtl->vend_sns = WL_RADIOTAP_LEGACY_SNS;
+
+ }
+ if ((fc & FC_KIND_MASK) == FC_RTS) {
+ if (rxsts->vhtflags & WL_RXS_VHTF_DYN_BW_NONHT)
+ rtl->nonht_vht.flags
+ |= WL_RADIOTAP_F_NONHT_VHT_DYN_BW;
+ }
+ }
+ else if (rxsts->encoding == WL_RXS_ENCODING_VHT) {
+ wl_radiotap_vht_t *rtvht = (wl_radiotap_vht_t *)skb->data;
+
+ rtvht->ieee_radiotap.it_version = 0;
+ rtvht->ieee_radiotap.it_pad = 0;
+ rtvht->ieee_radiotap.it_len = HTOL16(rtap_len);
+ rtvht->ieee_radiotap.it_present =
+ HTOL32(WL_RADIOTAP_PRESENT_VHT);
+
+ rtvht->tsft_l = htol32(rxsts->mactime);
+ rtvht->tsft_h = 0;
+ rtvht->flags = flags;
+ rtvht->pad1 = 0;
+ rtvht->channel_freq = HTOL16(channel_frequency);
+ rtvht->channel_flags = HTOL16(channel_flags);
+ rtvht->signal = (int8)rxsts->signal;
+ rtvht->noise = (int8)rxsts->noise;
+ rtvht->antenna = rxsts->antenna;
+
+ rtvht->vht_known = (IEEE80211_RADIOTAP_VHT_HAVE_STBC |
+ IEEE80211_RADIOTAP_VHT_HAVE_TXOP_PS |
+ IEEE80211_RADIOTAP_VHT_HAVE_GI |
+ IEEE80211_RADIOTAP_VHT_HAVE_SGI_NSYM_DA |
+ IEEE80211_RADIOTAP_VHT_HAVE_LDPC_EXTRA |
+ IEEE80211_RADIOTAP_VHT_HAVE_BF |
+ IEEE80211_RADIOTAP_VHT_HAVE_BW |
+ IEEE80211_RADIOTAP_VHT_HAVE_GID |
+ IEEE80211_RADIOTAP_VHT_HAVE_PAID);
+
+ STATIC_ASSERT(WL_RXS_VHTF_STBC ==
+ IEEE80211_RADIOTAP_VHT_STBC);
+ STATIC_ASSERT(WL_RXS_VHTF_TXOP_PS ==
+ IEEE80211_RADIOTAP_VHT_TXOP_PS);
+ STATIC_ASSERT(WL_RXS_VHTF_SGI ==
+ IEEE80211_RADIOTAP_VHT_SGI);
+ STATIC_ASSERT(WL_RXS_VHTF_SGI_NSYM_DA ==
+ IEEE80211_RADIOTAP_VHT_SGI_NSYM_DA);
+ STATIC_ASSERT(WL_RXS_VHTF_LDPC_EXTRA ==
+ IEEE80211_RADIOTAP_VHT_LDPC_EXTRA);
+ STATIC_ASSERT(WL_RXS_VHTF_BF ==
+ IEEE80211_RADIOTAP_VHT_BF);
+
+ rtvht->vht_flags = HTOL16(rxsts->vhtflags);
+
+ STATIC_ASSERT(WL_RXS_VHT_BW_20 ==
+ IEEE80211_RADIOTAP_VHT_BW_20);
+ STATIC_ASSERT(WL_RXS_VHT_BW_40 ==
+ IEEE80211_RADIOTAP_VHT_BW_40);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20L ==
+ IEEE80211_RADIOTAP_VHT_BW_20L);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20U ==
+ IEEE80211_RADIOTAP_VHT_BW_20U);
+ STATIC_ASSERT(WL_RXS_VHT_BW_80 ==
+ IEEE80211_RADIOTAP_VHT_BW_80);
+ STATIC_ASSERT(WL_RXS_VHT_BW_40L ==
+ IEEE80211_RADIOTAP_VHT_BW_40L);
+ STATIC_ASSERT(WL_RXS_VHT_BW_40U ==
+ IEEE80211_RADIOTAP_VHT_BW_40U);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20LL ==
+ IEEE80211_RADIOTAP_VHT_BW_20LL);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20LU ==
+ IEEE80211_RADIOTAP_VHT_BW_20LU);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20UL ==
+ IEEE80211_RADIOTAP_VHT_BW_20UL);
+ STATIC_ASSERT(WL_RXS_VHT_BW_20UU ==
+ IEEE80211_RADIOTAP_VHT_BW_20UU);
+
+ rtvht->vht_bw = rxsts->bw;
+
+ rtvht->vht_mcs_nss[0] = (rxsts->mcs << 4) |
+ (rxsts->nss & IEEE80211_RADIOTAP_VHT_NSS);
+ rtvht->vht_mcs_nss[1] = 0;
+ rtvht->vht_mcs_nss[2] = 0;
+ rtvht->vht_mcs_nss[3] = 0;
+
+ STATIC_ASSERT(WL_RXS_VHTF_CODING_LDCP ==
+ IEEE80211_RADIOTAP_VHT_CODING_LDPC);
+
+ rtvht->vht_coding = rxsts->coding;
+ rtvht->vht_group_id = rxsts->gid;
+ rtvht->vht_partial_aid = HTOL16(rxsts->aid);
+
+ rtvht->ampdu_flags = 0;
+ rtvht->ampdu_delim_crc = 0;
+
+ rtvht->ampdu_ref_num = rxsts->ampdu_counter;
+
+ if (!(rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_FIRST) &&
+ !(rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_SUB))
+ rtvht->ampdu_flags |= IEEE80211_RADIOTAP_AMPDU_IS_LAST;
+
+ if (rxsts->nfrmtype & WL_RXS_NFRM_AMPDU_NONE)
+ rtvht->ampdu_flags |= IEEE80211_RADIOTAP_AMPDU_MPDU_ONLY;
+ }
+ else if (rxsts->encoding == WL_RXS_ENCODING_HT) {
+ wl_radiotap_ht_t *rtht =
+ (wl_radiotap_ht_t *)skb->data;
+
+ rtht->ieee_radiotap.it_version = 0;
+ rtht->ieee_radiotap.it_pad = 0;
+ rtht->ieee_radiotap.it_len = HTOL16(rtap_len);
+ rtht->ieee_radiotap.it_present
+ = HTOL32(WL_RADIOTAP_PRESENT_HT);
+ rtht->pad1 = 0;
+
+ rtht->tsft_l = htol32(rxsts->mactime);
+ rtht->tsft_h = 0;
+ rtht->flags = flags;
+ rtht->channel_freq = HTOL16(channel_frequency);
+ rtht->channel_flags = HTOL16(channel_flags);
+ rtht->signal = (int8)rxsts->signal;
+ rtht->noise = (int8)rxsts->noise;
+ rtht->antenna = rxsts->antenna;
+
+ rtht->mcs_known = (IEEE80211_RADIOTAP_MCS_HAVE_BW |
+ IEEE80211_RADIOTAP_MCS_HAVE_MCS |
+ IEEE80211_RADIOTAP_MCS_HAVE_GI |
+ IEEE80211_RADIOTAP_MCS_HAVE_FEC |
+ IEEE80211_RADIOTAP_MCS_HAVE_FMT);
+
+ rtht->mcs_flags = 0;
+ switch (rxsts->htflags & WL_RXS_HTF_BW_MASK) {
+ case WL_RXS_HTF_20L:
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20L;
+ break;
+ case WL_RXS_HTF_20U:
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20U;
+ break;
+ case WL_RXS_HTF_40:
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_40;
+ break;
+ default:
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_BW_20;
+ }
+
+ if (rxsts->htflags & WL_RXS_HTF_SGI) {
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_SGI;
+ }
+ if (rxsts->preamble & WL_RXS_PREAMBLE_HT_GF) {
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_FMT_GF;
+ }
+ if (rxsts->htflags & WL_RXS_HTF_LDPC) {
+ rtht->mcs_flags |= IEEE80211_RADIOTAP_MCS_FEC_LDPC;
+ }
+ rtht->mcs_index = rxsts->mcs;
+ }
+
+ pdata = skb->data + rtap_len;
+ bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN);
+
+ if (oskb->next) {
+ struct sk_buff *amsdu_p = oskb->next;
+ amsdu_p = oskb->next;
+ pdata += (oskb->len - D11_PHY_HDR_LEN);
+ while (amsdu_p) {
+ bcopy(amsdu_p->data, pdata, amsdu_p->len);
+ pdata += amsdu_p->len;
+ amsdu_p = amsdu_p->next;
+ }
+ }
+ }
+
+ if (skb == NULL) return;
+
+ skb->dev = wl->monitor_dev;
+ skb->dev->last_rx = jiffies;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
+ skb_reset_mac_header(skb);
+#else
+ skb->mac.raw = skb->data;
+#endif
+ skb->ip_summed = CHECKSUM_NONE;
+ skb->pkt_type = PACKET_OTHERHOST;
+ skb->protocol = htons(ETH_P_80211_RAW);
+
+ netif_rx(skb);
+}
+
+static int
+wl_monitor_start(struct sk_buff *skb, struct net_device *dev)
+{
+ wl_info_t *wl;
+
+ wl = WL_DEV_IF(dev)->wl;
+ PKTFREE(wl->osh, skb, FALSE);
+ return 0;
+}
+
+static void
+_wl_add_monitor_if(wl_task_t *task)
+{
+ struct net_device *dev;
+ wl_if_t *wlif = (wl_if_t *) task->context;
+ wl_info_t *wl = wlif->wl;
+
+ WL_TRACE(("wl%d: %s\n", wl->pub->unit, __FUNCTION__));
+ ASSERT(wl);
+ ASSERT(!wl->monitor_dev);
+
+ if ((dev = wl_alloc_linux_if(wlif)) == NULL) {
+ WL_ERROR(("wl%d: %s: wl_alloc_linux_if failed\n", wl->pub->unit, __FUNCTION__));
+ goto done;
+ }
+
+ ASSERT(strlen(wlif->name) > 0);
+ strncpy(wlif->dev->name, wlif->name, strlen(wlif->name));
+
+ wl->monitor_dev = dev;
+ if (wl->monitor_type == 1)
+ dev->type = ARPHRD_IEEE80211_PRISM;
+ else
+ dev->type = ARPHRD_IEEE80211_RADIOTAP;
+
+ bcopy(wl->dev->dev_addr, dev->dev_addr, ETHER_ADDR_LEN);
+
+#if defined(WL_USE_NETDEV_OPS)
+ dev->netdev_ops = &wl_netdev_monitor_ops;
+#else
+ dev->hard_start_xmit = wl_monitor_start;
+ dev->do_ioctl = wl_ioctl;
+ dev->get_stats = wl_get_stats;
+#endif
+
+ if (register_netdev(dev)) {
+ WL_ERROR(("wl%d: %s, register_netdev failed for %s\n",
+ wl->pub->unit, __FUNCTION__, wl->monitor_dev->name));
+ wl->monitor_dev = NULL;
+ goto done;
+ }
+ wlif->dev_registed = TRUE;
+
+done:
+ MFREE(wl->osh, task, sizeof(wl_task_t));
+ atomic_dec(&wl->callbacks);
+}
+
+static void
+_wl_del_monitor(wl_task_t *task)
+{
+ wl_info_t *wl = (wl_info_t *) task->context;
+
+ ASSERT(wl);
+ ASSERT(wl->monitor_dev);
+
+ WL_TRACE(("wl%d: _wl_del_monitor\n", wl->pub->unit));
+
+ wl_free_if(wl, WL_DEV_IF(wl->monitor_dev));
+ wl->monitor_dev = NULL;
+
+ MFREE(wl->osh, task, sizeof(wl_task_t));
+ atomic_dec(&wl->callbacks);
+}
+
+void
+wl_set_monitor(wl_info_t *wl, int val)
+{
+ const char *devname;
+ wl_if_t *wlif;
+
+ WL_TRACE(("wl%d: wl_set_monitor: val %d\n", wl->pub->unit, val));
+ if ((val && wl->monitor_dev) || (!val && !wl->monitor_dev)) {
+ WL_ERROR(("%s: Mismatched params, return\n", __FUNCTION__));
+ return;
+ }
+
+ if (!val) {
+ (void) wl_schedule_task(wl, _wl_del_monitor, wl);
+ return;
+ }
+
+ if (val >= 1 && val <= 3) {
+ wl->monitor_type = val;
+ } else {
+ WL_ERROR(("monitor type %d not supported\n", val));
+ ASSERT(0);
+ }
+
+ wlif = wl_alloc_if(wl, WL_IFTYPE_MON, wl->pub->unit, NULL);
+ if (!wlif) {
+ WL_ERROR(("wl%d: %s: alloc wlif failed\n", wl->pub->unit, __FUNCTION__));
+ return;
+ }
+
+ if (wl->monitor_type == 1)
+ devname = "prism";
+ else
+ devname = "radiotap";
+ sprintf(wlif->name, "%s%d", devname, wl->pub->unit);
+
+ if (wl_schedule_task(wl, _wl_add_monitor_if, wlif)) {
+ MFREE(wl->osh, wlif, sizeof(wl_if_t));
+ return;
+ }
+}
+
+#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15)
+const char *
+print_tainted()
+{
+ return "";
+}
+#endif
+
+struct net_device *
+wl_netdev_get(wl_info_t *wl)
+{
+ return wl->dev;
+}
+
+int
+wl_set_pktlen(osl_t *osh, void *p, int len)
+{
+ PKTSETLEN(osh, p, len);
+ return len;
+}
+
+void *
+wl_get_pktbuffer(osl_t *osh, int len)
+{
+ return (PKTGET(osh, len, FALSE));
+}
+
+uint
+wl_buf_to_pktcopy(osl_t *osh, void *p, uchar *buf, int len, uint offset)
+{
+ if (PKTLEN(osh, p) < len + offset)
+ return 0;
+ bcopy(buf, (char *)PKTDATA(osh, p) + offset, len);
+ return len;
+}
+
+#if defined(WL_CONFIG_RFKILL)
+
+static int
+wl_set_radio_block(void *data, bool blocked)
+{
+ wl_info_t *wl = data;
+ uint32 radioval;
+
+ WL_TRACE(("%s: kernel set blocked = %d\n", __FUNCTION__, blocked));
+
+ radioval = WL_RADIO_SW_DISABLE << 16 | blocked;
+
+ WL_LOCK(wl);
+
+ if (wlc_set(wl->wlc, WLC_SET_RADIO, radioval) < 0) {
+ WL_ERROR(("%s: SET_RADIO failed\n", __FUNCTION__));
+ return 1;
+ }
+
+ WL_UNLOCK(wl);
+
+ return 0;
+}
+
+static const struct rfkill_ops bcmwl_rfkill_ops = {
+ .set_block = wl_set_radio_block
+};
+
+static int
+wl_init_rfkill(wl_info_t *wl)
+{
+ int status;
+
+ snprintf(wl->wl_rfkill.rfkill_name, sizeof(wl->wl_rfkill.rfkill_name),
+ "brcmwl-%d", wl->pub->unit);
+
+ wl->wl_rfkill.rfkill = rfkill_alloc(wl->wl_rfkill.rfkill_name, &wl->dev->dev,
+ RFKILL_TYPE_WLAN, &bcmwl_rfkill_ops, wl);
+
+ if (!wl->wl_rfkill.rfkill) {
+ WL_ERROR(("%s: RFKILL: Failed to allocate rfkill\n", __FUNCTION__));
+ return -ENOMEM;
+ }
+
+ if (wlc_get(wl->wlc, WLC_GET_RADIO, &status) < 0) {
+ WL_ERROR(("%s: WLC_GET_RADIO failed\n", __FUNCTION__));
+ return 1;
+ }
+
+ rfkill_init_sw_state(wl->wl_rfkill.rfkill, status);
+
+ if (rfkill_register(wl->wl_rfkill.rfkill)) {
+ WL_ERROR(("%s: rfkill_register failed! \n", __FUNCTION__));
+ rfkill_destroy(wl->wl_rfkill.rfkill);
+ return 2;
+ }
+
+ WL_ERROR(("%s: rfkill registered\n", __FUNCTION__));
+ wl->wl_rfkill.registered = TRUE;
+ return 0;
+}
+
+static void
+wl_uninit_rfkill(wl_info_t *wl)
+{
+ if (wl->wl_rfkill.registered) {
+ rfkill_unregister(wl->wl_rfkill.rfkill);
+ rfkill_destroy(wl->wl_rfkill.rfkill);
+ wl->wl_rfkill.registered = FALSE;
+ wl->wl_rfkill.rfkill = NULL;
+ }
+}
+
+static void
+wl_report_radio_state(wl_info_t *wl)
+{
+ WL_TRACE(("%s: report radio state %d\n", __FUNCTION__, wl->last_phyind));
+
+ rfkill_set_hw_state(wl->wl_rfkill.rfkill, wl->last_phyind != 0);
+}
+
+#endif
+
+static int
+wl_linux_watchdog(void *ctx)
+{
+ wl_info_t *wl = (wl_info_t *) ctx;
+ struct net_device_stats *stats = NULL;
+ uint id;
+ wl_if_t *wlif;
+ wlc_if_stats_t wlcif_stats;
+#ifdef USE_IW
+ struct iw_statistics *wstats = NULL;
+ int phy_noise;
+#endif
+ if (wl == NULL)
+ return -1;
+
+ if (wl->if_list) {
+ for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) {
+ memset(&wlcif_stats, 0, sizeof(wlc_if_stats_t));
+ wlc_wlcif_stats_get(wl->wlc, wlif->wlcif, &wlcif_stats);
+
+ if (wl->pub->up) {
+ ASSERT(wlif->stats_id < 2);
+
+ id = 1 - wlif->stats_id;
+ stats = &wlif->stats_watchdog[id];
+ if (stats) {
+ stats->rx_packets = WLCNTVAL(wlcif_stats.rxframe);
+ stats->tx_packets = WLCNTVAL(wlcif_stats.txframe);
+ stats->rx_bytes = WLCNTVAL(wlcif_stats.rxbyte);
+ stats->tx_bytes = WLCNTVAL(wlcif_stats.txbyte);
+ stats->rx_errors = WLCNTVAL(wlcif_stats.rxerror);
+ stats->tx_errors = WLCNTVAL(wlcif_stats.txerror);
+ stats->collisions = 0;
+ stats->rx_length_errors = 0;
+
+ stats->rx_over_errors = WLCNTVAL(wl->pub->_cnt->rxoflo);
+ stats->rx_crc_errors = WLCNTVAL(wl->pub->_cnt->rxcrc);
+ stats->rx_frame_errors = 0;
+ stats->rx_fifo_errors = WLCNTVAL(wl->pub->_cnt->rxoflo);
+ stats->rx_missed_errors = 0;
+ stats->tx_fifo_errors = 0;
+ }
+
+#ifdef USE_IW
+ wstats = &wlif->wstats_watchdog[id];
+ if (wstats) {
+#if WIRELESS_EXT > 11
+ wstats->discard.nwid = 0;
+ wstats->discard.code = WLCNTVAL(wl->pub->_cnt->rxundec);
+ wstats->discard.fragment = WLCNTVAL(wlcif_stats.rxfragerr);
+ wstats->discard.retries = WLCNTVAL(wlcif_stats.txfail);
+ wstats->discard.misc = WLCNTVAL(wl->pub->_cnt->rxrunt) +
+ WLCNTVAL(wl->pub->_cnt->rxgiant);
+ wstats->miss.beacon = 0;
+#endif
+ }
+#endif
+
+ wlif->stats_id = id;
+ }
+#ifdef USE_IW
+ if (!wlc_get(wl->wlc, WLC_GET_PHY_NOISE, &phy_noise))
+ wlif->phy_noise = phy_noise;
+#endif
+
+ }
+ }
+
+ return 0;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+static int
+wl_proc_read(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
+{
+ wl_info_t * wl = (wl_info_t *)data;
+#else
+static ssize_t
+wl_proc_read(struct file *filp, char __user *buffer, size_t length, loff_t *offp)
+{
+ wl_info_t * wl = PDE_DATA(file_inode(filp));
+#endif
+ int bcmerror, len;
+ int to_user = 0;
+ char tmp[8];
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+ if (offset > 0) {
+ *eof = 1;
+ return 0;
+ }
+#else
+ if (*offp > 0) {
+ return 0;
+ }
+#endif
+
+ WL_LOCK(wl);
+ bcmerror = wlc_ioctl(wl->wlc, WLC_GET_MONITOR, &to_user, sizeof(int), NULL);
+ WL_UNLOCK(wl);
+
+ if (bcmerror != BCME_OK) {
+ WL_ERROR(("%s: GET_MONITOR failed with %d\n", __FUNCTION__, bcmerror));
+ return -EIO;
+ }
+
+ len = snprintf(tmp, ARRAY_SIZE(tmp), "%d\n", to_user);
+ tmp[ARRAY_SIZE(tmp) - 1] = '\0';
+ if ((len < 0) || (len >= ARRAY_SIZE(tmp))) {
+ WL_ERROR(("%s: tmp array not big enough %d > %zu", __FUNCTION__, len, ARRAY_SIZE(tmp)));
+ return -ERANGE;
+ }
+ if (length < len) {
+ WL_ERROR(( "%s: user buffer is too small (%d < %d)", __FUNCTION__, (int)length, len));
+ return -EMSGSIZE;
+ }
+ if (copy_to_user(buffer, tmp, len) != 0) {
+ WL_ERROR(( "%s: unable to copy data!", __FUNCTION__));
+ return -EFAULT;
+ }
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
+ *offp += len;
+#endif
+
+ return len;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+static int
+wl_proc_write(struct file *filp, const char *buff, unsigned long length, void *data)
+{
+ wl_info_t * wl = (wl_info_t *)data;
+#else
+static ssize_t
+wl_proc_write(struct file *filp, const char __user *buff, size_t length, loff_t *offp)
+{
+ wl_info_t * wl = PDE_DATA(file_inode(filp));
+#endif
+ int from_user = 0;
+ int bcmerror;
+
+ if (length == 0 || length > 2) {
+
+ WL_ERROR(("%s: Invalid data length\n", __FUNCTION__));
+ return -EIO;
+ }
+ if (copy_from_user(&from_user, buff, 1)) {
+ WL_ERROR(("%s: copy from user failed\n", __FUNCTION__));
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+ return -EIO;
+#else
+ return -EFAULT;
+#endif
+ }
+
+ if (from_user >= 0x30)
+ from_user -= 0x30;
+
+ WL_LOCK(wl);
+ bcmerror = wlc_ioctl(wl->wlc, WLC_SET_MONITOR, &from_user, sizeof(int), NULL);
+ WL_UNLOCK(wl);
+
+ if (bcmerror != BCME_OK) {
+ WL_ERROR(("%s: SET_MONITOR failed with %d\n", __FUNCTION__, bcmerror));
+ return -EIO;
+ }
+ return length;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
+static const struct file_operations wl_fops = {
+ .owner = THIS_MODULE,
+ .read = wl_proc_read,
+ .write = wl_proc_write,
+};
+#endif
+
+static int
+wl_reg_proc_entry(wl_info_t *wl)
+{
+ char tmp[32];
+ sprintf(tmp, "%s%d", HYBRID_PROC, wl->pub->unit);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+ if ((wl->proc_entry = create_proc_entry(tmp, 0644, NULL)) == NULL) {
+ WL_ERROR(("%s: create_proc_entry %s failed\n", __FUNCTION__, tmp));
+#else
+ if ((wl->proc_entry = proc_create_data(tmp, 0644, NULL, &wl_fops, wl)) == NULL) {
+ WL_ERROR(("%s: proc_create_data %s failed\n", __FUNCTION__, tmp));
+#endif
+ ASSERT(0);
+ return -1;
+ }
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
+ wl->proc_entry->read_proc = wl_proc_read;
+ wl->proc_entry->write_proc = wl_proc_write;
+ wl->proc_entry->data = wl;
+#endif
+ return 0;
+}
+uint32 wl_pcie_bar1(struct wl_info *wl, uchar** addr)
+{
+ *addr = wl->bar1_addr;
+ return (wl->bar1_size);
+}