/drivers/net/wireless/bcmdhd/dhd_linux.c
https://bitbucket.org/wisechild/galaxy-nexus · C · 5041 lines · 3804 code · 837 blank · 400 comment · 685 complexity · fe4e4f51f9d74a59db8f79ca55e59935 MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * Broadcom Dongle Host Driver (DHD), Linux-specific network interface
- * Basically selected code segments from usb-cdc.c and usb-rndis.c
- *
- * Copyright (C) 1999-2011, Broadcom Corporation
- *
- * Unless you and Broadcom execute a separate written software license
- * agreement governing use of this software, this software is licensed to you
- * under the terms of the GNU General Public License version 2 (the "GPL"),
- * available at http://www.broadcom.com/licenses/GPLv2.php, with the
- * following added to such license:
- *
- * As a special exception, the copyright holders of this software give you
- * permission to link this software with independent modules, and to copy and
- * distribute the resulting executable under terms of your choice, provided that
- * you also meet, for each linked independent module, the terms and conditions of
- * the license of that module. An independent module is a module which is not
- * derived from this software. The special exception does not apply to any
- * modifications of the software.
- *
- * Notwithstanding the above, under no circumstances may you combine this
- * software in any way with any other Broadcom software provided under a license
- * other than the GPL, without Broadcom's express prior written consent.
- *
- * $Id: dhd_linux.c 287541 2011-10-03 23:48:17Z $
- */
- #include <typedefs.h>
- #include <linuxver.h>
- #include <osl.h>
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/slab.h>
- #include <linux/skbuff.h>
- #include <linux/netdevice.h>
- #include <linux/inetdevice.h>
- #include <linux/rtnetlink.h>
- #include <linux/etherdevice.h>
- #include <linux/random.h>
- #include <linux/spinlock.h>
- #include <linux/ethtool.h>
- #include <linux/fcntl.h>
- #include <linux/fs.h>
- #include <asm/uaccess.h>
- #include <asm/unaligned.h>
- #include <epivers.h>
- #include <bcmutils.h>
- #include <bcmendian.h>
- #include <proto/ethernet.h>
- #include <dngl_stats.h>
- #include <dhd.h>
- #include <dhd_bus.h>
- #include <dhd_proto.h>
- #include <dhd_dbg.h>
- #ifdef CONFIG_HAS_WAKELOCK
- #include <linux/wakelock.h>
- #endif
- #ifdef WL_CFG80211
- #include <wl_cfg80211.h>
- #endif
- #include <proto/802.11_bta.h>
- #include <proto/bt_amp_hci.h>
- #include <dhd_bta.h>
- #ifdef WLMEDIA_HTSF
- #include <linux/time.h>
- #include <htsf.h>
- #define HTSF_MINLEN 200 /* min. packet length to timestamp */
- #define HTSF_BUS_DELAY 150 /* assume a fix propagation in us */
- #define TSMAX 1000 /* max no. of timing record kept */
- #define NUMBIN 34
- static uint32 tsidx = 0;
- static uint32 htsf_seqnum = 0;
- uint32 tsfsync;
- struct timeval tsync;
- static uint32 tsport = 5010;
- typedef struct histo_ {
- uint32 bin[NUMBIN];
- } histo_t;
- #if !ISPOWEROF2(DHD_SDALIGN)
- #error DHD_SDALIGN is not a power of 2!
- #endif
- static histo_t vi_d1, vi_d2, vi_d3, vi_d4;
- #endif /* WLMEDIA_HTSF */
- #if defined(SOFTAP)
- extern bool ap_cfg_running;
- extern bool ap_fw_loaded;
- #endif
- /* enable HOSTIP cache update from the host side when an eth0:N is up */
- #define AOE_IP_ALIAS_SUPPORT 1
- #ifdef PROP_TXSTATUS
- #include <wlfc_proto.h>
- #include <dhd_wlfc.h>
- #endif
- #include <wl_android.h>
- #ifdef ARP_OFFLOAD_SUPPORT
- static int dhd_device_event(struct notifier_block *this,
- unsigned long event,
- void *ptr);
- static struct notifier_block dhd_notifier = {
- .notifier_call = dhd_device_event
- };
- #endif /* ARP_OFFLOAD_SUPPORT */
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP)
- #include <linux/suspend.h>
- volatile bool dhd_mmc_suspend = FALSE;
- DECLARE_WAIT_QUEUE_HEAD(dhd_dpc_wait);
- #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */
- #if defined(OOB_INTR_ONLY)
- extern void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable);
- #endif /* defined(OOB_INTR_ONLY) */
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
- MODULE_LICENSE("GPL v2");
- #endif /* LinuxVer */
- #include <dhd_bus.h>
- #ifndef PROP_TXSTATUS
- #define DBUS_RX_BUFFER_SIZE_DHD(net) (net->mtu + net->hard_header_len + dhd->pub.hdrlen)
- #else
- #define DBUS_RX_BUFFER_SIZE_DHD(net) (net->mtu + net->hard_header_len + dhd->pub.hdrlen + 128)
- #endif
- #if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15)
- const char *
- print_tainted()
- {
- return "";
- }
- #endif /* LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) */
- /* Linux wireless extension support */
- #if defined(CONFIG_WIRELESS_EXT)
- #include <wl_iw.h>
- extern wl_iw_extra_params_t g_wl_iw_params;
- #endif /* defined(CONFIG_WIRELESS_EXT) */
- #if defined(CONFIG_HAS_EARLYSUSPEND)
- #include <linux/earlysuspend.h>
- extern int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len);
- extern int dhd_get_dtim_skip(dhd_pub_t *dhd);
- #endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
- #ifdef PKT_FILTER_SUPPORT
- extern void dhd_pktfilter_offload_set(dhd_pub_t * dhd, char *arg);
- extern void dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode);
- #endif
- /* Interface control information */
- typedef struct dhd_if {
- struct dhd_info *info; /* back pointer to dhd_info */
- /* OS/stack specifics */
- struct net_device *net;
- struct net_device_stats stats;
- int idx; /* iface idx in dongle */
- int state; /* interface state */
- uint subunit; /* subunit */
- uint8 mac_addr[ETHER_ADDR_LEN]; /* assigned MAC address */
- bool attached; /* Delayed attachment when unset */
- bool txflowcontrol; /* Per interface flow control indicator */
- char name[IFNAMSIZ+1]; /* linux interface name */
- uint8 bssidx; /* bsscfg index for the interface */
- bool set_multicast;
- } dhd_if_t;
- #ifdef WLMEDIA_HTSF
- typedef struct {
- uint32 low;
- uint32 high;
- } tsf_t;
- typedef struct {
- uint32 last_cycle;
- uint32 last_sec;
- uint32 last_tsf;
- uint32 coef; /* scaling factor */
- uint32 coefdec1; /* first decimal */
- uint32 coefdec2; /* second decimal */
- } htsf_t;
- typedef struct {
- uint32 t1;
- uint32 t2;
- uint32 t3;
- uint32 t4;
- } tstamp_t;
- static tstamp_t ts[TSMAX];
- static tstamp_t maxdelayts;
- static uint32 maxdelay = 0, tspktcnt = 0, maxdelaypktno = 0;
- #endif /* WLMEDIA_HTSF */
- /* Local private structure (extension of pub) */
- typedef struct dhd_info {
- #if defined(CONFIG_WIRELESS_EXT)
- wl_iw_t iw; /* wireless extensions state (must be first) */
- #endif /* defined(CONFIG_WIRELESS_EXT) */
- dhd_pub_t pub;
- /* For supporting multiple interfaces */
- dhd_if_t *iflist[DHD_MAX_IFS];
- struct semaphore proto_sem;
- #ifdef PROP_TXSTATUS
- spinlock_t wlfc_spinlock;
- #endif /* PROP_TXSTATUS */
- #ifdef WLMEDIA_HTSF
- htsf_t htsf;
- #endif
- wait_queue_head_t ioctl_resp_wait;
- struct timer_list timer;
- bool wd_timer_valid;
- struct tasklet_struct tasklet;
- spinlock_t sdlock;
- spinlock_t txqlock;
- spinlock_t dhd_lock;
- #ifdef DHDTHREAD
- /* Thread based operation */
- bool threads_only;
- struct semaphore sdsem;
- tsk_ctl_t thr_dpc_ctl;
- tsk_ctl_t thr_wdt_ctl;
- #else
- bool dhd_tasklet_create;
- #endif /* DHDTHREAD */
- tsk_ctl_t thr_sysioc_ctl;
- /* Wakelocks */
- #if defined(CONFIG_HAS_WAKELOCK) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
- struct wake_lock wl_wifi; /* Wifi wakelock */
- struct wake_lock wl_rxwake; /* Wifi rx wakelock */
- #endif
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25))
- /* net_device interface lock, prevent race conditions among net_dev interface
- * calls and wifi_on or wifi_off
- */
- struct mutex dhd_net_if_mutex;
- #endif
- spinlock_t wakelock_spinlock;
- int wakelock_counter;
- int wakelock_timeout_enable;
- /* Thread to issue ioctl for multicast */
- bool set_macaddress;
- struct ether_addr macvalue;
- wait_queue_head_t ctrl_wait;
- atomic_t pend_8021x_cnt;
- dhd_attach_states_t dhd_state;
- #ifdef CONFIG_HAS_EARLYSUSPEND
- struct early_suspend early_suspend;
- #endif /* CONFIG_HAS_EARLYSUSPEND */
- } dhd_info_t;
- /* Definitions to provide path to the firmware and nvram
- * example nvram_path[MOD_PARAM_PATHLEN]="/projects/wlan/nvram.txt"
- */
- char firmware_path[MOD_PARAM_PATHLEN];
- char nvram_path[MOD_PARAM_PATHLEN];
- extern int wl_control_wl_start(struct net_device *dev);
- extern int net_os_send_hang_message(struct net_device *dev);
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27))
- struct semaphore dhd_registration_sem;
- #define DHD_REGISTRATION_TIMEOUT 12000 /* msec : allowed time to finished dhd registration */
- #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) */
- /* Spawn a thread for system ioctls (set mac, set mcast) */
- uint dhd_sysioc = TRUE;
- module_param(dhd_sysioc, uint, 0);
- /* Error bits */
- module_param(dhd_msg_level, int, 0);
- /* load firmware and/or nvram values from the filesystem */
- module_param_string(firmware_path, firmware_path, MOD_PARAM_PATHLEN, 0660);
- module_param_string(nvram_path, nvram_path, MOD_PARAM_PATHLEN, 0);
- /* Watchdog interval */
- uint dhd_watchdog_ms = 10;
- module_param(dhd_watchdog_ms, uint, 0);
- #if defined(DHD_DEBUG)
- /* Console poll interval */
- uint dhd_console_ms = 0;
- module_param(dhd_console_ms, uint, 0);
- #endif /* defined(DHD_DEBUG) */
- /* ARP offload agent mode : Enable ARP Host Auto-Reply and ARP Peer Auto-Reply */
- uint dhd_arp_mode = 0xb;
- module_param(dhd_arp_mode, uint, 0);
- /* ARP offload enable */
- uint dhd_arp_enable = TRUE;
- module_param(dhd_arp_enable, uint, 0);
- /* Global Pkt filter enable control */
- uint dhd_pkt_filter_enable = TRUE;
- module_param(dhd_pkt_filter_enable, uint, 0);
- /* Pkt filter init setup */
- uint dhd_pkt_filter_init = 0;
- module_param(dhd_pkt_filter_init, uint, 0);
- /* Pkt filter mode control */
- uint dhd_master_mode = TRUE;
- module_param(dhd_master_mode, uint, 1);
- #ifdef DHDTHREAD
- /* Watchdog thread priority, -1 to use kernel timer */
- int dhd_watchdog_prio = 97;
- module_param(dhd_watchdog_prio, int, 0);
- /* DPC thread priority, -1 to use tasklet */
- int dhd_dpc_prio = 98;
- module_param(dhd_dpc_prio, int, 0);
- /* DPC thread priority, -1 to use tasklet */
- extern int dhd_dongle_memsize;
- module_param(dhd_dongle_memsize, int, 0);
- #endif /* DHDTHREAD */
- /* Control fw roaming */
- uint dhd_roam_disable = 0;
- /* Control radio state */
- uint dhd_radio_up = 1;
- /* Network inteface name */
- char iface_name[IFNAMSIZ];
- module_param_string(iface_name, iface_name, IFNAMSIZ, 0);
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
- #define DAEMONIZE(a) daemonize(a); \
- allow_signal(SIGKILL); \
- allow_signal(SIGTERM);
- #else /* Linux 2.4 (w/o preemption patch) */
- #define RAISE_RX_SOFTIRQ() \
- cpu_raise_softirq(smp_processor_id(), NET_RX_SOFTIRQ)
- #define DAEMONIZE(a) daemonize(); \
- do { if (a) \
- strncpy(current->comm, a, MIN(sizeof(current->comm), (strlen(a) + 1))); \
- } while (0);
- #endif /* LINUX_VERSION_CODE */
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
- #define BLOCKABLE() (!in_atomic())
- #else
- #define BLOCKABLE() (!in_interrupt())
- #endif
- /* The following are specific to the SDIO dongle */
- /* IOCTL response timeout */
- int dhd_ioctl_timeout_msec = IOCTL_RESP_TIMEOUT;
- /* Idle timeout for backplane clock */
- int dhd_idletime = DHD_IDLETIME_TICKS;
- module_param(dhd_idletime, int, 0);
- /* Use polling */
- uint dhd_poll = FALSE;
- module_param(dhd_poll, uint, 0);
- /* Use interrupts */
- uint dhd_intr = TRUE;
- module_param(dhd_intr, uint, 0);
- /* SDIO Drive Strength (in milliamps) */
- uint dhd_sdiod_drive_strength = 6;
- module_param(dhd_sdiod_drive_strength, uint, 0);
- /* Tx/Rx bounds */
- extern uint dhd_txbound;
- extern uint dhd_rxbound;
- module_param(dhd_txbound, uint, 0);
- module_param(dhd_rxbound, uint, 0);
- /* Deferred transmits */
- extern uint dhd_deferred_tx;
- module_param(dhd_deferred_tx, uint, 0);
- #ifdef BCMDBGFS
- extern void dhd_dbg_init(dhd_pub_t *dhdp);
- extern void dhd_dbg_remove(void);
- #endif /* BCMDBGFS */
- #ifdef SDTEST
- /* Echo packet generator (pkts/s) */
- uint dhd_pktgen = 0;
- module_param(dhd_pktgen, uint, 0);
- /* Echo packet len (0 => sawtooth, max 2040) */
- uint dhd_pktgen_len = 0;
- module_param(dhd_pktgen_len, uint, 0);
- #endif /* SDTEST */
- /* Version string to report */
- #ifdef DHD_DEBUG
- #ifndef SRCBASE
- #define SRCBASE "drivers/net/wireless/bcmdhd"
- #endif
- #define DHD_COMPILED "\nCompiled in " SRCBASE
- #else
- #define DHD_COMPILED
- #endif /* DHD_DEBUG */
- static char dhd_version[] = "Dongle Host Driver, version " EPI_VERSION_STR
- #ifdef DHD_DEBUG
- "\nCompiled in " SRCBASE " on " __DATE__ " at " __TIME__
- #endif
- ;
- static void dhd_net_if_lock_local(dhd_info_t *dhd);
- static void dhd_net_if_unlock_local(dhd_info_t *dhd);
- #ifdef WLMEDIA_HTSF
- void htsf_update(dhd_info_t *dhd, void *data);
- tsf_t prev_tsf, cur_tsf;
- uint32 dhd_get_htsf(dhd_info_t *dhd, int ifidx);
- static int dhd_ioctl_htsf_get(dhd_info_t *dhd, int ifidx);
- static void dhd_dump_latency(void);
- static void dhd_htsf_addtxts(dhd_pub_t *dhdp, void *pktbuf);
- static void dhd_htsf_addrxts(dhd_pub_t *dhdp, void *pktbuf);
- static void dhd_dump_htsfhisto(histo_t *his, char *s);
- #endif /* WLMEDIA_HTSF */
- /* Monitor interface */
- int dhd_monitor_init(void *dhd_pub);
- int dhd_monitor_uninit(void);
- #if defined(CONFIG_WIRELESS_EXT)
- struct iw_statistics *dhd_get_wireless_stats(struct net_device *dev);
- #endif /* defined(CONFIG_WIRELESS_EXT) */
- static void dhd_dpc(ulong data);
- /* forward decl */
- extern int dhd_wait_pend8021x(struct net_device *dev);
- #ifdef TOE
- #ifndef BDC
- #error TOE requires BDC
- #endif /* !BDC */
- static int dhd_toe_get(dhd_info_t *dhd, int idx, uint32 *toe_ol);
- static int dhd_toe_set(dhd_info_t *dhd, int idx, uint32 toe_ol);
- #endif /* TOE */
- static int dhd_wl_host_event(dhd_info_t *dhd, int *ifidx, void *pktdata,
- wl_event_msg_t *event_ptr, void **data_ptr);
- #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP)
- static int dhd_sleep_pm_callback(struct notifier_block *nfb, unsigned long action, void *ignored)
- {
- int ret = NOTIFY_DONE;
- switch (action) {
- case PM_HIBERNATION_PREPARE:
- case PM_SUSPEND_PREPARE:
- dhd_mmc_suspend = TRUE;
- ret = NOTIFY_OK;
- break;
- case PM_POST_HIBERNATION:
- case PM_POST_SUSPEND:
- dhd_mmc_suspend = FALSE;
- ret = NOTIFY_OK;
- break;
- }
- smp_mb();
- return ret;
- }
- static struct notifier_block dhd_sleep_pm_notifier = {
- .notifier_call = dhd_sleep_pm_callback,
- .priority = 0
- };
- extern int register_pm_notifier(struct notifier_block *nb);
- extern int unregister_pm_notifier(struct notifier_block *nb);
- #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM_SLEEP) */
- static void dhd_set_packet_filter(int value, dhd_pub_t *dhd)
- {
- #ifdef PKT_FILTER_SUPPORT
- DHD_TRACE(("%s: %d\n", __FUNCTION__, value));
- /* 1 - Enable packet filter, only allow unicast packet to send up */
- /* 0 - Disable packet filter */
- if (dhd_pkt_filter_enable) {
- int i;
- for (i = 0; i < dhd->pktfilter_count; i++) {
- dhd_pktfilter_offload_set(dhd, dhd->pktfilter[i]);
- dhd_pktfilter_offload_enable(dhd, dhd->pktfilter[i],
- value, dhd_master_mode);
- }
- }
- #endif
- }
- #if defined(CONFIG_HAS_EARLYSUSPEND)
- static int dhd_set_suspend(int value, dhd_pub_t *dhd)
- {
- int power_mode = PM_FAST;
- /* wl_pkt_filter_enable_t enable_parm; */
- char iovbuf[32];
- int bcn_li_dtim = 3;
- uint roamvar = 1;
- DHD_TRACE(("%s: enter, value = %d in_suspend=%d\n",
- __FUNCTION__, value, dhd->in_suspend));
- if (dhd && dhd->up) {
- if (value && dhd->in_suspend) {
- /* Kernel suspended */
- DHD_ERROR(("%s: force extra Suspend setting \n", __FUNCTION__));
- dhd_wl_ioctl_cmd(dhd, WLC_SET_PM, (char *)&power_mode,
- sizeof(power_mode), TRUE, 0);
- /* Enable packet filter, only allow unicast packet to send up */
- dhd_set_packet_filter(1, dhd);
- /* If DTIM skip is set up as default, force it to wake
- * each third DTIM for better power savings. Note that
- * one side effect is a chance to miss BC/MC packet.
- */
- bcn_li_dtim = dhd_get_dtim_skip(dhd);
- bcm_mkiovar("bcn_li_dtim", (char *)&bcn_li_dtim,
- 4, iovbuf, sizeof(iovbuf));
- dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
- /* Disable firmware roaming during suspend */
- bcm_mkiovar("roam_off", (char *)&roamvar, 4,
- iovbuf, sizeof(iovbuf));
- dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
- } else {
- /* Kernel resumed */
- DHD_TRACE(("%s: Remove extra suspend setting \n", __FUNCTION__));
- power_mode = PM_FAST;
- dhd_wl_ioctl_cmd(dhd, WLC_SET_PM, (char *)&power_mode,
- sizeof(power_mode), TRUE, 0);
- /* disable pkt filter */
- dhd_set_packet_filter(0, dhd);
- /* restore pre-suspend setting for dtim_skip */
- bcm_mkiovar("bcn_li_dtim", (char *)&dhd->dtim_skip,
- 4, iovbuf, sizeof(iovbuf));
- dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
- roamvar = dhd_roam_disable;
- bcm_mkiovar("roam_off", (char *)&roamvar, 4, iovbuf,
- sizeof(iovbuf));
- dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0);
- }
- }
- return 0;
- }
- static void dhd_suspend_resume_helper(struct dhd_info *dhd, int val)
- {
- dhd_pub_t *dhdp = &dhd->pub;
- DHD_OS_WAKE_LOCK(dhdp);
- /* Set flag when early suspend was called */
- dhdp->in_suspend = val;
- if (!dhdp->suspend_disable_flag)
- dhd_set_suspend(val, dhdp);
- DHD_OS_WAKE_UNLOCK(dhdp);
- }
- static void dhd_early_suspend(struct early_suspend *h)
- {
- struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend);
- DHD_TRACE(("%s: enter\n", __FUNCTION__));
- if (dhd)
- dhd_suspend_resume_helper(dhd, 1);
- }
- static void dhd_late_resume(struct early_suspend *h)
- {
- struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend);
- DHD_TRACE(("%s: enter\n", __FUNCTION__));
- if (dhd)
- dhd_suspend_resume_helper(dhd, 0);
- }
- #endif /* defined(CONFIG_HAS_EARLYSUSPEND) */
- /*
- * Generalized timeout mechanism. Uses spin sleep with exponential back-off until
- * the sleep time reaches one jiffy, then switches over to task delay. Usage:
- *
- * dhd_timeout_start(&tmo, usec);
- * while (!dhd_timeout_expired(&tmo))
- * if (poll_something())
- * break;
- * if (dhd_timeout_expired(&tmo))
- * fatal();
- */
- void
- dhd_timeout_start(dhd_timeout_t *tmo, uint usec)
- {
- tmo->limit = usec;
- tmo->increment = 0;
- tmo->elapsed = 0;
- tmo->tick = 1000000 / HZ;
- }
- int
- dhd_timeout_expired(dhd_timeout_t *tmo)
- {
- /* Does nothing the first call */
- if (tmo->increment == 0) {
- tmo->increment = 1;
- return 0;
- }
- if (tmo->elapsed >= tmo->limit)
- return 1;
- /* Add the delay that's about to take place */
- tmo->elapsed += tmo->increment;
- if (tmo->increment < tmo->tick) {
- OSL_DELAY(tmo->increment);
- tmo->increment *= 2;
- if (tmo->increment > tmo->tick)
- tmo->increment = tmo->tick;
- } else {
- wait_queue_head_t delay_wait;
- DECLARE_WAITQUEUE(wait, current);
- int pending;
- init_waitqueue_head(&delay_wait);
- add_wait_queue(&delay_wait, &wait);
- set_current_state(TASK_INTERRUPTIBLE);
- schedule_timeout(1);
- pending = signal_pending(current);
- remove_wait_queue(&delay_wait, &wait);
- set_current_state(TASK_RUNNING);
- if (pending)
- return 1; /* Interrupted */
- }
- return 0;
- }
- int
- dhd_net2idx(dhd_info_t *dhd, struct net_device *net)
- {
- int i = 0;
- ASSERT(dhd);
- while (i < DHD_MAX_IFS) {
- if (dhd->iflist[i] && (dhd->iflist[i]->net == net))
- return i;
- i++;
- }
- return DHD_BAD_IF;
- }
- struct net_device * dhd_idx2net(struct dhd_pub *dhd_pub, int ifidx)
- {
- struct dhd_info *dhd_info;
- if (!dhd_pub || ifidx < 0 || ifidx >= DHD_MAX_IFS)
- return NULL;
- dhd_info = dhd_pub->info;
- if (dhd_info && dhd_info->iflist[ifidx])
- return dhd_info->iflist[ifidx]->net;
- return NULL;
- }
- int
- dhd_ifname2idx(dhd_info_t *dhd, char *name)
- {
- int i = DHD_MAX_IFS;
- ASSERT(dhd);
- if (name == NULL || *name == '\0')
- return 0;
- while (--i > 0)
- if (dhd->iflist[i] && !strncmp(dhd->iflist[i]->name, name, IFNAMSIZ))
- break;
- DHD_TRACE(("%s: return idx %d for \"%s\"\n", __FUNCTION__, i, name));
- return i; /* default - the primary interface */
- }
- char *
- dhd_ifname(dhd_pub_t *dhdp, int ifidx)
- {
- dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
- ASSERT(dhd);
- if (ifidx < 0 || ifidx >= DHD_MAX_IFS) {
- DHD_ERROR(("%s: ifidx %d out of range\n", __FUNCTION__, ifidx));
- return "<if_bad>";
- }
- if (dhd->iflist[ifidx] == NULL) {
- DHD_ERROR(("%s: null i/f %d\n", __FUNCTION__, ifidx));
- return "<if_null>";
- }
- if (dhd->iflist[ifidx]->net)
- return dhd->iflist[ifidx]->net->name;
- return "<if_none>";
- }
- uint8 *
- dhd_bssidx2bssid(dhd_pub_t *dhdp, int idx)
- {
- int i;
- dhd_info_t *dhd = (dhd_info_t *)dhdp;
- ASSERT(dhd);
- for (i = 0; i < DHD_MAX_IFS; i++)
- if (dhd->iflist[i] && dhd->iflist[i]->bssidx == idx)
- return dhd->iflist[i]->mac_addr;
- return NULL;
- }
- static void
- _dhd_set_multicast_list(dhd_info_t *dhd, int ifidx)
- {
- struct net_device *dev;
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
- struct netdev_hw_addr *ha;
- #else
- struct dev_mc_list *mclist;
- #endif
- uint32 allmulti, cnt;
- wl_ioctl_t ioc;
- char *buf, *bufp;
- uint buflen;
- int ret;
- ASSERT(dhd && dhd->iflist[ifidx]);
- dev = dhd->iflist[ifidx]->net;
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
- netif_addr_lock_bh(dev);
- #endif
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
- cnt = netdev_mc_count(dev);
- #else
- cnt = dev->mc_count;
- #endif
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
- netif_addr_unlock_bh(dev);
- #endif
- /* Determine initial value of allmulti flag */
- allmulti = (dev->flags & IFF_ALLMULTI) ? TRUE : FALSE;
- /* Send down the multicast list first. */
- buflen = sizeof("mcast_list") + sizeof(cnt) + (cnt * ETHER_ADDR_LEN);
- if (!(bufp = buf = MALLOC(dhd->pub.osh, buflen))) {
- DHD_ERROR(("%s: out of memory for mcast_list, cnt %d\n",
- dhd_ifname(&dhd->pub, ifidx), cnt));
- return;
- }
- strcpy(bufp, "mcast_list");
- bufp += strlen("mcast_list") + 1;
- cnt = htol32(cnt);
- memcpy(bufp, &cnt, sizeof(cnt));
- bufp += sizeof(cnt);
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
- netif_addr_lock_bh(dev);
- #endif
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35)
- netdev_for_each_mc_addr(ha, dev) {
- if (!cnt)
- break;
- memcpy(bufp, ha->addr, ETHER_ADDR_LEN);
- bufp += ETHER_ADDR_LEN;
- cnt--;
- }
- #else
- for (mclist = dev->mc_list; (mclist && (cnt > 0)); cnt--, mclist = mclist->next) {
- memcpy(bufp, (void *)mclist->dmi_addr, ETHER_ADDR_LEN);
- bufp += ETHER_ADDR_LEN;
- }
- #endif
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
- netif_addr_unlock_bh(dev);
- #endif
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_SET_VAR;
- ioc.buf = buf;
- ioc.len = buflen;
- ioc.set = TRUE;
- ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
- if (ret < 0) {
- DHD_ERROR(("%s: set mcast_list failed, cnt %d\n",
- dhd_ifname(&dhd->pub, ifidx), cnt));
- allmulti = cnt ? TRUE : allmulti;
- }
- MFREE(dhd->pub.osh, buf, buflen);
- /* Now send the allmulti setting. This is based on the setting in the
- * net_device flags, but might be modified above to be turned on if we
- * were trying to set some addresses and dongle rejected it...
- */
- buflen = sizeof("allmulti") + sizeof(allmulti);
- if (!(buf = MALLOC(dhd->pub.osh, buflen))) {
- DHD_ERROR(("%s: out of memory for allmulti\n", dhd_ifname(&dhd->pub, ifidx)));
- return;
- }
- allmulti = htol32(allmulti);
- if (!bcm_mkiovar("allmulti", (void*)&allmulti, sizeof(allmulti), buf, buflen)) {
- DHD_ERROR(("%s: mkiovar failed for allmulti, datalen %d buflen %u\n",
- dhd_ifname(&dhd->pub, ifidx), (int)sizeof(allmulti), buflen));
- MFREE(dhd->pub.osh, buf, buflen);
- return;
- }
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_SET_VAR;
- ioc.buf = buf;
- ioc.len = buflen;
- ioc.set = TRUE;
- ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
- if (ret < 0) {
- DHD_ERROR(("%s: set allmulti %d failed\n",
- dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti)));
- }
- MFREE(dhd->pub.osh, buf, buflen);
- /* Finally, pick up the PROMISC flag as well, like the NIC driver does */
- allmulti = (dev->flags & IFF_PROMISC) ? TRUE : FALSE;
- allmulti = htol32(allmulti);
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_SET_PROMISC;
- ioc.buf = &allmulti;
- ioc.len = sizeof(allmulti);
- ioc.set = TRUE;
- ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
- if (ret < 0) {
- DHD_ERROR(("%s: set promisc %d failed\n",
- dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti)));
- }
- }
- static int
- _dhd_set_mac_address(dhd_info_t *dhd, int ifidx, struct ether_addr *addr)
- {
- char buf[32];
- wl_ioctl_t ioc;
- int ret;
- if (!bcm_mkiovar("cur_etheraddr", (char*)addr, ETHER_ADDR_LEN, buf, 32)) {
- DHD_ERROR(("%s: mkiovar failed for cur_etheraddr\n", dhd_ifname(&dhd->pub, ifidx)));
- return -1;
- }
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_SET_VAR;
- ioc.buf = buf;
- ioc.len = 32;
- ioc.set = TRUE;
- ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len);
- if (ret < 0) {
- DHD_ERROR(("%s: set cur_etheraddr failed\n", dhd_ifname(&dhd->pub, ifidx)));
- } else {
- memcpy(dhd->iflist[ifidx]->net->dev_addr, addr, ETHER_ADDR_LEN);
- }
- return ret;
- }
- #ifdef SOFTAP
- extern struct net_device *ap_net_dev;
- extern tsk_ctl_t ap_eth_ctl; /* ap netdev heper thread ctl */
- #endif
- static void
- dhd_op_if(dhd_if_t *ifp)
- {
- dhd_info_t *dhd;
- int ret = 0, err = 0;
- #ifdef SOFTAP
- unsigned long flags;
- #endif
- ASSERT(ifp && ifp->info && ifp->idx); /* Virtual interfaces only */
- dhd = ifp->info;
- DHD_TRACE(("%s: idx %d, state %d\n", __FUNCTION__, ifp->idx, ifp->state));
- #ifdef WL_CFG80211
- if (wl_cfg80211_is_progress_ifchange())
- return;
- #endif
- switch (ifp->state) {
- case WLC_E_IF_ADD:
- /*
- * Delete the existing interface before overwriting it
- * in case we missed the WLC_E_IF_DEL event.
- */
- if (ifp->net != NULL) {
- DHD_ERROR(("%s: ERROR: netdev:%s already exists, try free & unregister \n",
- __FUNCTION__, ifp->net->name));
- netif_stop_queue(ifp->net);
- unregister_netdev(ifp->net);
- free_netdev(ifp->net);
- }
- /* Allocate etherdev, including space for private structure */
- if (!(ifp->net = alloc_etherdev(sizeof(dhd)))) {
- DHD_ERROR(("%s: OOM - alloc_etherdev\n", __FUNCTION__));
- ret = -ENOMEM;
- }
- if (ret == 0) {
- strncpy(ifp->net->name, ifp->name, IFNAMSIZ);
- ifp->net->name[IFNAMSIZ - 1] = '\0';
- memcpy(netdev_priv(ifp->net), &dhd, sizeof(dhd));
- #ifdef WL_CFG80211
- if (dhd->dhd_state & DHD_ATTACH_STATE_CFG80211)
- if (!wl_cfg80211_notify_ifadd(ifp->net, ifp->idx, ifp->bssidx,
- dhd_net_attach)) {
- ifp->state = 0;
- return;
- }
- #endif
- if ((err = dhd_net_attach(&dhd->pub, ifp->idx)) != 0) {
- DHD_ERROR(("%s: dhd_net_attach failed, err %d\n",
- __FUNCTION__, err));
- ret = -EOPNOTSUPP;
- } else {
- #if defined(SOFTAP)
- if (ap_fw_loaded && !(dhd->dhd_state & DHD_ATTACH_STATE_CFG80211)) {
- /* semaphore that the soft AP CODE waits on */
- flags = dhd_os_spin_lock(&dhd->pub);
- /* save ptr to wl0.1 netdev for use in wl_iw.c */
- ap_net_dev = ifp->net;
- /* signal to the SOFTAP 'sleeper' thread, wl0.1 is ready */
- up(&ap_eth_ctl.sema);
- dhd_os_spin_unlock(&dhd->pub, flags);
- }
- #endif
- DHD_TRACE(("\n ==== pid:%x, net_device for if:%s created ===\n\n",
- current->pid, ifp->net->name));
- ifp->state = 0;
- }
- }
- break;
- case WLC_E_IF_DEL:
- if (ifp->net != NULL) {
- DHD_TRACE(("\n%s: got 'WLC_E_IF_DEL' state\n", __FUNCTION__));
- #ifdef WL_CFG80211
- wl_cfg80211_ifdel_ops(ifp->net);
- #endif
- netif_stop_queue(ifp->net);
- unregister_netdev(ifp->net);
- ret = DHD_DEL_IF; /* Make sure the free_netdev() is called */
- }
- break;
- default:
- DHD_ERROR(("%s: bad op %d\n", __FUNCTION__, ifp->state));
- ASSERT(!ifp->state);
- break;
- }
- if (ret < 0) {
- ifp->set_multicast = FALSE;
- if (ifp->net) {
- free_netdev(ifp->net);
- }
- dhd->iflist[ifp->idx] = NULL;
- #ifdef WL_CFG80211
- if (dhd->dhd_state & DHD_ATTACH_STATE_CFG80211) {
- wl_cfg80211_notify_ifdel(ifp->net);
- }
- #endif
- #ifdef SOFTAP
- flags = dhd_os_spin_lock(&dhd->pub);
- if (ifp->net == ap_net_dev)
- ap_net_dev = NULL; /* NULL SOFTAP global wl0.1 as well */
- dhd_os_spin_unlock(&dhd->pub, flags);
- #endif /* SOFTAP */
- MFREE(dhd->pub.osh, ifp, sizeof(*ifp));
- }
- }
- static int
- _dhd_sysioc_thread(void *data)
- {
- tsk_ctl_t *tsk = (tsk_ctl_t *)data;
- dhd_info_t *dhd = (dhd_info_t *)tsk->parent;
- int i;
- #ifdef SOFTAP
- bool in_ap = FALSE;
- unsigned long flags;
- #endif
- DAEMONIZE("dhd_sysioc");
- complete(&tsk->completed);
- while (down_interruptible(&tsk->sema) == 0) {
- SMP_RD_BARRIER_DEPENDS();
- if (tsk->terminated) {
- break;
- }
- dhd_net_if_lock_local(dhd);
- DHD_OS_WAKE_LOCK(&dhd->pub);
- for (i = 0; i < DHD_MAX_IFS; i++) {
- if (dhd->iflist[i]) {
- DHD_TRACE(("%s: interface %d\n", __FUNCTION__, i));
- #ifdef SOFTAP
- flags = dhd_os_spin_lock(&dhd->pub);
- in_ap = (ap_net_dev != NULL);
- dhd_os_spin_unlock(&dhd->pub, flags);
- #endif /* SOFTAP */
- if (dhd->iflist[i] && dhd->iflist[i]->state)
- dhd_op_if(dhd->iflist[i]);
- if (dhd->iflist[i] == NULL) {
- DHD_TRACE(("\n\n %s: interface %d just been removed,"
- "!\n\n", __FUNCTION__, i));
- continue;
- }
- #ifdef SOFTAP
- if (in_ap && dhd->set_macaddress) {
- DHD_TRACE(("attempt to set MAC for %s in AP Mode,"
- "blocked. \n", dhd->iflist[i]->net->name));
- dhd->set_macaddress = FALSE;
- continue;
- }
- if (in_ap && dhd->iflist[i]->set_multicast) {
- DHD_TRACE(("attempt to set MULTICAST list for %s"
- "in AP Mode, blocked. \n", dhd->iflist[i]->net->name));
- dhd->iflist[i]->set_multicast = FALSE;
- continue;
- }
- #endif /* SOFTAP */
- if (dhd->iflist[i]->set_multicast) {
- dhd->iflist[i]->set_multicast = FALSE;
- _dhd_set_multicast_list(dhd, i);
- }
- if (dhd->set_macaddress) {
- dhd->set_macaddress = FALSE;
- _dhd_set_mac_address(dhd, i, &dhd->macvalue);
- }
- }
- }
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- dhd_net_if_unlock_local(dhd);
- }
- DHD_TRACE(("%s: stopped\n", __FUNCTION__));
- complete_and_exit(&tsk->completed, 0);
- }
- static int
- dhd_set_mac_address(struct net_device *dev, void *addr)
- {
- int ret = 0;
- dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
- struct sockaddr *sa = (struct sockaddr *)addr;
- int ifidx;
- ifidx = dhd_net2idx(dhd, dev);
- if (ifidx == DHD_BAD_IF)
- return -1;
- ASSERT(&dhd->thr_sysioc_ctl.thr_pid >= 0);
- memcpy(&dhd->macvalue, sa->sa_data, ETHER_ADDR_LEN);
- dhd->set_macaddress = TRUE;
- up(&dhd->thr_sysioc_ctl.sema);
- return ret;
- }
- static void
- dhd_set_multicast_list(struct net_device *dev)
- {
- dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(dev);
- int ifidx;
- ifidx = dhd_net2idx(dhd, dev);
- if (ifidx == DHD_BAD_IF)
- return;
- ASSERT(&dhd->thr_sysioc_ctl.thr_pid >= 0);
- dhd->iflist[ifidx]->set_multicast = TRUE;
- up(&dhd->thr_sysioc_ctl.sema);
- }
- #ifdef PROP_TXSTATUS
- int
- dhd_os_wlfc_block(dhd_pub_t *pub)
- {
- dhd_info_t *di = (dhd_info_t *)(pub->info);
- ASSERT(di != NULL);
- spin_lock_bh(&di->wlfc_spinlock);
- return 1;
- }
- int
- dhd_os_wlfc_unblock(dhd_pub_t *pub)
- {
- dhd_info_t *di = (dhd_info_t *)(pub->info);
- ASSERT(di != NULL);
- spin_unlock_bh(&di->wlfc_spinlock);
- return 1;
- }
- const uint8 wme_fifo2ac[] = { 0, 1, 2, 3, 1, 1 };
- uint8 prio2fifo[8] = { 1, 0, 0, 1, 2, 2, 3, 3 };
- #define WME_PRIO2AC(prio) wme_fifo2ac[prio2fifo[(prio)]]
- #endif /* PROP_TXSTATUS */
- int
- dhd_sendpkt(dhd_pub_t *dhdp, int ifidx, void *pktbuf)
- {
- int ret;
- dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
- struct ether_header *eh = NULL;
- /* Reject if down */
- if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN)) {
- /* free the packet here since the caller won't */
- PKTFREE(dhdp->osh, pktbuf, TRUE);
- return -ENODEV;
- }
- /* Update multicast statistic */
- if (PKTLEN(dhdp->osh, pktbuf) >= ETHER_ADDR_LEN) {
- uint8 *pktdata = (uint8 *)PKTDATA(dhdp->osh, pktbuf);
- eh = (struct ether_header *)pktdata;
- if (ETHER_ISMULTI(eh->ether_dhost))
- dhdp->tx_multicast++;
- if (ntoh16(eh->ether_type) == ETHER_TYPE_802_1X)
- atomic_inc(&dhd->pend_8021x_cnt);
- }
- /* Look into the packet and update the packet priority */
- if (PKTPRIO(pktbuf) == 0)
- pktsetprio(pktbuf, FALSE);
- #ifdef PROP_TXSTATUS
- if (dhdp->wlfc_state) {
- /* store the interface ID */
- DHD_PKTTAG_SETIF(PKTTAG(pktbuf), ifidx);
- /* store destination MAC in the tag as well */
- DHD_PKTTAG_SETDSTN(PKTTAG(pktbuf), eh->ether_dhost);
- /* decide which FIFO this packet belongs to */
- if (ETHER_ISMULTI(eh->ether_dhost))
- /* one additional queue index (highest AC + 1) is used for bc/mc queue */
- DHD_PKTTAG_SETFIFO(PKTTAG(pktbuf), AC_COUNT);
- else
- DHD_PKTTAG_SETFIFO(PKTTAG(pktbuf), WME_PRIO2AC(PKTPRIO(pktbuf)));
- } else
- #endif /* PROP_TXSTATUS */
- /* If the protocol uses a data header, apply it */
- dhd_prot_hdrpush(dhdp, ifidx, pktbuf);
- /* Use bus module to send data frame */
- #ifdef WLMEDIA_HTSF
- dhd_htsf_addtxts(dhdp, pktbuf);
- #endif
- #ifdef PROP_TXSTATUS
- if (dhdp->wlfc_state && ((athost_wl_status_info_t*)dhdp->wlfc_state)->proptxstatus_mode
- != WLFC_FCMODE_NONE) {
- dhd_os_wlfc_block(dhdp);
- ret = dhd_wlfc_enque_sendq(dhdp->wlfc_state, DHD_PKTTAG_FIFO(PKTTAG(pktbuf)),
- pktbuf);
- dhd_wlfc_commit_packets(dhdp->wlfc_state, (f_commitpkt_t)dhd_bus_txdata,
- dhdp->bus);
- if (((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if) {
- ((athost_wl_status_info_t*)dhdp->wlfc_state)->toggle_host_if = 0;
- }
- dhd_os_wlfc_unblock(dhdp);
- }
- else
- /* non-proptxstatus way */
- ret = dhd_bus_txdata(dhdp->bus, pktbuf);
- #else
- ret = dhd_bus_txdata(dhdp->bus, pktbuf);
- #endif /* PROP_TXSTATUS */
- return ret;
- }
- int
- dhd_start_xmit(struct sk_buff *skb, struct net_device *net)
- {
- int ret;
- void *pktbuf;
- dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
- int ifidx;
- #ifdef WLMEDIA_HTSF
- uint8 htsfdlystat_sz = dhd->pub.htsfdlystat_sz;
- #else
- uint8 htsfdlystat_sz = 0;
- #endif
- DHD_TRACE(("%s: Enter\n", __FUNCTION__));
- DHD_OS_WAKE_LOCK(&dhd->pub);
- /* Reject if down */
- if (!dhd->pub.up || (dhd->pub.busstate == DHD_BUS_DOWN)) {
- DHD_ERROR(("%s: xmit rejected pub.up=%d busstate=%d \n",
- __FUNCTION__, dhd->pub.up, dhd->pub.busstate));
- netif_stop_queue(net);
- /* Send Event when bus down detected during data session */
- if (dhd->pub.busstate == DHD_BUS_DOWN) {
- DHD_ERROR(("%s: Event HANG sent up\n", __FUNCTION__));
- net_os_send_hang_message(net);
- }
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- return -ENODEV;
- }
- ifidx = dhd_net2idx(dhd, net);
- if (ifidx == DHD_BAD_IF) {
- DHD_ERROR(("%s: bad ifidx %d\n", __FUNCTION__, ifidx));
- netif_stop_queue(net);
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- return -ENODEV;
- }
- /* Make sure there's enough room for any header */
- if (skb_headroom(skb) < dhd->pub.hdrlen + htsfdlystat_sz) {
- struct sk_buff *skb2;
- DHD_INFO(("%s: insufficient headroom\n",
- dhd_ifname(&dhd->pub, ifidx)));
- dhd->pub.tx_realloc++;
- skb2 = skb_realloc_headroom(skb, dhd->pub.hdrlen + htsfdlystat_sz);
- dev_kfree_skb(skb);
- if ((skb = skb2) == NULL) {
- DHD_ERROR(("%s: skb_realloc_headroom failed\n",
- dhd_ifname(&dhd->pub, ifidx)));
- ret = -ENOMEM;
- goto done;
- }
- }
- /* Convert to packet */
- if (!(pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb))) {
- DHD_ERROR(("%s: PKTFRMNATIVE failed\n",
- dhd_ifname(&dhd->pub, ifidx)));
- dev_kfree_skb_any(skb);
- ret = -ENOMEM;
- goto done;
- }
- #ifdef WLMEDIA_HTSF
- if (htsfdlystat_sz && PKTLEN(dhd->pub.osh, pktbuf) >= ETHER_ADDR_LEN) {
- uint8 *pktdata = (uint8 *)PKTDATA(dhd->pub.osh, pktbuf);
- struct ether_header *eh = (struct ether_header *)pktdata;
- if (!ETHER_ISMULTI(eh->ether_dhost) &&
- (ntoh16(eh->ether_type) == ETHER_TYPE_IP)) {
- eh->ether_type = hton16(ETHER_TYPE_BRCM_PKTDLYSTATS);
- }
- }
- #endif
- ret = dhd_sendpkt(&dhd->pub, ifidx, pktbuf);
- done:
- if (ret)
- dhd->pub.dstats.tx_dropped++;
- else
- dhd->pub.tx_packets++;
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- /* Return ok: we always eat the packet */
- return 0;
- }
- void
- dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool state)
- {
- struct net_device *net;
- dhd_info_t *dhd = dhdp->info;
- int i;
- DHD_TRACE(("%s: Enter\n", __FUNCTION__));
- dhdp->txoff = state;
- ASSERT(dhd);
- if (ifidx == ALL_INTERFACES) {
- /* Flow control on all active interfaces */
- for (i = 0; i < DHD_MAX_IFS; i++) {
- if (dhd->iflist[i]) {
- net = dhd->iflist[i]->net;
- if (state == ON)
- netif_stop_queue(net);
- else
- netif_wake_queue(net);
- }
- }
- }
- else {
- if (dhd->iflist[ifidx]) {
- net = dhd->iflist[ifidx]->net;
- if (state == ON)
- netif_stop_queue(net);
- else
- netif_wake_queue(net);
- }
- }
- }
- void
- dhd_rx_frame(dhd_pub_t *dhdp, int ifidx, void *pktbuf, int numpkt, uint8 chan)
- {
- dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
- struct sk_buff *skb;
- uchar *eth;
- uint len;
- void *data, *pnext = NULL, *save_pktbuf;
- int i;
- dhd_if_t *ifp;
- wl_event_msg_t event;
- int tout = DHD_PACKET_TIMEOUT;
- DHD_TRACE(("%s: Enter\n", __FUNCTION__));
- save_pktbuf = pktbuf;
- for (i = 0; pktbuf && i < numpkt; i++, pktbuf = pnext) {
- struct ether_header *eh;
- struct dot11_llc_snap_header *lsh;
- ifp = dhd->iflist[ifidx];
- if (ifp == NULL) {
- DHD_ERROR(("%s: ifp is NULL. drop packet\n",
- __FUNCTION__));
- PKTFREE(dhdp->osh, pktbuf, TRUE);
- continue;
- }
- /* Dropping packets before registering net device to avoid kernel panic */
- if (!ifp->net || ifp->net->reg_state != NETREG_REGISTERED ||
- !dhd->pub.up) {
- DHD_ERROR(("%s: net device is NOT registered yet. drop packet\n",
- __FUNCTION__));
- PKTFREE(dhdp->osh, pktbuf, TRUE);
- continue;
- }
- pnext = PKTNEXT(dhdp->osh, pktbuf);
- PKTSETNEXT(wl->sh.osh, pktbuf, NULL);
- eh = (struct ether_header *)PKTDATA(wl->sh.osh, pktbuf);
- lsh = (struct dot11_llc_snap_header *)&eh[1];
- if ((ntoh16(eh->ether_type) < ETHER_TYPE_MIN) &&
- (PKTLEN(wl->sh.osh, pktbuf) >= RFC1042_HDR_LEN) &&
- bcmp(lsh, BT_SIG_SNAP_MPROT, DOT11_LLC_SNAP_HDR_LEN - 2) == 0 &&
- lsh->type == HTON16(BTA_PROT_L2CAP)) {
- amp_hci_ACL_data_t *ACL_data = (amp_hci_ACL_data_t *)
- ((uint8 *)eh + RFC1042_HDR_LEN);
- ACL_data = NULL;
- }
- #ifdef PROP_TXSTATUS
- if (dhdp->wlfc_state && PKTLEN(wl->sh.osh, pktbuf) == 0) {
- /* WLFC may send header only packet when
- there is an urgent message but no packet to
- piggy-back on
- */
- ((athost_wl_status_info_t*)dhdp->wlfc_state)->stats.wlfc_header_only_pkt++;
- PKTFREE(dhdp->osh, pktbuf, TRUE);
- continue;
- }
- #endif
- skb = PKTTONATIVE(dhdp->osh, pktbuf);
- /* Get the protocol, maintain skb around eth_type_trans()
- * The main reason for this hack is for the limitation of
- * Linux 2.4 where 'eth_type_trans' uses the 'net->hard_header_len'
- * to perform skb_pull inside vs ETH_HLEN. Since to avoid
- * coping of the packet coming from the network stack to add
- * BDC, Hardware header etc, during network interface registration
- * we set the 'net->hard_header_len' to ETH_HLEN + extra space required
- * for BDC, Hardware header etc. and not just the ETH_HLEN
- */
- eth = skb->data;
- len = skb->len;
- ifp = dhd->iflist[ifidx];
- if (ifp == NULL)
- ifp = dhd->iflist[0];
- ASSERT(ifp);
- skb->dev = ifp->net;
- skb->protocol = eth_type_trans(skb, skb->dev);
- if (skb->pkt_type == PACKET_MULTICAST) {
- dhd->pub.rx_multicast++;
- }
- skb->data = eth;
- skb->len = len;
- #ifdef WLMEDIA_HTSF
- dhd_htsf_addrxts(dhdp, pktbuf);
- #endif
- /* Strip header, count, deliver upward */
- skb_pull(skb, ETH_HLEN);
- /* Process special event packets and then discard them */
- if (ntoh16(skb->protocol) == ETHER_TYPE_BRCM) {
- dhd_wl_host_event(dhd, &ifidx,
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
- skb->mac_header,
- #else
- skb->mac.raw,
- #endif
- &event,
- &data);
- wl_event_to_host_order(&event);
- if (event.event_type == WLC_E_BTA_HCI_EVENT) {
- dhd_bta_doevt(dhdp, data, event.datalen);
- }
- tout = DHD_EVENT_TIMEOUT;
- }
- ASSERT(ifidx < DHD_MAX_IFS && dhd->iflist[ifidx]);
- if (dhd->iflist[ifidx] && !dhd->iflist[ifidx]->state)
- ifp = dhd->iflist[ifidx];
- if (ifp->net)
- ifp->net->last_rx = jiffies;
- dhdp->dstats.rx_bytes += skb->len;
- dhdp->rx_packets++; /* Local count */
- if (in_interrupt()) {
- netif_rx(skb);
- } else {
- /* If the receive is not processed inside an ISR,
- * the softirqd must be woken explicitly to service
- * the NET_RX_SOFTIRQ. In 2.6 kernels, this is handled
- * by netif_rx_ni(), but in earlier kernels, we need
- * to do it manually.
- */
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
- netif_rx_ni(skb);
- #else
- ulong flags;
- netif_rx(skb);
- local_irq_save(flags);
- RAISE_RX_SOFTIRQ();
- local_irq_restore(flags);
- #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) */
- }
- }
- DHD_OS_WAKE_LOCK_TIMEOUT_ENABLE(dhdp, tout);
- }
- void
- dhd_event(struct dhd_info *dhd, char *evpkt, int evlen, int ifidx)
- {
- /* Linux version has nothing to do */
- return;
- }
- void
- dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success)
- {
- uint ifidx;
- dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
- struct ether_header *eh;
- uint16 type;
- uint len;
- dhd_prot_hdrpull(dhdp, &ifidx, txp);
- eh = (struct ether_header *)PKTDATA(dhdp->osh, txp);
- type = ntoh16(eh->ether_type);
- if (type == ETHER_TYPE_802_1X)
- atomic_dec(&dhd->pend_8021x_cnt);
- /* Crack open the packet and check to see if it is BT HCI ACL data packet.
- * If yes generate packet completion event.
- */
- len = PKTLEN(dhdp->osh, txp);
- /* Generate ACL data tx completion event locally to avoid SDIO bus transaction */
- if ((type < ETHER_TYPE_MIN) && (len >= RFC1042_HDR_LEN)) {
- struct dot11_llc_snap_header *lsh = (struct dot11_llc_snap_header *)&eh[1];
- if (bcmp(lsh, BT_SIG_SNAP_MPROT, DOT11_LLC_SNAP_HDR_LEN - 2) == 0 &&
- ntoh16(lsh->type) == BTA_PROT_L2CAP) {
- dhd_bta_tx_hcidata_complete(dhdp, txp, success);
- }
- }
- }
- static struct net_device_stats *
- dhd_get_stats(struct net_device *net)
- {
- dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
- dhd_if_t *ifp;
- int ifidx;
- DHD_TRACE(("%s: Enter\n", __FUNCTION__));
- ifidx = dhd_net2idx(dhd, net);
- if (ifidx == DHD_BAD_IF)
- return NULL;
- ifp = dhd->iflist[ifidx];
- ASSERT(dhd && ifp);
- if (dhd->pub.up) {
- /* Use the protocol to get dongle stats */
- dhd_prot_dstats(&dhd->pub);
- }
- /* Copy dongle stats to net device stats */
- ifp->stats.rx_packets = dhd->pub.dstats.rx_packets;
- ifp->stats.tx_packets = dhd->pub.dstats.tx_packets;
- ifp->stats.rx_bytes = dhd->pub.dstats.rx_bytes;
- ifp->stats.tx_bytes = dhd->pub.dstats.tx_bytes;
- ifp->stats.rx_errors = dhd->pub.dstats.rx_errors;
- ifp->stats.tx_errors = dhd->pub.dstats.tx_errors;
- ifp->stats.rx_dropped = dhd->pub.dstats.rx_dropped;
- ifp->stats.tx_dropped = dhd->pub.dstats.tx_dropped;
- ifp->stats.multicast = dhd->pub.dstats.multicast;
- return &ifp->stats;
- }
- #ifdef DHDTHREAD
- static int
- dhd_watchdog_thread(void *data)
- {
- tsk_ctl_t *tsk = (tsk_ctl_t *)data;
- dhd_info_t *dhd = (dhd_info_t *)tsk->parent;
- /* This thread doesn't need any user-level access,
- * so get rid of all our resources
- */
- if (dhd_watchdog_prio > 0) {
- struct sched_param param;
- param.sched_priority = (dhd_watchdog_prio < MAX_RT_PRIO)?
- dhd_watchdog_prio:(MAX_RT_PRIO-1);
- setScheduler(current, SCHED_FIFO, ¶m);
- }
- DAEMONIZE("dhd_watchdog");
- /* Run until signal received */
- complete(&tsk->completed);
- while (1)
- if (down_interruptible (&tsk->sema) == 0) {
- unsigned long flags;
- SMP_RD_BARRIER_DEPENDS();
- if (tsk->terminated) {
- break;
- }
- dhd_os_sdlock(&dhd->pub);
- if (dhd->pub.dongle_reset == FALSE) {
- DHD_TIMER(("%s:\n", __FUNCTION__));
- /* Call the bus module watchdog */
- dhd_bus_watchdog(&dhd->pub);
- flags = dhd_os_spin_lock(&dhd->pub);
- /* Count the tick for reference */
- dhd->pub.tickcnt++;
- /* Reschedule the watchdog */
- if (dhd->wd_timer_valid)
- mod_timer(&dhd->timer,
- jiffies + dhd_watchdog_ms * HZ / 1000);
- dhd_os_spin_unlock(&dhd->pub, flags);
- }
- dhd_os_sdunlock(&dhd->pub);
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- } else {
- break;
- }
- complete_and_exit(&tsk->completed, 0);
- }
- #endif /* DHDTHREAD */
- static void dhd_watchdog(ulong data)
- {
- dhd_info_t *dhd = (dhd_info_t *)data;
- unsigned long flags;
- DHD_OS_WAKE_LOCK(&dhd->pub);
- if (dhd->pub.dongle_reset) {
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- return;
- }
- #ifdef DHDTHREAD
- if (dhd->thr_wdt_ctl.thr_pid >= 0) {
- up(&dhd->thr_wdt_ctl.sema);
- return;
- }
- #endif /* DHDTHREAD */
- dhd_os_sdlock(&dhd->pub);
- /* Call the bus module watchdog */
- dhd_bus_watchdog(&dhd->pub);
- flags = dhd_os_spin_lock(&dhd->pub);
- /* Count the tick for reference */
- dhd->pub.tickcnt++;
- /* Reschedule the watchdog */
- if (dhd->wd_timer_valid)
- mod_timer(&dhd->timer, jiffies + dhd_watchdog_ms * HZ / 1000);
- dhd_os_spin_unlock(&dhd->pub, flags);
- dhd_os_sdunlock(&dhd->pub);
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- }
- #ifdef DHDTHREAD
- static int
- dhd_dpc_thread(void *data)
- {
- tsk_ctl_t *tsk = (tsk_ctl_t *)data;
- dhd_info_t *dhd = (dhd_info_t *)tsk->parent;
- /* This thread doesn't need any user-level access,
- * so get rid of all our resources
- */
- if (dhd_dpc_prio > 0)
- {
- struct sched_param param;
- param.sched_priority = (dhd_dpc_prio < MAX_RT_PRIO)?dhd_dpc_prio:(MAX_RT_PRIO-1);
- setScheduler(current, SCHED_FIFO, ¶m);
- }
- DAEMONIZE("dhd_dpc");
- /* DHD_OS_WAKE_LOCK is called in dhd_sched_dpc[dhd_linux.c] down below */
- /* signal: thread has started */
- complete(&tsk->completed);
- /* Run until signal received */
- while (1) {
- if (down_interruptible(&tsk->sema) == 0) {
- SMP_RD_BARRIER_DEPENDS();
- if (tsk->terminated) {
- break;
- }
- /* Call bus dpc unless it indicated down (then clean stop) */
- if (dhd->pub.busstate != DHD_BUS_DOWN) {
- if (dhd_bus_dpc(dhd->pub.bus)) {
- up(&tsk->sema);
- }
- else {
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- }
- } else {
- dhd_bus_stop(dhd->pub.bus, TRUE);
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- }
- }
- else
- break;
- }
- complete_and_exit(&tsk->completed, 0);
- }
- #endif /* DHDTHREAD */
- static void
- dhd_dpc(ulong data)
- {
- dhd_info_t *dhd;
- dhd = (dhd_info_t *)data;
- /* this (tasklet) can be scheduled in dhd_sched_dpc[dhd_linux.c]
- * down below , wake lock is set,
- * the tasklet is initialized in dhd_attach()
- */
- /* Call bus dpc unless it indicated down (then clean stop) */
- if (dhd->pub.busstate != DHD_BUS_DOWN) {
- if (dhd_bus_dpc(dhd->pub.bus))
- tasklet_schedule(&dhd->tasklet);
- else
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- } else {
- dhd_bus_stop(dhd->pub.bus, TRUE);
- DHD_OS_WAKE_UNLOCK(&dhd->pub);
- }
- }
- void
- dhd_sched_dpc(dhd_pub_t *dhdp)
- {
- dhd_info_t *dhd = (dhd_info_t *)dhdp->info;
- DHD_OS_WAKE_LOCK(dhdp);
- #ifdef DHDTHREAD
- if (dhd->thr_dpc_ctl.thr_pid >= 0) {
- up(&dhd->thr_dpc_ctl.sema);
- return;
- }
- #endif /* DHDTHREAD */
- tasklet_schedule(&dhd->tasklet);
- }
- #ifdef TOE
- /* Retrieve current toe component enables, which are kept as a bitmap in toe_ol iovar */
- static int
- dhd_toe_get(dhd_info_t *dhd, int ifidx, uint32 *toe_ol)
- {
- wl_ioctl_t ioc;
- char buf[32];
- int ret;
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_GET_VAR;
- ioc.buf = buf;
- ioc.len = (uint)sizeof(buf);
- ioc.set = FALSE;
- strcpy(buf, "toe_ol");
- if ((ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) {
- /* Check for older dongle image that doesn't support toe_ol */
- if (ret == -EIO) {
- DHD_ERROR(("%s: toe not supported by device\n",
- dhd_ifname(&dhd->pub, ifidx)));
- return -EOPNOTSUPP;
- }
- DHD_INFO(("%s: could not get toe_ol: ret=%d\n", dhd_ifname(&dhd->pub, ifidx), ret));
- return ret;
- }
- memcpy(toe_ol, buf, sizeof(uint32));
- return 0;
- }
- /* Set current toe component enables in toe_ol iovar, and set toe global enable iovar */
- static int
- dhd_toe_set(dhd_info_t *dhd, int ifidx, uint32 toe_ol)
- {
- wl_ioctl_t ioc;
- char buf[32];
- int toe, ret;
- memset(&ioc, 0, sizeof(ioc));
- ioc.cmd = WLC_SET_VAR;
- ioc.buf = buf;
- ioc.len = (uint)sizeof(buf);
- ioc.set = TRUE;
- /* Set toe_ol as requested */
- strcpy(buf, "toe_ol");
- memcpy(&buf[sizeof("toe_ol")], &toe_ol, sizeof(uint32));
- if ((ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) {
- DHD_ERROR(("%s: could not set toe_ol: ret=%d\n",
- dhd_ifname(&dhd->pub, ifidx), ret));
- return ret;
- }
- /* Enable toe globally only if any components are enabled. */
- toe = (toe_ol != 0);
- strcpy(buf, "toe");
- memcpy(&buf[sizeof("toe")], &toe, sizeof(uint32));
- if ((ret = dhd_wl_ioctl(&dhd->pub, ifidx, &ioc, ioc.buf, ioc.len)) < 0) {
- DHD_ERROR(("%s: could not set toe: ret=%d\n", dhd_ifname(&dhd->pub, ifidx), ret));
- return ret;
- }
- return 0;
- }
- #endif /* TOE */
- #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
- static void
- dhd_ethtool_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info)
- {
- dhd_info_t *dhd = *(dhd_info_t **)netdev_priv(net);
- sprintf(info->driver, "wl");
- sprintf(info->version, "%lu", dhd->pub.drv_version);
- }
- struct ethtool_ops dhd_ethtool_ops = {
- .get_drvinfo = dhd_ethtool_get_drvinfo
- };
- #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) */
- #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 4, 2)
- static int
- dhd_ethtool(dhd_info_t *dhd, void *uaddr)
- {
- struct ethtool_drvinfo info;
- char drvname[sizeof(info.driver)];
- uint32 cmd;
- #ifdef TOE
- struct ethtool_value edata;
- uint32 toe_cmpnt, csum_dir;
- int ret;
- #endif
- DHD_TRACE(("%s: Enter\n", __FUNCTION__));
- /* all ethtool calls start with a cmd word */
- if (copy_from_user(&cmd, uaddr, sizeof (uint32)))
- return -EFAULT;
- switch (cmd) {
- case ETHTOOL_GDRVINFO:
- /* Copy out any request driver name */
- if (copy_from_user(&info, uaddr, sizeof(info)))
- return -EFAULT;
- strncpy(drvname, info.driver, sizeof(info.driver));
- drvname[sizeof(info.driver)-1] = '\0';
- /* clear struct for return */
- memset(&info, 0, sizeof(info));
- info.cmd = cmd;
- /* if dhd requested, identify ourselves */
- if (strcmp(drvname, "?dhd") == 0) {
- sprintf(info.driver, "dhd");
- strcpy(info.version, EPI_VERSION_STR);
- }
- /* otherwise, require dongle to be up */
- else if (!dhd->pub.up) {
- DHD_ERROR(("%s: dongle is not up\n", __FUNCTION__));
- return -ENODEV;
- }
- /* finally, report dongle driver type */
- else if (dhd->pub.iswl)
- sprintf(info.driver, "wl");
- else
- sprintf(info.driver, "xx");
- sprintf(info.version, "%lu", dhd->pub.drv_version);
- if (copy_to_user(uaddr, &info, sizeof(info)))
- return -EFAULT;
- DHD_CTL(("%s: given %*s, returning %s\n", __FUNCTION__,
- (int)sizeof(drvname), drvname, info.driver));
- break;
- #ifdef TOE
- /* Get toe…