/* * Broadcom Dongle Host Driver (DHD), Linux-specific network interface * Basically selected code segments from usb-cdc.c and usb-rndis.c * * Copyright (C) 1999-2015, Broadcom Corporation * * Portions contributed by Nvidia * Copyright (C) 2015-2020, NVIDIA Corporation. All rights reserved. * * 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 531927 2015-02-04 14:00:07Z $ */ #include #include "dynamic.h" #include #include #ifdef SHOW_LOGTRACE #include #include #endif /* SHOW_LOGTRACE */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_ADAPTIVE_SCHED #include #endif /* ENABLE_ADAPTIVE_SCHED */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)) #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef DHD_L2_FILTER #include #endif #include #include #include #include #include #ifdef PCIE_FULL_DONGLE #include #endif #include #include #include /* Used for the bottom half, so same priority as the other irqthread */ #define DHD_DEFAULT_RT_PRIORITY (MAX_USER_RT_PRIO / 2) #ifdef WL_CFG80211 #include #endif #ifdef P2PONEINT #include #endif #ifdef PNO_SUPPORT #include #endif #ifdef CONFIG_COMPAT #include #endif #ifdef DHD_WMF #include #endif /* DHD_WMF */ #ifdef DHDTCPACK_SUPPRESS #include #endif /* DHDTCPACK_SUPPRESS */ #ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA #include "dhd_custom_sysfs_tegra.h" #include "dhd_custom_sysfs_tegra_scan.h" #include "dhd_custom_sysfs_tegra_stat.h" #define RX_CAPTURE(skb)\ {\ TEGRA_SYSFS_HISTOGRAM_WAKE_CNT_INC(skb);\ tegra_sysfs_histogram_tcpdump_rx(skb, __func__, __LINE__);\ }\ #define DPC_CAPTURE(void)\ {\ tegra_sysfs_dpc_pkt();\ } \ #else #define RX_CAPTURE(skb) #define DPC_CAPTURE(void) #endif #ifdef CONFIG_BCMDHD_CUSTOM_NET_PERF_TEGRA #include "dhd_custom_net_perf_tegra.h" #endif #ifdef WLMEDIA_HTSF #include #include #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 extern void dhd_dump_eapol_4way_message(int ifidx, char *dump_data, bool direction); #ifdef ENABLE_ADAPTIVE_SCHED #define DEFAULT_CPUFREQ_THRESH 1000000 /* threshold frequency : 1000000 = 1GHz */ #ifndef CUSTOM_CPUFREQ_THRESH #define CUSTOM_CPUFREQ_THRESH DEFAULT_CPUFREQ_THRESH #endif /* CUSTOM_CPUFREQ_THRESH */ #endif /* ENABLE_ADAPTIVE_SCHED */ /* enable HOSTIP cache update from the host side when an eth0:N is up */ #define AOE_IP_ALIAS_SUPPORT 1 #ifdef BCM_FD_AGGR #include #include #endif #ifdef PROP_TXSTATUS #include #include #endif #include #if defined(CUSTOMER_HW20) && defined(WLANAUDIO) #include #endif /* CUSTOMER_HW20 && WLANAUDIO */ /* Maximum STA per radio */ #define DHD_MAX_STA 32 const uint8 wme_fifo2ac[] = { 0, 1, 2, 3, 1, 1 }; const uint8 prio2fifo[8] = { 1, 0, 0, 1, 2, 2, 3, 3 }; #define WME_PRIO2AC(prio) wme_fifo2ac[prio2fifo[(prio)]] #ifdef ARP_OFFLOAD_SUPPORT void aoe_update_host_ipv4_table(dhd_pub_t *dhd_pub, u32 ipa, bool add, int idx); static int dhd_inetaddr_notifier_call(struct notifier_block *this, unsigned long event, void *ptr); static struct notifier_block dhd_inetaddr_notifier = { .notifier_call = dhd_inetaddr_notifier_call }; /* to make sure we won't register the same notifier twice, otherwise a loop is likely to be * created in kernel notifier link list (with 'next' pointing to itself) */ static bool dhd_inetaddr_notifier_registered = FALSE; #endif /* ARP_OFFLOAD_SUPPORT */ #ifdef CONFIG_IPV6 static int dhd_inet6addr_notifier_call(struct notifier_block *this, unsigned long event, void *ptr); static struct notifier_block dhd_inet6addr_notifier = { .notifier_call = dhd_inet6addr_notifier_call }; /* to make sure we won't register the same notifier twice, otherwise a loop is likely to be * created in kernel notifier link list (with 'next' pointing to itself) */ static bool dhd_inet6addr_notifier_registered = FALSE; #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(CONFIG_PM) #include 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) */ #if defined(OOB_INTR_ONLY) extern void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) static void dhd_hang_process(void *dhd_info, void *event_data, u8 event); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) MODULE_LICENSE("GPL and additional rights"); #endif /* LinuxVer */ #include #ifdef BCM_FD_AGGR #define DBUS_RX_BUFFER_SIZE_DHD(net) (BCM_RPC_TP_DNGL_AGG_MAX_BYTE) #else #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 #endif /* BCM_FD_AGGR */ #ifdef PROP_TXSTATUS extern bool dhd_wlfc_skip_fc(void); extern void dhd_wlfc_plat_init(void *dhd); extern void dhd_wlfc_plat_deinit(void *dhd); #endif /* PROP_TXSTATUS */ #ifdef BCMSDIO extern int dhd_slpauto_config(dhd_pub_t *dhd, s32 val); #endif #if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) const char * print_tainted() { return ""; } #endif /* LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) */ #if defined(CONFIG_HAS_EARLYSUSPEND) && defined(DHD_USE_EARLYSUSPEND) #include #endif /* defined(CONFIG_HAS_EARLYSUSPEND) && defined(DHD_USE_EARLYSUSPEND) */ extern int dhd_get_suspend_bcn_li_dtim(dhd_pub_t *dhd); #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); extern void dhd_pktfilter_offload_delete(dhd_pub_t *dhd, int id); #endif #ifdef READ_MACADDR extern int dhd_read_macaddr(struct dhd_info *dhd); #else static inline int dhd_read_macaddr(struct dhd_info *dhd) { return 0; } #endif #ifdef WRITE_MACADDR extern int dhd_write_macaddr(struct ether_addr *mac); #else static inline int dhd_write_macaddr(struct ether_addr *mac) { return 0; } #endif #if defined(DHD_DEBUG) static void dhd_mem_dump(void *dhd_info, void *event_info, u8 event); #endif /* DHD_DEBUG */ static int dhd_reboot_callback(struct notifier_block *this, unsigned long code, void *unused); static struct notifier_block dhd_reboot_notifier = { .notifier_call = dhd_reboot_callback, .priority = 1, }; typedef struct dhd_if_event { struct list_head list; wl_event_data_if_t event; char name[IFNAMSIZ+1]; uint8 mac[ETHER_ADDR_LEN]; } dhd_if_event_t; /* Interface control information */ typedef struct dhd_if { struct dhd_info *info; /* back pointer to dhd_info */ /* OS/stack specifics */ struct net_device *net; int idx; /* iface idx in dongle */ uint subunit; /* subunit */ uint8 mac_addr[ETHER_ADDR_LEN]; /* assigned MAC address */ bool set_macaddress; bool set_multicast; uint8 bssidx; /* bsscfg index for the interface */ bool attached; /* Delayed attachment when unset */ bool txflowcontrol; /* Per interface flow control indicator */ char name[IFNAMSIZ+1]; /* linux interface name */ struct net_device_stats stats; #ifdef DHD_WMF dhd_wmf_t wmf; /* per bsscfg wmf setting */ #endif /* DHD_WMF */ #ifdef PCIE_FULL_DONGLE struct list_head sta_list; /* sll of associated stations */ #if !defined(BCM_GMAC3) spinlock_t sta_list_lock; /* lock for manipulating sll */ #endif /* ! BCM_GMAC3 */ #endif /* PCIE_FULL_DONGLE */ uint32 ap_isolate; /* ap-isolation settings */ } 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 */ struct ipv6_work_info_t { uint8 if_idx; char ipv6_addr[16]; unsigned long event; }; #if defined(CUSTOMER_HW20) && defined(WLANAUDIO) #define MAX_WLANAUDIO_BLACKLIST 4 struct wlanaudio_blacklist { bool is_blacklist; uint32 cnt; ulong txfail_jiffies; struct ether_addr blacklist_addr; }; #endif /* CUSTOMER_HW20 && WLANAUDIO */ #if defined(DHD_DEBUG) typedef struct dhd_dump { uint8 *buf; int bufsize; } dhd_dump_t; #endif /* DHD_DEBUG */ /* When Perimeter locks are deployed, any blocking calls must be preceeded * with a PERIM UNLOCK and followed by a PERIM LOCK. * Examples of blocking calls are: schedule_timeout(), down_interruptible(), * wait_event_timeout(). */ /* Local private structure (extension of pub) */ typedef struct dhd_info { dhd_pub_t pub; dhd_if_t *iflist[DHD_MAX_IFS]; /* for supporting multiple interfaces */ void *p2p_del_ifp; void *adapter; /* adapter information, interrupt, fw path etc. */ char fw_path[PATH_MAX]; /* path to firmware image */ char nv_path[PATH_MAX]; /* path to nvram vars file */ 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; wait_queue_head_t d3ack_wait; uint32 default_wd_interval; struct timer_list timer; bool wd_timer_valid; struct tasklet_struct tasklet; spinlock_t sdlock; spinlock_t txqlock; spinlock_t dhd_lock; struct semaphore sdsem; tsk_ctl_t thr_dpc_ctl; tsk_ctl_t thr_wdt_ctl; tsk_ctl_t thr_rxf_ctl; spinlock_t rxf_lock; bool rxthread_enabled; /* Wakelocks */ #if defined(CONFIG_PM_WAKELOCKS) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) struct wakeup_source wl_wifi; /* Wifi wakelock */ struct wakeup_source wl_rxwake; /* Wifi rx wakelock */ struct wakeup_source wl_ctrlwake; /* Wifi ctrl wakelock */ struct wakeup_source wl_wdwake; /* Wifi wd wakelock */ #ifdef BCMPCIE_OOB_HOST_WAKE struct wakeup_source wl_intrwake; /* Host wakeup wakelock */ #endif /* BCMPCIE_OOB_HOST_WAKE */ #endif /* CONFIG_PM_WAKELOCKS && LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27) */ #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; struct mutex dhd_suspend_mutex; #endif spinlock_t wakelock_spinlock; uint32 wakelock_counter; int wakelock_wd_counter; int wakelock_rx_timeout_enable; int wakelock_ctrl_timeout_enable; bool waive_wakelock; uint32 wakelock_before_waive; /* Thread to issue ioctl for multicast */ wait_queue_head_t ctrl_wait; atomic_t pend_8021x_cnt; dhd_attach_states_t dhd_state; #ifdef SHOW_LOGTRACE dhd_event_log_t event_data; #endif /* SHOW_LOGTRACE */ #if defined(CONFIG_HAS_EARLYSUSPEND) && defined(DHD_USE_EARLYSUSPEND) struct early_suspend early_suspend; #endif /* CONFIG_HAS_EARLYSUSPEND && DHD_USE_EARLYSUSPEND */ #ifdef ARP_OFFLOAD_SUPPORT u32 pend_ipaddr; #endif /* ARP_OFFLOAD_SUPPORT */ #ifdef BCM_FD_AGGR void *rpc_th; void *rpc_osh; struct timer_list rpcth_timer; bool rpcth_timer_active; bool fdaggr; #endif #ifdef DHDTCPACK_SUPPRESS spinlock_t tcpack_lock; #endif /* DHDTCPACK_SUPPRESS */ void *dhd_deferred_wq; #ifdef DEBUG_CPU_FREQ struct notifier_block freq_trans; int __percpu *new_freq; #endif unsigned int unit; struct notifier_block pm_notifier; #if defined(CUSTOMER_HW20) && defined(WLANAUDIO) struct wlanaudio_blacklist wlanaudio_blist[MAX_WLANAUDIO_BLACKLIST]; bool is_wlanaudio_blist; #endif /* CUSTOMER_HW20 && WLANAUDIO */ } dhd_info_t; #define DHDIF_FWDER(dhdif) FALSE /* Flag to indicate if we should download firmware on driver load */ uint dhd_download_fw_on_driverload = TRUE; /* 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]; /* flag to restrict p2p GO bw to 20Mhz */ u32 restrict_bw_20; /* backup buffer for firmware and nvram path */ char fw_bak_path[MOD_PARAM_PATHLEN]; char nv_bak_path[MOD_PARAM_PATHLEN]; /* information string to keep firmware, chio, cheip version info visiable from log */ char info_string[MOD_PARAM_INFOLEN]; module_param_string(info_string, info_string, MOD_PARAM_INFOLEN, 0444); int op_mode = 0; int disable_proptx = 0; module_param(op_mode, int, 0644); char clm_path[MOD_PARAM_PATHLEN]; module_param_string(clm_path, clm_path, MOD_PARAM_PATHLEN, 0660); unsigned long dpc_sleep_cnt; atomic_t dpc_bound = ATOMIC_INIT(8); /* ms */ atomic_t dpc_frame_time = ATOMIC_INIT(16); /* ms */ extern int wl_control_wl_start(struct net_device *dev); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && defined(BCMLXSDMMC) struct semaphore dhd_registration_sem; #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) */ /* deferred handlers */ static void dhd_ifadd_event_handler(void *handle, void *event_info, u8 event); static void dhd_ifdel_event_handler(void *handle, void *event_info, u8 event); static void dhd_set_mac_addr_handler(void *handle, void *event_info, u8 event); static void dhd_set_mcast_list_handler(void *handle, void *event_info, u8 event); #ifdef CONFIG_IPV6 static void dhd_inet6_work_handler(void *dhd_info, void *event_data, u8 event); #endif #ifdef WL_CFG80211 extern void dhd_netdev_free(struct net_device *ndev); #endif /* WL_CFG80211 */ /* Error bits */ module_param(dhd_msg_level, int, 0644); #ifdef ARP_OFFLOAD_SUPPORT /* ARP offload enable */ uint dhd_arp_enable = TRUE; module_param(dhd_arp_enable, uint, 0); /* ARP offload agent mode : Enable ARP Host Auto-Reply and ARP Peer Auto-Reply */ uint dhd_arp_mode = ARP_OL_AGENT | ARP_OL_PEER_AUTO_REPLY; module_param(dhd_arp_mode, uint, 0); #endif /* ARP_OFFLOAD_SUPPORT */ /* Disable Prop tx */ module_param(disable_proptx, int, 0644); /* 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, 0660); /* Watchdog interval */ /* extend watchdog expiration to 2 seconds when DPC is running */ #define WATCHDOG_EXTEND_INTERVAL (2000) uint dhd_watchdog_ms = CUSTOM_DHD_WATCHDOG_MS; 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, 0644); #endif /* defined(DHD_DEBUG) */ uint dhd_slpauto = TRUE; module_param(dhd_slpauto, uint, 0); #ifdef PKT_FILTER_SUPPORT /* Global Pkt filter enable control */ uint dhd_pkt_filter_enable = TRUE; module_param(dhd_pkt_filter_enable, uint, 0); #endif /* 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, 0); int dhd_watchdog_prio = 0; module_param(dhd_watchdog_prio, int, 0); /* DPC thread priority */ int dhd_dpc_prio = CUSTOM_DPC_PRIO_SETTING; module_param(dhd_dpc_prio, int, 0); /* RX frame thread priority */ int dhd_rxf_prio = CUSTOM_RXF_PRIO_SETTING; module_param(dhd_rxf_prio, int, 0); int passive_channel_skip = 0; module_param(passive_channel_skip, int, (S_IRUSR|S_IWUSR)); #if !defined(BCMDHDUSB) extern int dhd_dongle_ramsize; module_param(dhd_dongle_ramsize, int, 0); #endif /* BCMDHDUSB */ /* Keep track of number of instances */ static int dhd_found = 0; static int instance_base = 0; /* Starting instance number */ module_param(instance_base, int, 0644); #if defined(CUSTOMER_HW20) && defined(WLANAUDIO) dhd_info_t *dhd_global = NULL; #endif /* CUSTOMER_HW20 && WLANAUDIO */ /* DHD Perimiter lock only used in router with bypass forwarding. */ #define DHD_PERIM_RADIO_INIT() do { /* noop */ } while (0) #define DHD_PERIM_LOCK_TRY(unit, flag) do { /* noop */ } while (0) #define DHD_PERIM_UNLOCK_TRY(unit, flag) do { /* noop */ } while (0) #define DHD_PERIM_LOCK_ALL() do { /* noop */ } while (0) #define DHD_PERIM_UNLOCK_ALL() do { /* noop */ } while (0) #ifdef PCIE_FULL_DONGLE #if defined(BCM_GMAC3) #define DHD_IF_STA_LIST_LOCK_INIT(ifp) do { /* noop */ } while (0) #define DHD_IF_STA_LIST_LOCK(ifp, flags) ({ BCM_REFERENCE(flags); }) #define DHD_IF_STA_LIST_UNLOCK(ifp, flags) ({ BCM_REFERENCE(flags); }) #else /* ! BCM_GMAC3 */ #define DHD_IF_STA_LIST_LOCK_INIT(ifp) spin_lock_init(&(ifp)->sta_list_lock) #define DHD_IF_STA_LIST_LOCK(ifp, flags) \ spin_lock_irqsave(&(ifp)->sta_list_lock, (flags)) #define DHD_IF_STA_LIST_UNLOCK(ifp, flags) \ spin_unlock_irqrestore(&(ifp)->sta_list_lock, (flags)) #endif /* ! BCM_GMAC3 */ #endif /* PCIE_FULL_DONGLE */ /* Control fw roaming */ uint dhd_roam_disable = 0; /* Control radio state */ uint dhd_radio_up = 1; /* Network inteface name */ char iface_name[IFNAMSIZ] = {'\0'}; module_param_string(iface_name, iface_name, IFNAMSIZ, 0); /* The following are specific to the SDIO dongle */ /* IOCTL response timeout */ int dhd_ioctl_timeout_msec = IOCTL_RESP_TIMEOUT; /* Idle timeout for backplane clock */ int dhd_idletime = DHD_IDLETIME_TICKS; module_param(dhd_idletime, int, 0); /* Use polling */ uint dhd_poll = FALSE; module_param(dhd_poll, uint, 0); /* Use 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); #ifdef BCMSDIO /* 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 */ #endif /* BCMSDIO */ #ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA #define OZ_ETHERTYPE 0x892e extern atomic_t tegra_downgrade_ac; #endif #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 */ extern char dhd_version[]; int dhd_net_bus_devreset(struct net_device *dev, uint8 flag); static void dhd_net_if_lock_local(dhd_info_t *dhd); static void dhd_net_if_unlock_local(dhd_info_t *dhd); static void dhd_suspend_lock(dhd_pub_t *dhdp); static void dhd_suspend_unlock(dhd_pub_t *dhdp); #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); static void dhd_dpc(ulong data); /* forward decl */ extern int dhd_wait_pend8021x(struct net_device *dev); void dhd_os_wd_timer_extend(void *bus, bool extend); #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, size_t pktlen, wl_event_msg_t *event_ptr, void **data_ptr); #ifdef DHD_UNICAST_DHCP static const uint8 llc_snap_hdr[SNAP_HDR_LEN] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; static int dhd_get_pkt_ip_type(dhd_pub_t *dhd, void *skb, uint8 **data_ptr, int *len_ptr, uint8 *prot_ptr); static int dhd_get_pkt_ether_type(dhd_pub_t *dhd, void *skb, uint8 **data_ptr, int *len_ptr, uint16 *et_ptr, bool *snap_ptr); static int dhd_convert_dhcp_broadcast_ack_to_unicast(dhd_pub_t *pub, void *pktbuf, int ifidx); #endif /* DHD_UNICAST_DHCP */ #ifdef DHD_L2_FILTER static int dhd_l2_filter_block_ping(dhd_pub_t *pub, void *pktbuf, int ifidx); #endif void dhd_enable_packet_filter(int value, dhd_pub_t *dhd); #if defined(CONFIG_PM_SLEEP) static int dhd_pm_callback(struct notifier_block *nfb, unsigned long action, void *ignored) { int ret = NOTIFY_DONE; bool suspend = FALSE; dhd_info_t *dhdinfo = (dhd_info_t*)container_of(nfb, struct dhd_info, pm_notifier); BCM_REFERENCE(dhdinfo); switch (action) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: suspend = TRUE; break; case PM_POST_HIBERNATION: case PM_POST_SUSPEND: suspend = FALSE; break; } /* FIXME: dhd_wlfc_suspend acquires wd wakelock and calling in this function is breaking LP0. So moving this function call to dhd_set_suspend. Need to enable it after fixing wd wakelock issue. */ #if 0 #if defined(SUPPORT_P2P_GO_PS) #ifdef PROP_TXSTATUS if (suspend) { DHD_OS_WAKE_LOCK_WAIVE(&dhdinfo->pub); dhd_wlfc_suspend(&dhdinfo->pub); DHD_OS_WAKE_LOCK_RESTORE(&dhdinfo->pub); } else dhd_wlfc_resume(&dhdinfo->pub); #endif #endif /* defined(SUPPORT_P2P_GO_PS) */ #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)) && (LINUX_VERSION_CODE <= \ KERNEL_VERSION(2, 6, 39)) dhd_mmc_suspend = suspend; smp_mb(); #endif return ret; } static struct notifier_block dhd_pm_notifier = { .notifier_call = dhd_pm_callback, .priority = 10 }; /* to make sure we won't register the same notifier twice, otherwise a loop is likely to be * created in kernel notifier link list (with 'next' pointing to itself) */ static bool dhd_pm_notifier_registered = FALSE; extern int register_pm_notifier(struct notifier_block *nb); extern int unregister_pm_notifier(struct notifier_block *nb); #endif /* CONFIG_PM_SLEEP */ /* Request scheduling of the bus rx frame */ static void dhd_sched_rxf(dhd_pub_t *dhdp, void *skb); static void dhd_os_rxflock(dhd_pub_t *pub); static void dhd_os_rxfunlock(dhd_pub_t *pub); /** priv_link is the link between netdev and the dhdif and dhd_info structs. */ typedef struct dhd_dev_priv { dhd_info_t * dhd; /* cached pointer to dhd_info in netdevice priv */ dhd_if_t * ifp; /* cached pointer to dhd_if in netdevice priv */ int ifidx; /* interface index */ } dhd_dev_priv_t; #define DHD_DEV_PRIV_SIZE (sizeof(dhd_dev_priv_t)) #define DHD_DEV_PRIV(dev) ((dhd_dev_priv_t *)DEV_PRIV(dev)) #define DHD_DEV_INFO(dev) (((dhd_dev_priv_t *)DEV_PRIV(dev))->dhd) #define DHD_DEV_IFP(dev) (((dhd_dev_priv_t *)DEV_PRIV(dev))->ifp) #define DHD_DEV_IFIDX(dev) (((dhd_dev_priv_t *)DEV_PRIV(dev))->ifidx) /** Clear the dhd net_device's private structure. */ static inline void dhd_dev_priv_clear(struct net_device * dev) { dhd_dev_priv_t * dev_priv; ASSERT(dev != (struct net_device *)NULL); dev_priv = DHD_DEV_PRIV(dev); dev_priv->dhd = (dhd_info_t *)NULL; dev_priv->ifp = (dhd_if_t *)NULL; dev_priv->ifidx = DHD_BAD_IF; } /** Setup the dhd net_device's private structure. */ static inline void dhd_dev_priv_save(struct net_device * dev, dhd_info_t * dhd, dhd_if_t * ifp, int ifidx) { dhd_dev_priv_t * dev_priv; ASSERT(dev != (struct net_device *)NULL); dev_priv = DHD_DEV_PRIV(dev); dev_priv->dhd = dhd; dev_priv->ifp = ifp; dev_priv->ifidx = ifidx; } #ifdef PCIE_FULL_DONGLE /** Dummy objects are defined with state representing bad|down. * Performance gains from reducing branch conditionals, instruction parallelism, * dual issue, reducing load shadows, avail of larger pipelines. * Use DHD_XXX_NULL instead of (dhd_xxx_t *)NULL, whenever an object pointer * is accessed via the dhd_sta_t. */ /* Dummy dhd_info object */ dhd_info_t dhd_info_null = { #if defined(BCM_GMAC3) .fwdh = FWDER_NULL, #endif .pub = { .info = &dhd_info_null, #ifdef DHDTCPACK_SUPPRESS .tcpack_sup_mode = TCPACK_SUP_REPLACE, #endif /* DHDTCPACK_SUPPRESS */ .up = FALSE, .busstate = DHD_BUS_DOWN } }; #define DHD_INFO_NULL (&dhd_info_null) #define DHD_PUB_NULL (&dhd_info_null.pub) /* Dummy netdevice object */ struct net_device dhd_net_dev_null = { .reg_state = NETREG_UNREGISTERED }; #define DHD_NET_DEV_NULL (&dhd_net_dev_null) /* Dummy dhd_if object */ dhd_if_t dhd_if_null = { #if defined(BCM_GMAC3) .fwdh = FWDER_NULL, #endif #ifdef WMF .wmf = { .wmf_enable = TRUE }, #endif .info = DHD_INFO_NULL, .net = DHD_NET_DEV_NULL, .idx = DHD_BAD_IF }; #define DHD_IF_NULL (&dhd_if_null) #define DHD_STA_NULL ((dhd_sta_t *)NULL) /** Interface STA list management. */ /** Fetch the dhd_if object, given the interface index in the dhd. */ static inline dhd_if_t *dhd_get_ifp(dhd_pub_t *dhdp, uint32 ifidx); /** Alloc/Free a dhd_sta object from the dhd instances' sta_pool. */ static void dhd_sta_free(dhd_pub_t *pub, dhd_sta_t *sta); static dhd_sta_t * dhd_sta_alloc(dhd_pub_t * dhdp); /* Delete a dhd_sta or flush all dhd_sta in an interface's sta_list. */ static void dhd_if_del_sta_list(dhd_if_t * ifp); static void dhd_if_flush_sta(dhd_if_t * ifp); /* Construct/Destruct a sta pool. */ static int dhd_sta_pool_init(dhd_pub_t *dhdp, int max_sta); static void dhd_sta_pool_fini(dhd_pub_t *dhdp, int max_sta); static void dhd_sta_pool_clear(dhd_pub_t *dhdp, int max_sta); /* Return interface pointer */ static inline dhd_if_t *dhd_get_ifp(dhd_pub_t *dhdp, uint32 ifidx) { ASSERT(ifidx < DHD_MAX_IFS); if (ifidx >= DHD_MAX_IFS) return NULL; return dhdp->info->iflist[ifidx]; } /** Reset a dhd_sta object and free into the dhd pool. */ static void dhd_sta_free(dhd_pub_t * dhdp, dhd_sta_t * sta) { int prio; ASSERT((sta != DHD_STA_NULL) && (sta->idx != ID16_INVALID)); ASSERT((dhdp->staid_allocator != NULL) && (dhdp->sta_pool != NULL)); id16_map_free(dhdp->staid_allocator, sta->idx); for (prio = 0; prio < (int)NUMPRIO; prio++) sta->flowid[prio] = FLOWID_INVALID; sta->ifp = DHD_IF_NULL; /* dummy dhd_if object */ sta->ifidx = DHD_BAD_IF; bzero(sta->ea.octet, ETHER_ADDR_LEN); INIT_LIST_HEAD(&sta->list); sta->idx = ID16_INVALID; /* implying free */ } /** Allocate a dhd_sta object from the dhd pool. */ static dhd_sta_t * dhd_sta_alloc(dhd_pub_t * dhdp) { uint16 idx; dhd_sta_t * sta; dhd_sta_pool_t * sta_pool; ASSERT((dhdp->staid_allocator != NULL) && (dhdp->sta_pool != NULL)); idx = id16_map_alloc(dhdp->staid_allocator); if (idx == ID16_INVALID) { DHD_ERROR(("%s: cannot get free staid\n", __FUNCTION__)); return DHD_STA_NULL; } sta_pool = (dhd_sta_pool_t *)(dhdp->sta_pool); sta = &sta_pool[idx]; ASSERT((sta->idx == ID16_INVALID) && (sta->ifp == DHD_IF_NULL) && (sta->ifidx == DHD_BAD_IF)); sta->idx = idx; /* implying allocated */ return sta; } /** Delete all STAs in an interface's STA list. */ static void dhd_if_del_sta_list(dhd_if_t *ifp) { dhd_sta_t *sta, *next; unsigned long flags; DHD_IF_STA_LIST_LOCK(ifp, flags); list_for_each_entry_safe(sta, next, &ifp->sta_list, list) { #if defined(BCM_GMAC3) if (ifp->fwdh) { /* Remove sta from WOFA forwarder. */ fwder_deassoc(ifp->fwdh, (uint16 *)(sta->ea.octet), (wofa_t)sta); } #endif /* BCM_GMAC3 */ list_del(&sta->list); dhd_sta_free(&ifp->info->pub, sta); } DHD_IF_STA_LIST_UNLOCK(ifp, flags); return; } /** Router/GMAC3: Flush all station entries in the forwarder's WOFA database. */ static void dhd_if_flush_sta(dhd_if_t * ifp) { #if defined(BCM_GMAC3) if (ifp && (ifp->fwdh != FWDER_NULL)) { dhd_sta_t *sta, *next; unsigned long flags; DHD_IF_STA_LIST_LOCK(ifp, flags); list_for_each_entry_safe(sta, next, &ifp->sta_list, list) { /* Remove any sta entry from WOFA forwarder. */ fwder_flush(ifp->fwdh, (wofa_t)sta); } DHD_IF_STA_LIST_UNLOCK(ifp, flags); } #endif /* BCM_GMAC3 */ } /** Construct a pool of dhd_sta_t objects to be used by interfaces. */ static int dhd_sta_pool_init(dhd_pub_t *dhdp, int max_sta) { int idx, sta_pool_memsz; dhd_sta_t * sta; dhd_sta_pool_t * sta_pool; void * staid_allocator; ASSERT(dhdp != (dhd_pub_t *)NULL); ASSERT((dhdp->staid_allocator == NULL) && (dhdp->sta_pool == NULL)); /* dhd_sta objects per radio are managed in a table. id#0 reserved. */ staid_allocator = id16_map_init(dhdp->osh, max_sta, 1); if (staid_allocator == NULL) { DHD_ERROR(("%s: sta id allocator init failure\n", __FUNCTION__)); return BCME_ERROR; } /* Pre allocate a pool of dhd_sta objects (one extra). */ sta_pool_memsz = ((max_sta + 1) * sizeof(dhd_sta_t)); /* skip idx 0 */ sta_pool = (dhd_sta_pool_t *)MALLOC(dhdp->osh, sta_pool_memsz); if (sta_pool == NULL) { DHD_ERROR(("%s: sta table alloc failure\n", __FUNCTION__)); id16_map_fini(dhdp->osh, staid_allocator); return BCME_ERROR; } dhdp->sta_pool = sta_pool; dhdp->staid_allocator = staid_allocator; /* Initialize all sta(s) for the pre-allocated free pool. */ bzero((uchar *)sta_pool, sta_pool_memsz); for (idx = max_sta; idx >= 1; idx--) { /* skip sta_pool[0] */ sta = &sta_pool[idx]; sta->idx = id16_map_alloc(staid_allocator); ASSERT(sta->idx <= max_sta); } /* Now place them into the pre-allocated free pool. */ for (idx = 1; idx <= max_sta; idx++) { sta = &sta_pool[idx]; dhd_sta_free(dhdp, sta); } return BCME_OK; } /** Destruct the pool of dhd_sta_t objects. * Caller must ensure that no STA objects are currently associated with an if. */ static void dhd_sta_pool_fini(dhd_pub_t *dhdp, int max_sta) { dhd_sta_pool_t * sta_pool = (dhd_sta_pool_t *)dhdp->sta_pool; if (sta_pool) { int idx; int sta_pool_memsz = ((max_sta + 1) * sizeof(dhd_sta_t)); for (idx = 1; idx <= max_sta; idx++) { ASSERT(sta_pool[idx].ifp == DHD_IF_NULL); ASSERT(sta_pool[idx].idx == ID16_INVALID); } MFREE(dhdp->osh, dhdp->sta_pool, sta_pool_memsz); dhdp->sta_pool = NULL; } id16_map_fini(dhdp->osh, dhdp->staid_allocator); dhdp->staid_allocator = NULL; } /* Clear the pool of dhd_sta_t objects for built-in type driver */ static void dhd_sta_pool_clear(dhd_pub_t *dhdp, int max_sta) { int idx, sta_pool_memsz; dhd_sta_t * sta; dhd_sta_pool_t * sta_pool; void *staid_allocator; if (!dhdp) { DHD_ERROR(("%s: dhdp is NULL\n", __FUNCTION__)); return; } sta_pool = (dhd_sta_pool_t *)dhdp->sta_pool; staid_allocator = dhdp->staid_allocator; if (!sta_pool) { DHD_ERROR(("%s: sta_pool is NULL\n", __FUNCTION__)); return; } if (!staid_allocator) { DHD_ERROR(("%s: staid_allocator is NULL\n", __FUNCTION__)); return; } /* clear free pool */ sta_pool_memsz = ((max_sta + 1) * sizeof(dhd_sta_t)); bzero((uchar *)sta_pool, sta_pool_memsz); /* dhd_sta objects per radio are managed in a table. id#0 reserved. */ id16_map_clear(staid_allocator, max_sta, 1); /* Initialize all sta(s) for the pre-allocated free pool. */ for (idx = max_sta; idx >= 1; idx--) { /* skip sta_pool[0] */ sta = &sta_pool[idx]; sta->idx = id16_map_alloc(staid_allocator); ASSERT(sta->idx <= max_sta); } /* Now place them into the pre-allocated free pool. */ for (idx = 1; idx <= max_sta; idx++) { sta = &sta_pool[idx]; dhd_sta_free(dhdp, sta); } } /** Find STA with MAC address ea in an interface's STA list. */ dhd_sta_t * dhd_find_sta(void *pub, int ifidx, void *ea) { dhd_sta_t *sta, *next; dhd_if_t *ifp; unsigned long flags; ASSERT(ea != NULL); ifp = dhd_get_ifp((dhd_pub_t *)pub, ifidx); if (ifp == NULL) return DHD_STA_NULL; DHD_IF_STA_LIST_LOCK(ifp, flags); list_for_each_entry_safe(sta, next, &ifp->sta_list, list) { if (!memcmp(sta->ea.octet, ea, ETHER_ADDR_LEN)) { DHD_IF_STA_LIST_UNLOCK(ifp, flags); return sta; } } DHD_IF_STA_LIST_UNLOCK(ifp, flags); return DHD_STA_NULL; } /** Add STA into the interface's STA list. */ dhd_sta_t * dhd_add_sta(void *pub, int ifidx, void *ea) { dhd_sta_t *sta; dhd_if_t *ifp; unsigned long flags; ASSERT(ea != NULL); ifp = dhd_get_ifp((dhd_pub_t *)pub, ifidx); if (ifp == NULL) return DHD_STA_NULL; sta = dhd_sta_alloc((dhd_pub_t *)pub); if (sta == DHD_STA_NULL) { DHD_ERROR(("%s: Alloc failed\n", __FUNCTION__)); return DHD_STA_NULL; } memcpy(sta->ea.octet, ea, ETHER_ADDR_LEN); /* link the sta and the dhd interface */ sta->ifp = ifp; sta->ifidx = ifidx; INIT_LIST_HEAD(&sta->list); DHD_IF_STA_LIST_LOCK(ifp, flags); list_add_tail(&sta->list, &ifp->sta_list); #if defined(BCM_GMAC3) if (ifp->fwdh) { ASSERT(ISALIGNED(ea, 2)); /* Add sta to WOFA forwarder. */ fwder_reassoc(ifp->fwdh, (uint16 *)ea, (wofa_t)sta); } #endif /* BCM_GMAC3 */ DHD_IF_STA_LIST_UNLOCK(ifp, flags); return sta; } /** Delete STA from the interface's STA list. */ void dhd_del_sta(void *pub, int ifidx, void *ea) { dhd_sta_t *sta, *next; dhd_if_t *ifp; unsigned long flags; ASSERT(ea != NULL); ifp = dhd_get_ifp((dhd_pub_t *)pub, ifidx); if (ifp == NULL) return; DHD_IF_STA_LIST_LOCK(ifp, flags); list_for_each_entry_safe(sta, next, &ifp->sta_list, list) { if (!memcmp(sta->ea.octet, ea, ETHER_ADDR_LEN)) { #if defined(BCM_GMAC3) if (ifp->fwdh) { /* Found a sta, remove from WOFA forwarder. */ ASSERT(ISALIGNED(ea, 2)); fwder_deassoc(ifp->fwdh, (uint16 *)ea, (wofa_t)sta); } #endif /* BCM_GMAC3 */ list_del(&sta->list); dhd_sta_free(&ifp->info->pub, sta); } } DHD_IF_STA_LIST_UNLOCK(ifp, flags); return; } /** Add STA if it doesn't exist. Not reentrant. */ dhd_sta_t* dhd_findadd_sta(void *pub, int ifidx, void *ea) { dhd_sta_t *sta; sta = dhd_find_sta(pub, ifidx, ea); if (!sta) { /* Add entry */ sta = dhd_add_sta(pub, ifidx, ea); } return sta; } #else static inline void dhd_if_flush_sta(dhd_if_t * ifp) { } static inline void dhd_if_del_sta_list(dhd_if_t *ifp) {} static inline int dhd_sta_pool_init(dhd_pub_t *dhdp, int max_sta) { return BCME_OK; } static inline void dhd_sta_pool_fini(dhd_pub_t *dhdp, int max_sta) {} static inline void dhd_sta_pool_clear(dhd_pub_t *dhdp, int max_sta) {} dhd_sta_t *dhd_findadd_sta(void *pub, int ifidx, void *ea) { return NULL; } void dhd_del_sta(void *pub, int ifidx, void *ea) {} #endif /* PCIE_FULL_DONGLE */ /* Returns dhd iflist index correspondig the the bssidx provided by apps */ int dhd_bssidx2idx(dhd_pub_t *dhdp, uint32 bssidx) { dhd_if_t *ifp; dhd_info_t *dhd = dhdp->info; int i; ASSERT(bssidx < DHD_MAX_IFS); ASSERT(dhdp); for (i = 0; i < DHD_MAX_IFS; i++) { ifp = dhd->iflist[i]; if (ifp && (ifp->bssidx == bssidx)) { DHD_TRACE(("Index manipulated for %s from %d to %d\n", ifp->name, bssidx, i)); break; } } return i; } static inline int dhd_rxf_enqueue(dhd_pub_t *dhdp, void* skb) { uint32 store_idx; uint32 sent_idx; if (!skb) { DHD_ERROR(("dhd_rxf_enqueue: NULL skb!!!\n")); return BCME_ERROR; } dhd_os_rxflock(dhdp); store_idx = dhdp->store_idx; sent_idx = dhdp->sent_idx; if (dhdp->skbbuf[store_idx] != NULL) { /* Make sure the previous packets are processed */ dhd_os_rxfunlock(dhdp); #ifdef RXF_DEQUEUE_ON_BUSY DHD_TRACE(("dhd_rxf_enqueue: pktbuf not consumed %p, store idx %d sent idx %d\n", skb, store_idx, sent_idx)); return BCME_BUSY; #else /* RXF_DEQUEUE_ON_BUSY */ DHD_ERROR(("dhd_rxf_enqueue: pktbuf not consumed %p, store idx %d sent idx %d\n", skb, store_idx, sent_idx)); /* removed msleep here, should use wait_event_timeout if we * want to give rx frame thread a chance to run */ #if defined(WAIT_DEQUEUE) OSL_SLEEP(1); #endif return BCME_ERROR; #endif /* RXF_DEQUEUE_ON_BUSY */ } DHD_TRACE(("dhd_rxf_enqueue: Store SKB %p. idx %d -> %d\n", skb, store_idx, (store_idx + 1) & (MAXSKBPEND - 1))); dhdp->skbbuf[store_idx] = skb; dhdp->store_idx = (store_idx + 1) & (MAXSKBPEND - 1); dhd_os_rxfunlock(dhdp); return BCME_OK; } static inline void* dhd_rxf_dequeue(dhd_pub_t *dhdp) { uint32 store_idx; uint32 sent_idx; void *skb; dhd_os_rxflock(dhdp); store_idx = dhdp->store_idx; sent_idx = dhdp->sent_idx; skb = dhdp->skbbuf[sent_idx]; if (skb == NULL) { dhd_os_rxfunlock(dhdp); DHD_ERROR(("dhd_rxf_dequeue: Dequeued packet is NULL, store idx %d sent idx %d\n", store_idx, sent_idx)); return NULL; } dhdp->skbbuf[sent_idx] = NULL; dhdp->sent_idx = (sent_idx + 1) & (MAXSKBPEND - 1); DHD_TRACE(("dhd_rxf_dequeue: netif_rx_ni(%p), sent idx %d\n", skb, sent_idx)); dhd_os_rxfunlock(dhdp); return skb; } int dhd_process_cid_mac(dhd_pub_t *dhdp, bool prepost) { dhd_info_t *dhd = (dhd_info_t *)dhdp->info; if (prepost) { /* pre process */ dhd_read_macaddr(dhd); } else { /* post process */ dhd_write_macaddr(&dhd->pub.mac); } return 0; } #if defined(PKT_FILTER_SUPPORT) && !defined(GAN_LITE_NAT_KEEPALIVE_FILTER) static bool _turn_on_arp_filter(dhd_pub_t *dhd, int op_mode) { bool _apply = FALSE; /* In case of IBSS mode, apply arp pkt filter */ if (op_mode & DHD_FLAG_IBSS_MODE) { _apply = TRUE; goto exit; } /* In case of P2P GO or GC, apply pkt filter to pass arp pkt to host */ if ((dhd->arp_version == 1) && (op_mode & (DHD_FLAG_P2P_GC_MODE | DHD_FLAG_P2P_GO_MODE))) { _apply = TRUE; goto exit; } exit: return _apply; } #endif /* PKT_FILTER_SUPPORT && !GAN_LITE_NAT_KEEPALIVE_FILTER */ #ifdef PKT_FILTER_SUPPORT void dhd_set_packet_filter_mode(struct net_device *dev, char *command) { dhd_info_t *dhdi = *(dhd_info_t **)netdev_priv(dev); dhd_pub_t *dhdp = &dhdi->pub; dhdi->pub.pkt_filter_mode = bcm_strtoul(command, &command, 0); dhd_enable_packet_filter(1, dhdp); } int dhd_set_packet_filter_ports(struct net_device *dev, char *command) { int i = 0, error = BCME_OK, count = 0, get_count = 0, action = 0; uint16 portnum = 0, *ports = NULL, get_ports[WL_PKT_FILTER_PORTS_MAX]; dhd_info_t *dhdi = *(dhd_info_t **)netdev_priv(dev); dhd_pub_t *dhdp = &dhdi->pub; char iovbuf[WLC_IOCTL_SMLEN]; /* get action */ action = bcm_strtoul(command, &command, 0); if (action > PKT_FILTER_PORTS_MAX) return BCME_BADARG; if (action == PKT_FILTER_PORTS_LOOPBACK) { /* echo the loopback value if port filter is supported else error */ bcm_mkiovar("cap", NULL, 0, iovbuf, sizeof(iovbuf)); error = dhd_wl_ioctl_cmd(dhdp, WLC_GET_VAR, iovbuf, sizeof(iovbuf), FALSE, 0); if (error < 0) { DHD_ERROR(("%s: Get Capability failed (error=%d)\n", __FUNCTION__, error)); return error; } if (strstr(iovbuf, "pktfltr2")) return bcm_strtoul(command, &command, 0); else { DHD_ERROR(("%s: pktfltr2 is not supported\n", __FUNCTION__)); return BCME_UNSUPPORTED; } } if (action == PKT_FILTER_PORTS_CLEAR) { /* action 0 is clear all ports */ dhdp->pkt_filter_ports_count = 0; bzero(dhdp->pkt_filter_ports, sizeof(dhdp->pkt_filter_ports)); } else { portnum = bcm_strtoul(command, &command, 0); if (portnum == 0) { /* no ports to add or remove */ return BCME_BADARG; } /* get configured ports */ count = dhdp->pkt_filter_ports_count; ports = dhdp->pkt_filter_ports; if (action == PKT_FILTER_PORTS_ADD) { /* action 1 is add ports */ /* copy new ports */ while ((portnum != 0) && (count < WL_PKT_FILTER_PORTS_MAX)) { for (i = 0; i < count; i++) { /* duplicate port */ if (portnum == ports[i]) break; } if (portnum != ports[i]) ports[count++] = portnum; portnum = bcm_strtoul(command, &command, 0); } } else if ((action == PKT_FILTER_PORTS_DEL) && (count > 0)) { /* action 2 is remove ports */ bcopy(dhdp->pkt_filter_ports, get_ports, count * sizeof(uint16)); get_count = count; while (portnum != 0) { count = 0; for (i = 0; i < get_count; i++) { if (portnum != get_ports[i]) ports[count++] = get_ports[i]; } get_count = count; bcopy(ports, get_ports, count * sizeof(uint16)); portnum = bcm_strtoul(command, &command, 0); } } dhdp->pkt_filter_ports_count = count; } return error; } static void dhd_enable_packet_filter_ports(dhd_pub_t *dhd, bool enable) { int error = 0; wl_pkt_filter_ports_t *portlist = NULL; const uint pkt_filter_ports_buf_len = sizeof("pkt_filter_ports") + WL_PKT_FILTER_PORTS_FIXED_LEN + (WL_PKT_FILTER_PORTS_MAX * sizeof(uint16)); char pkt_filter_ports_buf[pkt_filter_ports_buf_len]; char iovbuf[pkt_filter_ports_buf_len]; DHD_ERROR(("%s: enable %d, in_suspend %d, mode %d, port count %d\n", __FUNCTION__, enable, dhd->in_suspend, dhd->pkt_filter_mode, dhd->pkt_filter_ports_count)); bzero(pkt_filter_ports_buf, sizeof(pkt_filter_ports_buf)); portlist = (wl_pkt_filter_ports_t*)pkt_filter_ports_buf; portlist->version = WL_PKT_FILTER_PORTS_VERSION; portlist->reserved = 0; if (enable) { if (!(dhd->pkt_filter_mode & PKT_FILTER_MODE_PORTS_ONLY)) { /* disable port filter */ portlist->count = 0; dhd_master_mode &= ~PKT_FILTER_MODE_PORTS_ONLY; dhd_master_mode |= PKT_FILTER_MODE_FORWARD_ON_MATCH; } else { /* enable port filter */ dhd_master_mode |= PKT_FILTER_MODE_PORTS_ONLY; if (dhd->pkt_filter_mode & PKT_FILTER_MODE_FORWARD_ON_MATCH) /* whitelist mode: FORWARD_ON_MATCH */ dhd_master_mode |= PKT_FILTER_MODE_FORWARD_ON_MATCH; else /* blacklist mode: DISCARD_ON_MATCH */ dhd_master_mode &= ~PKT_FILTER_MODE_FORWARD_ON_MATCH; portlist->count = dhd->pkt_filter_ports_count; bcopy(dhd->pkt_filter_ports, portlist->ports, dhd->pkt_filter_ports_count * sizeof(uint16)); } } else { /* disable port filter */ portlist->count = 0; dhd_master_mode &= ~PKT_FILTER_MODE_PORTS_ONLY; dhd_master_mode |= PKT_FILTER_MODE_FORWARD_ON_MATCH; } DHD_INFO(("%s: update: mode %d, port count %d\n", __FUNCTION__, dhd_master_mode, portlist->count)); /* update ports */ bcm_mkiovar("pkt_filter_ports", (char*)portlist, (WL_PKT_FILTER_PORTS_FIXED_LEN + (portlist->count * sizeof(uint16))), iovbuf, sizeof(iovbuf)); error = dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0); if (error < 0) DHD_ERROR(("%s: set pkt_filter_ports failed %d\n", __FUNCTION__, error)); /* update mode */ bcm_mkiovar("pkt_filter_mode", (char*)&dhd_master_mode, sizeof(dhd_master_mode), iovbuf, sizeof(iovbuf)); error = dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0); if (error < 0) DHD_ERROR(("%s: set pkt_filter_mode failed %d\n", __FUNCTION__, error)); return; } #endif /* PKT_FILTER_SUPPORT */ void dhd_set_packet_filter(dhd_pub_t *dhd) { #ifdef PKT_FILTER_SUPPORT int i; DHD_TRACE(("%s: enter\n", __FUNCTION__)); if (dhd_pkt_filter_enable) { for (i = 0; i < dhd->pktfilter_count; i++) { dhd_pktfilter_offload_set(dhd, dhd->pktfilter[i]); } } #endif /* PKT_FILTER_SUPPORT */ } void dhd_enable_packet_filter(int value, dhd_pub_t *dhd) { #ifdef PKT_FILTER_SUPPORT int i; DHD_TRACE(("%s: enter, value = %d\n", __FUNCTION__, value)); dhd_enable_packet_filter_ports(dhd, value); /* 1 - Enable packet filter, only allow unicast packet to send up */ /* 0 - Disable packet filter */ if (dhd_pkt_filter_enable && (!value || (dhd_support_sta_mode(dhd) && !dhd->dhcp_in_progress))) { for (i = 0; i < dhd->pktfilter_count; i++) { #ifndef GAN_LITE_NAT_KEEPALIVE_FILTER if (value && (i == DHD_ARP_FILTER_NUM) && !_turn_on_arp_filter(dhd, dhd->op_mode)) { DHD_TRACE(("Do not turn on ARP white list pkt filter:" "val %d, cnt %d, op_mode 0x%x\n", value, i, dhd->op_mode)); continue; } #endif /* !GAN_LITE_NAT_KEEPALIVE_FILTER */ dhd_pktfilter_offload_enable(dhd, dhd->pktfilter[i], value, dhd_master_mode); } } #endif /* PKT_FILTER_SUPPORT */ } static int dhd_set_suspend(int value, dhd_pub_t *dhd) { #ifndef SUPPORT_PM2_ONLY int power_mode = PM_MAX; #endif /* SUPPORT_PM2_ONLY */ /* wl_pkt_filter_enable_t enable_parm; */ int bcn_li_dtim = 0; /* Default bcn_li_dtim in resume mode is 0 */ #ifndef ENABLE_FW_ROAM_SUSPEND uint roamvar = 1; #endif /* ENABLE_FW_ROAM_SUSPEND */ uint nd_ra_filter = 0; int ret = 0; if (!dhd) return -ENODEV; DHD_TRACE(("%s: enter, value = %d in_suspend=%d\n", __FUNCTION__, value, dhd->in_suspend)); dhd_suspend_lock(dhd); #ifdef CUSTOM_SET_CPUCORE DHD_TRACE(("%s set cpucore(suspend%d)\n", __FUNCTION__, value)); /* set specific cpucore */ dhd_set_cpucore(dhd, TRUE); #endif /* CUSTOM_SET_CPUCORE */ if (dhd->up) { if (value && dhd->in_suspend) { #ifdef PKT_FILTER_SUPPORT dhd->early_suspended = 1; #endif #ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA tegra_sysfs_suspend(); #endif nvlogger_suspend_work(); /* Kernel suspended */ DHD_ERROR(("%s: force extra Suspend setting\n", __FUNCTION__)); #ifndef SUPPORT_PM2_ONLY dhd_wl_ioctl_cmd(dhd, WLC_SET_PM, (char *)&power_mode, sizeof(power_mode), TRUE, 0); #endif /* SUPPORT_PM2_ONLY */ /* Enable packet filter, only allow unicast packet to send up */ dhd_enable_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_suspend_bcn_li_dtim(dhd); if (dhd_iovar(dhd, 0, "bcn_li_dtim", (char *)&bcn_li_dtim, sizeof(bcn_li_dtim), NULL, 0, TRUE) < 0) DHD_ERROR(("%s: set dtim failed\n", __FUNCTION__)); #ifndef ENABLE_FW_ROAM_SUSPEND /* Disable firmware roaming during suspend */ if (!builtin_roam_disabled) { dhd_iovar(dhd, 0, "roam_off", (char *)&roamvar, sizeof(roamvar), NULL, 0, TRUE); } #endif /* ENABLE_FW_ROAM_SUSPEND */ if (FW_SUPPORTED(dhd, ndoe)) { /* enable IPv6 RA filter in firmware during suspend */ nd_ra_filter = 1; ret = dhd_iovar(dhd, 0, "nd_ra_filter_enable", (char *)&nd_ra_filter, sizeof(nd_ra_filter), NULL, 0, TRUE); if (ret < 0) DHD_ERROR(("nd_ra_filter :%d\n", ret)); } #if defined(SUPPORT_P2P_GO_PS) if (bcmdhd_support_p2p_go_ps) { #ifdef PROP_TXSTATUS DHD_OS_WAKE_LOCK_WAIVE(dhd); dhd_wlfc_suspend(dhd); DHD_OS_WAKE_LOCK_RESTORE(dhd); #endif } #endif /* defined(SUPPORT_P2P_GO_PS) */ } else { #ifdef PKT_FILTER_SUPPORT dhd->early_suspended = 0; #endif #ifdef CONFIG_BCMDHD_CUSTOM_SYSFS_TEGRA tegra_sysfs_resume(); #endif nvlogger_resume_work(); /* Kernel resumed */ DHD_ERROR(("%s: Remove extra suspend setting\n", __FUNCTION__)); #ifndef SUPPORT_PM2_ONLY power_mode = PM_FAST; dhd_wl_ioctl_cmd(dhd, WLC_SET_PM, (char *)&power_mode, sizeof(power_mode), TRUE, 0); #endif /* SUPPORT_PM2_ONLY */ #ifdef PKT_FILTER_SUPPORT /* disable pkt filter */ dhd_enable_packet_filter(0, dhd); #endif /* PKT_FILTER_SUPPORT */ /* restore pre-suspend setting for dtim_skip */ dhd_iovar(dhd, 0, "bcn_li_dtim", (char *)&bcn_li_dtim, sizeof(bcn_li_dtim), NULL, 0, TRUE); #ifndef ENABLE_FW_ROAM_SUSPEND roamvar = dhd_roam_disable; if (!builtin_roam_disabled) { dhd_iovar(dhd, 0, "roam_off", (char *)&roamvar, sizeof(roamvar), NULL, 0, TRUE); } #endif /* ENABLE_FW_ROAM_SUSPEND */ if (FW_SUPPORTED(dhd, ndoe)) { /* disable IPv6 RA filter in firmware during suspend */ nd_ra_filter = 0; ret = dhd_iovar(dhd, 0, "nd_ra_filter_enable", (char *)&nd_ra_filter, sizeof(nd_ra_filter), NULL, 0, TRUE); if (ret < 0) DHD_ERROR(("nd_ra_filter: %d\n", ret)); } #if defined(SUPPORT_P2P_GO_PS) if (bcmdhd_support_p2p_go_ps) { #ifdef PROP_TXSTATUS dhd_wlfc_resume(dhd); #endif } #endif /* defined(SUPPORT_P2P_GO_PS) */ } } dhd_suspend_unlock(dhd); return 0; } static int dhd_suspend_resume_helper(struct dhd_info *dhd, int val, int force) { dhd_pub_t *dhdp = &dhd->pub; int ret = 0; DHD_OS_WAKE_LOCK(dhdp); DHD_PERIM_LOCK(dhdp); /* Set flag when early suspend was called */ dhdp->in_suspend = val; if ((force || !dhdp->suspend_disable_flag) && dhd_support_sta_mode(dhdp)) { ret = dhd_set_suspend(val, dhdp); } DHD_PERIM_UNLOCK(dhdp); DHD_OS_WAKE_UNLOCK(dhdp); return ret; } #if defined(CONFIG_HAS_EARLYSUSPEND) && defined(DHD_USE_EARLYSUSPEND) static void dhd_early_suspend(struct early_suspend *h) { struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend); DHD_TRACE_HW4(("%s: enter\n", __FUNCTION__)); if (dhd) dhd_suspend_resume_helper(dhd, 1, 0); } static void dhd_late_resume(struct early_suspend *h) { struct dhd_info *dhd = container_of(h, struct dhd_info, early_suspend); DHD_TRACE_HW4(("%s: enter\n", __FUNCTION__)); if (dhd) dhd_suspend_resume_helper(dhd, 0, 0); } #endif /* CONFIG_HAS_EARLYSUSPEND && DHD_USE_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 = jiffies_to_usecs(1); } 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 ((!CAN_SLEEP()) || 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); init_waitqueue_head(&delay_wait); add_wait_queue(&delay_wait, &wait); set_current_state(TASK_INTERRUPTIBLE); (void)schedule_timeout(1); remove_wait_queue(&delay_wait, &wait); set_current_state(TASK_RUNNING); } return 0; } int dhd_net2idx(dhd_info_t *dhd, struct net_device *net) { int i = 0; if (!dhd) { DHD_ERROR(("%s : DHD_BAD_IF return\n", __FUNCTION__)); return DHD_BAD_IF; } while (i < DHD_MAX_IFS) { if (dhd->iflist[i] && dhd->iflist[i]->net && (dhd->iflist[i]->net == net)) return i; i++; } return DHD_BAD_IF; } struct net_device * dhd_idx2net(void *pub, int ifidx) { struct dhd_pub *dhd_pub = (struct dhd_pub *)pub; 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 */ } int dhd_ifidx2hostidx(dhd_info_t *dhd, int ifidx) { int i = DHD_MAX_IFS; ASSERT(dhd); while (--i > 0) if (dhd->iflist[i] && (dhd->iflist[i]->idx == ifidx)) break; DHD_TRACE(("%s: return hostidx %d for ifidx %d\n", __FUNCTION__, i, ifidx)); 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 (dhd->iflist[ifidx] == NULL) { DHD_ERROR(("%s: null i/f %d\n", __FUNCTION__, ifidx)); return ""; } if (dhd->iflist[ifidx]->net) return dhd->iflist[ifidx]->net->name; return ""; } 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]); if (dhd == NULL || dhd->iflist[ifidx] == NULL) return; dev = dhd->iflist[ifidx]->net; if (!dev) return; #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 /* LINUX_VERSION_CODE */ #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; } strncpy(bufp, "mcast_list", buflen - 1); bufp[buflen - 1] = '\0'; 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 /* LINUX_VERSION_CODE */ #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... */ allmulti = htol32(allmulti); ret = dhd_iovar(&dhd->pub, ifidx, "allmulti", (char *)&allmulti, sizeof(allmulti), NULL, 0, TRUE); if (ret < 0) { DHD_ERROR(("%s: set allmulti %d failed\n", dhd_ifname(&dhd->pub, ifidx), ltoh32(allmulti))); } /* 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))); } } int _dhd_set_mac_address(dhd_info_t *dhd, int ifidx, uint8 *addr) { int ret; ret = dhd_iovar(&dhd->pub, ifidx, "cur_etheraddr", (char *)addr, ETHER_ADDR_LEN, NULL, 0, TRUE); 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); if (ifidx == 0) memcpy(dhd->pub.mac.octet, 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_ifadd_event_handler(void *handle, void *event_info, u8 event) { dhd_info_t *dhd = handle; dhd_if_event_t *if_event = event_info; struct net_device *ndev; int ifidx, bssidx; int ret; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) struct wireless_dev *vwdev, *primary_wdev; struct net_device *primary_ndev; #endif /* OEM_ANDROID && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) */ if (event != DHD_WQ_WORK_IF_ADD) { DHD_ERROR(("%s: unexpected event \n", __FUNCTION__)); return; } if (!dhd) { DHD_ERROR(("%s: dhd info not available \n", __FUNCTION__)); return; } if (!if_event) { DHD_ERROR(("%s: event data is null \n", __FUNCTION__)); return; } dhd_net_if_lock_local(dhd); DHD_OS_WAKE_LOCK(&dhd->pub); DHD_PERIM_LOCK(&dhd->pub); ifidx = if_event->event.ifidx; bssidx = if_event->event.bssidx; DHD_TRACE(("%s: registering if with ifidx %d\n", __FUNCTION__, ifidx)); ndev = dhd_allocate_if(&dhd->pub, ifidx, if_event->name, if_event->mac, bssidx, TRUE); if (!ndev) { DHD_ERROR(("%s: net device alloc failed \n", __FUNCTION__)); goto done; } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) vwdev = kzalloc(sizeof(*vwdev), GFP_KERNEL); if (unlikely(!vwdev)) { WL_ERR(("Could not allocate wireless device\n")); goto done; } primary_ndev = dhd->pub.info->iflist[0]->net; primary_wdev = ndev_to_wdev(primary_ndev); vwdev->wiphy = primary_wdev->wiphy; vwdev->iftype = if_event->event.role; vwdev->netdev = ndev; ndev->ieee80211_ptr = vwdev; SET_NETDEV_DEV(ndev, wiphy_dev(vwdev->wiphy)); DHD_ERROR(("virtual interface(%s) is created\n", if_event->name)); #endif /* OEM_ANDROID && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0)) */ DHD_PERIM_UNLOCK(&dhd->pub); ret = dhd_register_if(&dhd->pub, ifidx, TRUE); DHD_PERIM_LOCK(&dhd->pub); if (ret != BCME_OK) { DHD_ERROR(("%s: dhd_register_if failed\n", __FUNCTION__)); dhd_remove_if(&dhd->pub, ifidx, TRUE); goto done; } #ifdef PCIE_FULL_DONGLE /* Turn on AP isolation in the firmware for interfaces operating in AP mode */ if (FW_SUPPORTED((&dhd->pub), ap) && !(DHD_IF_ROLE_STA(if_event->event.role))) { uint32 var_int = 1; dhd_iovar(&dhd->pub, ifidx, "ap_isolate", (char *)&var_int, sizeof(var_int), NULL, 0, TRUE); if (ret != BCME_OK) { DHD_ERROR(("%s: Failed to set ap_isolate to dongle\n", __FUNCTION__)); dhd_remove_if(&dhd->pub, ifidx, TRUE); } } #endif /* PCIE_FULL_DONGLE */ done: MFREE(dhd->pub.osh, if_event, sizeof(dhd_if_event_t)); DHD_PERIM_UNLOCK(&dhd->pub); DHD_OS_WAKE_UNLOCK(&dhd->pub); dhd_net_if_unlock_local(dhd); } static void dhd_ifdel_event_handler(void *handle, void *event_info, u8 event) { dhd_info_t *dhd = handle; int ifidx; dhd_if_event_t *if_event = event_info; if (event != DHD_WQ_WORK_IF_DEL) { DHD_ERROR(("%s: unexpected event \n", __FUNCTION__)); return; } if (!dhd) { DHD_ERROR(("%s: dhd info not available \n", __FUNCTION__)); return; } if (!if_event) { DHD_ERROR(("%s: event data is null \n", __FUNCTION__)); return; } dhd_net_if_lock_local(dhd); DHD_OS_WAKE_LOCK(&dhd->pub); DHD_PERIM_LOCK(&dhd->pub); ifidx = if_event->event.ifidx; DHD_TRACE(("Removing interface with idx %d\n", ifidx)); dhd_remove_if(&dhd->pub, ifidx, TRUE); MFREE(dhd->pub.osh, if_event, sizeof(dhd_if_event_t)); DHD_PERIM_UNLOCK(&dhd->pub); DHD_OS_WAKE_UNLOCK(&dhd->pub); dhd_net_if_unlock_local(dhd); } static void dhd_set_mac_addr_handler(void *handle, void *event_info, u8 event) { dhd_info_t *dhd = handle; dhd_if_t *ifp = event_info; if (event != DHD_WQ_WORK_SET_MAC) { DHD_ERROR(("%s: unexpected event \n", __FUNCTION__)); } if (!dhd) { DHD_ERROR(("%s: dhd info not available \n", __FUNCTION__)); return; } dhd_net_if_lock_local(dhd); DHD_OS_WAKE_LOCK(&dhd->pub); DHD_PERIM_LOCK(&dhd->pub); #ifdef SOFTAP { unsigned long flags; bool in_ap = FALSE; DHD_GENERAL_LOCK(&dhd->pub, flags); in_ap = (ap_net_dev != NULL); DHD_GENERAL_UNLOCK(&dhd->pub, flags); if (in_ap) { DHD_ERROR(("attempt to set MAC for %s in AP Mode, blocked. \n", ifp->net->name)); goto done; } } #endif /* SOFTAP */ if (ifp == NULL || !dhd->pub.up) { DHD_ERROR(("%s: interface info not available/down \n", __FUNCTION__)); goto done; } DHD_ERROR(("%s: MACID is overwritten\n", __FUNCTION__)); ifp->set_macaddress = FALSE; if (_dhd_set_mac_address(dhd, ifp->idx, ifp->mac_addr) == 0) DHD_INFO(("%s: MACID is overwritten\n", __FUNCTION__)); else DHD_ERROR(("%s: _dhd_set_mac_address() failed\n", __FUNCTION__)); done: DHD_PERIM_UNLOCK(&dhd->pub); DHD_OS_WAKE_UNLOCK(&dhd->pub); dhd_net_if_unlock_local(dhd); } static void dhd_set_mcast_list_handler(void *handle, void *event_info, u8 event) { dhd_info_t *dhd = handle; dhd_if_t *ifp = event_info; int ifidx; if (event != DHD_WQ_WORK_SET_MCAST_LIST) { DHD_ERROR(("%s: unexpected event \n", __FUNCTION__)); return; } if (!dhd) { DHD_ERROR(("%s: dhd info not available \n", __FUNCTION__)); return; } dhd_net_if_lock_local(dhd); DHD_OS_WAKE_LOCK(&dhd->pub); DHD_PERIM_LOCK(&dhd->pub); #ifdef SOFTAP { bool in_ap = FALSE; unsigned long flags; DHD_GENERAL_LOCK(&dhd->pub, flags); in_ap = (ap_net_dev != NULL); DHD_GENERAL_UNLOCK(&dhd->pub, flags); if (in_ap) { DHD_ERROR(("set MULTICAST list for %s in AP Mode, blocked. \n", ifp->net->name)); ifp->set_multicast = FALSE; goto done; } } #endif /* SOFTAP */ if (ifp == NULL || !dhd->pub.up) { DHD_ERROR(("%s: interface info not available/down \n", __FUNCTION__)); goto done; } ifidx = ifp->idx; _dhd_set_multicast_list(dhd, ifidx); DHD_INFO(("%s: set multicast list for if %d\n", __FUNCTION__, ifidx)); done: DHD_PERIM_UNLOCK(&dhd->pub); DHD_OS_WAKE_UNLOCK(&dhd->pub); dhd_net_if_unlock_local(dhd); } static int dhd_set_mac_address(struct net_device *dev, void *addr) { int ret = 0; dhd_info_t *dhd = DHD_DEV_INFO(dev); struct sockaddr *sa = (struct sockaddr *)addr; int ifidx; dhd_if_t *dhdif; ifidx = dhd_net2idx(dhd, dev); if (ifidx == DHD_BAD_IF) return -1; dhdif = dhd->iflist[ifidx]; dhd_net_if_lock_local(dhd); memcpy(dhdif->mac_addr, sa->sa_data, ETHER_ADDR_LEN); dhdif->set_macaddress = TRUE; dhd_net_if_unlock_local(dhd); dhd_deferred_schedule_work(dhd->dhd_deferred_wq, (void *)dhdif, DHD_WQ_WORK_SET_MAC, dhd_set_mac_addr_handler, DHD_WORK_PRIORITY_LOW); return ret; } static void dhd_set_multicast_list(struct net_device *dev) { dhd_info_t *dhd = DHD_DEV_INFO(dev); int ifidx; ifidx = dhd_net2idx(dhd, dev); if (ifidx == DHD_BAD_IF) return; dhd->iflist[ifidx]->set_multicast = TRUE; dhd_deferred_schedule_work(dhd->dhd_deferred_wq, (void *)dhd->iflist[ifidx], DHD_WQ_WORK_SET_MCAST_LIST, dhd_set_mcast_list_handler, DHD_