diff options
Diffstat (limited to 'src/wl/sys/wl_linux.c')
-rw-r--r-- | src/wl/sys/wl_linux.c | 3371 |
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); +} |