/* * Linux cfg80211 driver - Dongle Host Driver (DHD) related * * Copyright (C) 1999-2013, 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: wl_cfg80211.c,v 1.1.4.1.2.14 2011/02/09 01:40:07 Exp $ */ #include #include #include #include #include #ifdef PKT_FILTER_SUPPORT #include #include #endif extern struct wl_priv *wlcfg_drv_priv; #ifdef PKT_FILTER_SUPPORT extern uint dhd_pkt_filter_enable; extern uint dhd_master_mode; extern void dhd_pktfilter_offload_enable(dhd_pub_t * dhd, char *arg, int enable, int master_mode); #endif static int dhd_dongle_up = FALSE; #include #include #include #include #include static s32 wl_dongle_up(struct net_device *ndev, u32 up); /** * Function implementations */ s32 dhd_cfg80211_init(struct wl_priv *wl) { dhd_dongle_up = FALSE; return 0; } s32 dhd_cfg80211_deinit(struct wl_priv *wl) { dhd_dongle_up = FALSE; return 0; } s32 dhd_cfg80211_down(struct wl_priv *wl) { dhd_dongle_up = FALSE; return 0; } s32 dhd_cfg80211_set_p2p_info(struct wl_priv *wl, int val) { dhd_pub_t *dhd = (dhd_pub_t *)(wl->pub); dhd->op_mode |= val; WL_ERR(("Set : op_mode=0x%04x\n", dhd->op_mode)); #ifdef ARP_OFFLOAD_SUPPORT if (dhd->arp_version == 1) { /* IF P2P is enabled, disable arpoe */ dhd_arp_offload_set(dhd, 0); dhd_arp_offload_enable(dhd, false); } #endif /* ARP_OFFLOAD_SUPPORT */ return 0; } s32 dhd_cfg80211_clean_p2p_info(struct wl_priv *wl) { dhd_pub_t *dhd = (dhd_pub_t *)(wl->pub); dhd->op_mode &= ~(DHD_FLAG_P2P_GC_MODE | DHD_FLAG_P2P_GO_MODE); WL_ERR(("Clean : op_mode=0x%04x\n", dhd->op_mode)); #ifdef ARP_OFFLOAD_SUPPORT if (dhd->arp_version == 1) { /* IF P2P is disabled, enable arpoe back for STA mode. */ dhd_arp_offload_set(dhd, dhd_arp_mode); dhd_arp_offload_enable(dhd, true); } #endif /* ARP_OFFLOAD_SUPPORT */ return 0; } static s32 wl_dongle_up(struct net_device *ndev, u32 up) { s32 err = 0; err = wldev_ioctl(ndev, WLC_UP, &up, sizeof(up), true); if (unlikely(err)) { WL_ERR(("WLC_UP error (%d)\n", err)); } return err; } s32 dhd_config_dongle(struct wl_priv *wl, bool need_lock) { #ifndef DHD_SDALIGN #define DHD_SDALIGN 32 #endif struct net_device *ndev; s32 err = 0; WL_TRACE(("In\n")); if (dhd_dongle_up) { WL_ERR(("Dongle is already up\n")); return err; } ndev = wl_to_prmry_ndev(wl); if (need_lock) rtnl_lock(); err = wl_dongle_up(ndev, 0); if (unlikely(err)) { WL_ERR(("wl_dongle_up failed\n")); goto default_conf_out; } dhd_dongle_up = true; default_conf_out: if (need_lock) rtnl_unlock(); return err; } #ifdef CONFIG_NL80211_TESTMODE int dhd_cfg80211_testmode_cmd(struct wiphy *wiphy, void *data, int len) { struct sk_buff *reply; struct wl_priv *wl; dhd_pub_t *dhd; dhd_ioctl_t *ioc = data; int err = 0; WL_TRACE(("entry: cmd = %d\n", ioc->cmd)); wl = wiphy_priv(wiphy); dhd = wl->pub; DHD_OS_WAKE_LOCK(dhd); /* send to dongle only if we are not waiting for reload already */ if (dhd->hang_was_sent) { WL_ERR(("HANG was sent up earlier\n")); DHD_OS_WAKE_LOCK_CTRL_TIMEOUT_ENABLE(dhd, DHD_EVENT_TIMEOUT_MS); DHD_OS_WAKE_UNLOCK(dhd); return OSL_ERROR(BCME_DONGLE_DOWN); } /* currently there is only one wiphy for ifidx 0 */ err = dhd_ioctl_process(dhd, 0, ioc); if (err) goto done; /* response data is in ioc->buf so return ioc here */ reply = cfg80211_testmode_alloc_reply_skb(wiphy, sizeof(*ioc)); nla_put(reply, NL80211_ATTR_TESTDATA, sizeof(*ioc), ioc); err = cfg80211_testmode_reply(reply); done: DHD_OS_WAKE_UNLOCK(dhd); return err; } #endif /* CONFIG_NL80211_TESTMODE */ /* TODO: clean up the BT-Coex code, it still have some legacy ioctl/iovar functions */ #define COEX_DHCP #if defined(COEX_DHCP) /* use New SCO/eSCO smart YG suppression */ #define BT_DHCP_eSCO_FIX /* this flag boost wifi pkt priority to max, caution: -not fair to sco */ #define BT_DHCP_USE_FLAGS /* T1 start SCO/ESCo priority suppression */ #define BT_DHCP_OPPR_WIN_TIME 2500 /* T2 turn off SCO/SCO supperesion is (timeout) */ #define BT_DHCP_FLAG_FORCE_TIME 5500 enum wl_cfg80211_btcoex_status { BT_DHCP_IDLE, BT_DHCP_START, BT_DHCP_OPPR_WIN, BT_DHCP_FLAG_FORCE_TIMEOUT }; /* * get named driver variable to uint register value and return error indication * calling example: dev_wlc_intvar_get_reg(dev, "btc_params",66, ®_value) */ static int dev_wlc_intvar_get_reg(struct net_device *dev, char *name, uint reg, int *retval) { union { char buf[WLC_IOCTL_SMLEN]; int val; } var; int error; bcm_mkiovar(name, (char *)(®), sizeof(reg), (char *)(&var), sizeof(var.buf)); error = wldev_ioctl(dev, WLC_GET_VAR, (char *)(&var), sizeof(var.buf), false); *retval = dtoh32(var.val); return (error); } static int dev_wlc_bufvar_set(struct net_device *dev, char *name, char *buf, int len) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) char ioctlbuf_local[1024]; #else static char ioctlbuf_local[1024]; #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) */ bcm_mkiovar(name, buf, len, ioctlbuf_local, sizeof(ioctlbuf_local)); return (wldev_ioctl(dev, WLC_SET_VAR, ioctlbuf_local, sizeof(ioctlbuf_local), true)); } /* get named driver variable to uint register value and return error indication calling example: dev_wlc_intvar_set_reg(dev, "btc_params",66, value) */ static int dev_wlc_intvar_set_reg(struct net_device *dev, char *name, char *addr, char * val) { char reg_addr[8]; memset(reg_addr, 0, sizeof(reg_addr)); memcpy((char *)®_addr[0], (char *)addr, 4); memcpy((char *)®_addr[4], (char *)val, 4); return (dev_wlc_bufvar_set(dev, name, (char *)®_addr[0], sizeof(reg_addr))); } static bool btcoex_is_sco_active(struct net_device *dev) { int ioc_res = 0; bool res = FALSE; int sco_id_cnt = 0; int param27; int i; for (i = 0; i < 12; i++) { ioc_res = dev_wlc_intvar_get_reg(dev, "btc_params", 27, ¶m27); WL_TRACE(("sample[%d], btc params: 27:%x\n", i, param27)); if (ioc_res < 0) { WL_ERR(("ioc read btc params error\n")); break; } if ((param27 & 0x6) == 2) { /* count both sco & esco */ sco_id_cnt++; } if (sco_id_cnt > 2) { WL_TRACE(("sco/esco detected, pkt id_cnt:%d samples:%d\n", sco_id_cnt, i)); res = TRUE; break; } OSL_SLEEP(5); } return res; } #if defined(BT_DHCP_eSCO_FIX) /* Enhanced BT COEX settings for eSCO compatibility during DHCP window */ static int set_btc_esco_params(struct net_device *dev, bool trump_sco) { static bool saved_status = FALSE; char buf_reg50va_dhcp_on[8] = { 50, 00, 00, 00, 0x22, 0x80, 0x00, 0x00 }; char buf_reg51va_dhcp_on[8] = { 51, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; char buf_reg64va_dhcp_on[8] = { 64, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; char buf_reg65va_dhcp_on[8] = { 65, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; char buf_reg71va_dhcp_on[8] = { 71, 00, 00, 00, 0x00, 0x00, 0x00, 0x00 }; uint32 regaddr; static uint32 saved_reg50; static uint32 saved_reg51; static uint32 saved_reg64; static uint32 saved_reg65; static uint32 saved_reg71; if (trump_sco) { /* this should reduce eSCO agressive retransmit * w/o breaking it */ /* 1st save current */ WL_TRACE(("Do new SCO/eSCO coex algo {save &" "override}\n")); if ((!dev_wlc_intvar_get_reg(dev, "btc_params", 50, &saved_reg50)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 51, &saved_reg51)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 64, &saved_reg64)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 65, &saved_reg65)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 71, &saved_reg71))) { saved_status = TRUE; WL_TRACE(("saved bt_params[50,51,64,65,71]:" "0x%x 0x%x 0x%x 0x%x 0x%x\n", saved_reg50, saved_reg51, saved_reg64, saved_reg65, saved_reg71)); } else { WL_ERR((":%s: save btc_params failed\n", __FUNCTION__)); saved_status = FALSE; return -1; } WL_TRACE(("override with [50,51,64,65,71]:" "0x%x 0x%x 0x%x 0x%x 0x%x\n", *(u32 *)(buf_reg50va_dhcp_on+4), *(u32 *)(buf_reg51va_dhcp_on+4), *(u32 *)(buf_reg64va_dhcp_on+4), *(u32 *)(buf_reg65va_dhcp_on+4), *(u32 *)(buf_reg71va_dhcp_on+4))); dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg50va_dhcp_on[0], 8); dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg51va_dhcp_on[0], 8); dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg64va_dhcp_on[0], 8); dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg65va_dhcp_on[0], 8); dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg71va_dhcp_on[0], 8); saved_status = TRUE; } else if (saved_status) { /* restore previously saved bt params */ WL_TRACE(("Do new SCO/eSCO coex algo {save &" "override}\n")); regaddr = 50; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg50); regaddr = 51; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg51); regaddr = 64; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg64); regaddr = 65; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg65); regaddr = 71; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg71); WL_TRACE(("restore bt_params[50,51,64,65,71]:" "0x%x 0x%x 0x%x 0x%x 0x%x\n", saved_reg50, saved_reg51, saved_reg64, saved_reg65, saved_reg71)); saved_status = FALSE; } else { WL_ERR((":%s att to restore not saved BTCOEX params\n", __FUNCTION__)); return -1; } return 0; } #endif /* BT_DHCP_eSCO_FIX */ static void wl_cfg80211_bt_setflag(struct net_device *dev, bool set) { #if defined(BT_DHCP_USE_FLAGS) char buf_flag7_dhcp_on[8] = { 7, 00, 00, 00, 0x1, 0x0, 0x00, 0x00 }; char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00}; #endif #if defined(BT_DHCP_eSCO_FIX) /* set = 1, save & turn on 0 - off & restore prev settings */ set_btc_esco_params(dev, set); #endif #if defined(BT_DHCP_USE_FLAGS) WL_TRACE(("WI-FI priority boost via bt flags, set:%d\n", set)); if (set == TRUE) /* Forcing bt_flag7 */ dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_dhcp_on[0], sizeof(buf_flag7_dhcp_on)); else /* Restoring default bt flag7 */ dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_default[0], sizeof(buf_flag7_default)); #endif } static void wl_cfg80211_bt_timerfunc(ulong data) { struct btcoex_info *bt_local = (struct btcoex_info *)data; WL_TRACE(("Enter\n")); bt_local->timer_on = 0; schedule_work(&bt_local->work); } static void wl_cfg80211_bt_handler(struct work_struct *work) { struct btcoex_info *btcx_inf; btcx_inf = container_of(work, struct btcoex_info, work); if (btcx_inf->timer_on) { btcx_inf->timer_on = 0; del_timer_sync(&btcx_inf->timer); } switch (btcx_inf->bt_state) { case BT_DHCP_START: /* DHCP started * provide OPPORTUNITY window to get DHCP address */ WL_TRACE(("bt_dhcp stm: started \n")); btcx_inf->bt_state = BT_DHCP_OPPR_WIN; mod_timer(&btcx_inf->timer, jiffies + msecs_to_jiffies(BT_DHCP_OPPR_WIN_TIME)); btcx_inf->timer_on = 1; break; case BT_DHCP_OPPR_WIN: if (btcx_inf->dhcp_done) { WL_TRACE(("DHCP Done before T1 expiration\n")); goto btc_coex_idle; } /* DHCP is not over yet, start lowering BT priority * enforce btc_params + flags if necessary */ WL_TRACE(("DHCP T1:%d expired\n", BT_DHCP_OPPR_WIN_TIME)); if (btcx_inf->dev) wl_cfg80211_bt_setflag(btcx_inf->dev, TRUE); btcx_inf->bt_state = BT_DHCP_FLAG_FORCE_TIMEOUT; mod_timer(&btcx_inf->timer, jiffies + msecs_to_jiffies(BT_DHCP_FLAG_FORCE_TIME)); btcx_inf->timer_on = 1; break; case BT_DHCP_FLAG_FORCE_TIMEOUT: if (btcx_inf->dhcp_done) { WL_TRACE(("DHCP Done before T2 expiration\n")); } else { /* Noo dhcp during T1+T2, restore BT priority */ WL_TRACE(("DHCP wait interval T2:%d msec expired\n", BT_DHCP_FLAG_FORCE_TIME)); } /* Restoring default bt priority */ if (btcx_inf->dev) wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE); btc_coex_idle: btcx_inf->bt_state = BT_DHCP_IDLE; btcx_inf->timer_on = 0; break; default: WL_ERR(("error g_status=%d !!!\n", btcx_inf->bt_state)); if (btcx_inf->dev) wl_cfg80211_bt_setflag(btcx_inf->dev, FALSE); btcx_inf->bt_state = BT_DHCP_IDLE; btcx_inf->timer_on = 0; break; } net_os_wake_unlock(btcx_inf->dev); } int wl_cfg80211_btcoex_init(struct wl_priv *wl) { struct btcoex_info *btco_inf = NULL; btco_inf = kmalloc(sizeof(struct btcoex_info), GFP_KERNEL); if (!btco_inf) return -ENOMEM; btco_inf->bt_state = BT_DHCP_IDLE; btco_inf->ts_dhcp_start = 0; btco_inf->ts_dhcp_ok = 0; /* Set up timer for BT */ btco_inf->timer_ms = 10; init_timer(&btco_inf->timer); btco_inf->timer.data = (ulong)btco_inf; btco_inf->timer.function = wl_cfg80211_bt_timerfunc; btco_inf->dev = wl->wdev->netdev; INIT_WORK(&btco_inf->work, wl_cfg80211_bt_handler); wl->btcoex_info = btco_inf; return 0; } void wl_cfg80211_btcoex_deinit(struct wl_priv *wl) { if (!wl->btcoex_info) return; if (wl->btcoex_info->timer_on) { wl->btcoex_info->timer_on = 0; del_timer_sync(&wl->btcoex_info->timer); } cancel_work_sync(&wl->btcoex_info->work); kfree(wl->btcoex_info); wl->btcoex_info = NULL; } int wl_cfg80211_set_btcoex_dhcp(struct net_device *dev, char *command) { struct wl_priv *wl = wlcfg_drv_priv; char powermode_val = 0; char buf_reg66va_dhcp_on[8] = { 66, 00, 00, 00, 0x10, 0x27, 0x00, 0x00 }; char buf_reg41va_dhcp_on[8] = { 41, 00, 00, 00, 0x33, 0x00, 0x00, 0x00 }; char buf_reg68va_dhcp_on[8] = { 68, 00, 00, 00, 0x90, 0x01, 0x00, 0x00 }; uint32 regaddr; static uint32 saved_reg66; static uint32 saved_reg41; static uint32 saved_reg68; static bool saved_status = FALSE; #ifdef COEX_DHCP char buf_flag7_default[8] = { 7, 00, 00, 00, 0x0, 0x00, 0x00, 0x00}; struct btcoex_info *btco_inf = wl->btcoex_info; #endif /* COEX_DHCP */ #ifdef PKT_FILTER_SUPPORT dhd_pub_t *dhd = (dhd_pub_t *)(wl->pub); #endif /* Figure out powermode 1 or o command */ strncpy((char *)&powermode_val, command + strlen("BTCOEXMODE") +1, 1); if (strnicmp((char *)&powermode_val, "1", strlen("1")) == 0) { WL_TRACE_HW4(("DHCP session starts\n")); #if defined(DHCP_SCAN_SUPPRESS) /* Suppress scan during the DHCP */ wl_cfg80211_scan_suppress(dev, 1); #endif /* OEM_ANDROID */ #ifdef PKT_FILTER_SUPPORT dhd->dhcp_in_progress = 1; if (dhd->early_suspended) { WL_TRACE_HW4(("DHCP in progressing , disable packet filter!!!\n")); dhd_enable_packet_filter(0, dhd); } #endif /* Retrieve and saved orig regs value */ if ((saved_status == FALSE) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 66, &saved_reg66)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 41, &saved_reg41)) && (!dev_wlc_intvar_get_reg(dev, "btc_params", 68, &saved_reg68))) { saved_status = TRUE; WL_TRACE(("Saved 0x%x 0x%x 0x%x\n", saved_reg66, saved_reg41, saved_reg68)); /* Disable PM mode during dhpc session */ /* Disable PM mode during dhpc session */ #ifdef COEX_DHCP /* Start BT timer only for SCO connection */ if (btcoex_is_sco_active(dev)) { /* btc_params 66 */ dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg66va_dhcp_on[0], sizeof(buf_reg66va_dhcp_on)); /* btc_params 41 0x33 */ dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg41va_dhcp_on[0], sizeof(buf_reg41va_dhcp_on)); /* btc_params 68 0x190 */ dev_wlc_bufvar_set(dev, "btc_params", (char *)&buf_reg68va_dhcp_on[0], sizeof(buf_reg68va_dhcp_on)); saved_status = TRUE; btco_inf->bt_state = BT_DHCP_START; btco_inf->timer_on = 1; mod_timer(&btco_inf->timer, btco_inf->timer.expires); WL_TRACE(("enable BT DHCP Timer\n")); } #endif /* COEX_DHCP */ } else if (saved_status == TRUE) { WL_ERR(("was called w/o DHCP OFF. Continue\n")); } } else if (strnicmp((char *)&powermode_val, "2", strlen("2")) == 0) { #if defined(DHCP_SCAN_SUPPRESS) /* Since DHCP is complete, enable the scan back */ wl_cfg80211_scan_suppress(dev, 0); #endif /* OEM_ANDROID */ #ifdef PKT_FILTER_SUPPORT dhd->dhcp_in_progress = 0; WL_TRACE_HW4(("DHCP is complete \n")); /* Enable packet filtering */ if (dhd->early_suspended) { WL_TRACE_HW4(("DHCP is complete , enable packet filter!!!\n")); dhd_enable_packet_filter(1, dhd); } #endif /* PKT_FILTER_SUPPORT */ /* Restoring PM mode */ #ifdef COEX_DHCP /* Stop any bt timer because DHCP session is done */ WL_TRACE(("disable BT DHCP Timer\n")); if (btco_inf->timer_on) { btco_inf->timer_on = 0; del_timer_sync(&btco_inf->timer); if (btco_inf->bt_state != BT_DHCP_IDLE) { /* need to restore original btc flags & extra btc params */ WL_TRACE(("bt->bt_state:%d\n", btco_inf->bt_state)); /* wake up btcoex thread to restore btlags+params */ schedule_work(&btco_inf->work); } } /* Restoring btc_flag paramter anyway */ if (saved_status == TRUE) dev_wlc_bufvar_set(dev, "btc_flags", (char *)&buf_flag7_default[0], sizeof(buf_flag7_default)); #endif /* COEX_DHCP */ /* Restore original values */ if (saved_status == TRUE) { regaddr = 66; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg66); regaddr = 41; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg41); regaddr = 68; dev_wlc_intvar_set_reg(dev, "btc_params", (char *)®addr, (char *)&saved_reg68); WL_TRACE(("restore regs {66,41,68} <- 0x%x 0x%x 0x%x\n", saved_reg66, saved_reg41, saved_reg68)); } saved_status = FALSE; } else { WL_ERR(("Unkwown yet power setting, ignored\n")); } snprintf(command, 3, "OK"); return (strlen("OK")); } #endif