/* * Copyright (C) 2015 Spreadtrum Communications Inc. * * Authors : * Keguang Zhang * Jingxiang Li * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "sprdwl.h" #include "npi.h" #include "cfg80211.h" #include "cmdevt.h" #include "txrx.h" #include "msg.h" #include "intf_ops.h" #include "vendor.h" #include "work.h" #if defined(UWE5621_FTR) #include "tx_msg.h" #include "rx_msg.h" #include "wl_core.h" #endif #include "tcp_ack.h" #include "rnd_mac_addr.h" #ifdef DFS_MASTER #include "11h.h" #endif #ifdef RTT_SUPPORT #include "rtt.h" #endif /* RTT_SUPPORT */ #ifdef TCPACK_DELAY_SUPPORT #include "tcp_ack.h" #endif struct sprdwl_priv *g_sprdwl_priv; static void str2mac(const char *mac_addr, u8 *mac) { unsigned int m[ETH_ALEN]; if (sscanf(mac_addr, "%02x:%02x:%02x:%02x:%02x:%02x", &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) != ETH_ALEN) wl_err("failed to parse mac address '%s'", mac_addr); mac[0] = m[0]; mac[1] = m[1]; mac[2] = m[2]; mac[3] = m[3]; mac[4] = m[4]; mac[5] = m[5]; } void sprdwl_netif_rx(struct sk_buff *skb, struct net_device *ndev) { struct sprdwl_vif *vif; struct sprdwl_intf *intf; struct sprdwl_rx_if *rx_if = NULL; ktime_t kt; u32 sec; vif = netdev_priv(ndev); intf = (struct sprdwl_intf *)(vif->priv->hw_priv); rx_if = (struct sprdwl_rx_if *)intf->sprdwl_rx; kt = ktime_get(); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) sec = (u32)(div_u64(kt, NSEC_PER_SEC)); #else/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)*/ sec = (u32)(div_u64(kt.tv64, NSEC_PER_SEC)); #endif/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)*/ vif->throughtput_rx.bytes += skb->len; if (vif->throughtput_rx.sec != sec) { vif->throughtput_rx.throughtput = (vif->throughtput_rx.bytes * 8) >> 10; vif->throughtput_rx.sec = sec; vif->throughtput_rx.bytes = 0; wl_trace("mode: %d, tp_rx: %d Kbps\n", vif->mode, vif->throughtput_rx.throughtput); } wl_hex_dump(L_DBG, "RX packet: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, skb->len, 0); skb->dev = ndev; skb->protocol = eth_type_trans(skb, ndev); /* CHECKSUM_UNNECESSARY not supported by our hardware */ /* skb->ip_summed = CHECKSUM_UNNECESSARY; */ ndev->stats.rx_packets++; ndev->stats.rx_bytes += skb->len; #if defined(MORE_DEBUG) intf->stats.rx_packets++; intf->stats.rx_bytes += skb->len; if (skb->pkt_type == PACKET_MULTICAST) intf->stats.rx_multicast++; #endif #ifndef RX_NAPI /*to ensure data handled in netif in order*/ local_bh_disable(); netif_receive_skb(skb); local_bh_enable(); #else/*RX_NAPI*/ napi_gro_receive(&rx_if->napi_rx, skb); #endif/*RX_NAPI*/ } void sprdwl_stop_net(struct sprdwl_vif *vif) { struct sprdwl_vif *real_vif, *tmp_vif; struct sprdwl_priv *priv = vif->priv; spin_lock_bh(&priv->list_lock); list_for_each_entry_safe(real_vif, tmp_vif, &priv->vif_list, vif_node) if (real_vif->ndev) netif_stop_queue(real_vif->ndev); spin_unlock_bh(&priv->list_lock); } static void sprdwl_netflowcontrl_mode(struct sprdwl_priv *priv, enum sprdwl_mode mode, bool state) { struct sprdwl_vif *vif; vif = mode_to_vif(priv, mode); if (vif) { if (state) netif_wake_queue(vif->ndev); else netif_stop_queue(vif->ndev); sprdwl_put_vif(vif); } } static void sprdwl_netflowcontrl_all(struct sprdwl_priv *priv, bool state) { struct sprdwl_vif *real_vif, *tmp_vif; spin_lock_bh(&priv->list_lock); list_for_each_entry_safe(real_vif, tmp_vif, &priv->vif_list, vif_node) if (real_vif->ndev) { if (state) netif_wake_queue(real_vif->ndev); else netif_stop_queue(real_vif->ndev); } spin_unlock_bh(&priv->list_lock); } /* @state: true for netif_start_queue * false for netif_stop_queue */ void sprdwl_net_flowcontrl(struct sprdwl_priv *priv, enum sprdwl_mode mode, bool state) { wl_trace("mode: %d, tp_flowcontrl: %d\n", mode, state); if (mode != SPRDWL_MODE_NONE) sprdwl_netflowcontrl_mode(priv, mode, state); else sprdwl_netflowcontrl_all(priv, state); } #ifdef PPPOE_LLC_SUPPORT static struct sk_buff *sprdwl_fill_pppoe_llc_header(struct sk_buff *skb) { #define LLC_HEADER_LEN 8 struct ethhdr *ethhdr = NULL; char *llc = NULL; char llc_header_disc[] = {0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00, 0x88, 0x63}; char llc_header_sec[] = {0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00, 0x88, 0x64}; ethhdr = (struct ethhdr *)skb->data; switch (htons(ethhdr->h_proto)) { case ETH_P_PPP_DISC: llc = llc_header_disc; break; case ETH_P_PPP_SES: llc = llc_header_sec; break; default: return skb; } if (unlikely(skb_headroom(skb) < LLC_HEADER_LEN)) { struct sk_buff *skb2 = NULL; skb2 = skb_realloc_headroom(skb, LLC_HEADER_LEN); if (!skb2) { kfree_skb(skb); pr_err("realloc skb headroom for llc failed\n"); return NULL; } if (skb->sk) skb_set_owner_w(skb2, skb->sk); consume_skb(skb); skb = skb2; } skb_push(skb, LLC_HEADER_LEN); memmove(skb->data, skb->data + LLC_HEADER_LEN, 14); memcpy(skb->data + 14, llc, LLC_HEADER_LEN); return skb; } #endif static netdev_tx_t sprdwl_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); int ret = 0; struct sprdwl_msg_buf *msg = NULL; u8 *data_temp; struct sprdwl_eap_hdr *eap_temp; struct sprdwl_intf *intf; ktime_t kt; u32 sec; intf = (struct sprdwl_intf *)vif->priv->hw_priv; if (intf->cp_asserted == 1 || intf->suspend_mode != SPRDWL_PS_RESUMED) { dev_kfree_skb(skb); return NETDEV_TX_OK; } if (vif->mode == SPRDWL_MODE_STATION && vif->sm_state != SPRDWL_CONNECTED) { wl_info("%s, %d, sta is not connect, should not send this data\n", __func__, __LINE__); wl_hex_dump(L_INFO, "TX packet: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, skb->len, 0); dev_kfree_skb(skb); return NETDEV_TX_OK; } /* drop nonlinearize skb */ if (skb_linearize(skb)) { wl_err("nonlinearize skb\n"); dev_kfree_skb(skb); ndev->stats.tx_dropped++; goto out; } data_temp = (u8 *)(skb->data) + sizeof(struct ethhdr); eap_temp = (struct sprdwl_eap_hdr *)data_temp; if (vif->mode == SPRDWL_MODE_P2P_GO && skb->protocol == cpu_to_be16(ETH_P_PAE) && eap_temp->type == EAP_PACKET_TYPE && eap_temp->code == EAP_FAILURE_CODE) { sprdwl_xmit_data2cmd(skb, ndev); return NETDEV_TX_OK; } /* FIXME vif connect state, need fix cfg80211_connect_result when MCC */ /*if (vif->connect_status != SPRDWL_CONNECTED) */ /* Hardware tx data queue prority is lower than management queue * management frame will be send out early even that get into queue * after data frame. * Workaround way: Put eap failure frame to high queue * by use tx mgmt cmd */ /* send 802.1x or WAPI frame from cmd channel */ if (skb->protocol == cpu_to_be16(ETH_P_PAE) || skb->protocol == cpu_to_be16(WAPI_TYPE)) { wl_info("send %s frame by WIFI_CMD_TX_DATA\n", skb->protocol == cpu_to_be16(ETH_P_PAE) ? "802.1X" : "WAI"); if (sprdwl_xmit_data2cmd_wq(skb, ndev) == -EAGAIN) return NETDEV_TX_BUSY; return NETDEV_TX_OK; } else { ret = sprdwl_tx_filter_packet(skb, ndev); if (!ret) return NETDEV_TX_OK; } /*mode not open, so we will not send data*/ if (vif->priv->fw_stat[vif->mode] != SPRDWL_INTF_OPEN) { wl_err_ratelimited("%s, %d, should not send this data\n", __func__, __LINE__); dev_kfree_skb(skb); return NETDEV_TX_OK; } kt = ktime_get(); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) sec = (u32)(div_u64(kt, NSEC_PER_SEC)); #else/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)*/ sec = (u32)(div_u64(kt.tv64, NSEC_PER_SEC)); #endif/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)*/ vif->throughtput_tx.bytes += skb->len; if (vif->throughtput_tx.sec != sec) { vif->throughtput_tx.throughtput = (vif->throughtput_tx.bytes * 8) >> 10; vif->throughtput_tx.sec = sec; vif->throughtput_tx.bytes = 0; wl_trace("mode: %d, tp_tx: %d Kbps\n", vif->mode, vif->throughtput_tx.throughtput); } msg = sprdwl_intf_get_msg_buf(vif->priv, SPRDWL_TYPE_DATA, vif->mode, vif->ctx_id); if (!msg) { wl_err("%s, %d, get msg bug failed\n", __func__, __LINE__); ndev->stats.tx_fifo_errors++; dev_kfree_skb(skb); return NETDEV_TX_BUSY; } if (skb_headroom(skb) < ndev->needed_headroom) { struct sk_buff *tmp_skb = skb; skb = skb_realloc_headroom(skb, ndev->needed_headroom); dev_kfree_skb(tmp_skb); if (!skb) { wl_ndev_log(L_ERR, ndev, "%s skb_realloc_headroom failed\n", __func__); sprdwl_intf_free_msg_buf(vif->priv, msg); goto out; } } #ifdef PPPOE_LLC_SUPPORT skb = sprdwl_fill_pppoe_llc_header(skb); if (!skb) { sprdwl_intf_free_msg_buf(vif->priv, msg); return NETDEV_TX_OK; } #endif #if !defined(UWE5621_FTR) /* sprdwl_send_data: offset use 2 for cp bytes align */ ret = sprdwl_send_data(vif, msg, skb, 2); #else ret = sprdwl_send_data(vif, msg, skb, 0); #endif /* UWE5621_FTR */ if (ret) { wl_ndev_log(L_ERR, ndev, "%s drop msg due to TX Err\n", __func__); /* FIXME as debug sdiom later, here just drop the msg * wapi temp drop */ dev_kfree_skb(skb); sprdwl_intf_free_msg_buf(vif->priv, msg); return NETDEV_TX_OK; } vif->ndev->stats.tx_bytes += skb->len; vif->ndev->stats.tx_packets++; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) ndev->trans_start = jiffies; #else netif_trans_update(vif->ndev); #endif wl_hex_dump(L_DBG, "TX packet: ", DUMP_PREFIX_OFFSET, 16, 1, skb->data, skb->len, 0); out: return NETDEV_TX_OK; } static int sprdwl_init(struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); #ifdef STA_SOFTAP_SCC_MODE enum nl80211_iftype type = vif->wdev.iftype; if (type == NL80211_IFTYPE_AP) return 0; #endif /* initialize firmware */ return sprdwl_init_fw(vif); } static void sprdwl_uninit(struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); #ifdef STA_SOFTAP_SCC_MODE enum nl80211_iftype type = vif->wdev.iftype; if (type == NL80211_IFTYPE_AP) return; #endif sprdwl_uninit_fw(vif); } static int sprdwl_open(struct net_device *ndev) { wl_ndev_log(L_DBG, ndev, "%s\n", __func__); #ifdef DFS_MASTER netif_carrier_off(ndev); #endif netif_start_queue(ndev); return 0; } static int sprdwl_close(struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); wl_ndev_log(L_DBG, ndev, "%s\n", __func__); sprdwl_scan_done(vif, true); sprdwl_sched_scan_done(vif, true); netif_stop_queue(ndev); if (netif_carrier_ok(ndev)) netif_carrier_off(ndev); return 0; } static struct net_device_stats *sprdwl_get_stats(struct net_device *ndev) { return &ndev->stats; } #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE static void sprdwl_tx_timeout(struct net_device *ndev, unsigned int txqueue) #else static void sprdwl_tx_timeout(struct net_device *ndev) #endif { wl_ndev_log(L_DBG, ndev, "%s\n", __func__); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) ndev->trans_start = jiffies; #else netif_trans_update(ndev); #endif netif_wake_queue(ndev); } #define MAX_PRIV_CMD_LEN SPRDWL_MAX_CMD_TXLEN #define CMD_BLACKLIST_ENABLE "BLOCK" #define CMD_BLACKLIST_DISABLE "UNBLOCK" #define CMD_ADD_WHITELIST "WHITE_ADD" #define CMD_DEL_WHITELIST "WHITE_DEL" #define CMD_ENABLE_WHITELIST "WHITE_EN" #define CMD_DISABLE_WHITELIST "WHITE_DIS" #define CMD_SETSUSPENDMODE "SETSUSPENDMODE" #define CMD_SET_FCC_CHANNEL "SET_FCC_CHANNEL" #define CMD_SET_COUNTRY "COUNTRY" #define CMD_11V_GET_CFG "11VCFG_GET" #define CMD_11V_SET_CFG "11VCFG_SET" #define CMD_11V_WNM_SLEEP "WNM_SLEEP" #define CMD_SET_MAX_CLIENTS "MAX_STA" #define CMD_BTCOEXSCAN_START "BTCOEXSCAN-START" #define CMD_BTCOEXSCAN_STOP "BTCOEXSCAN-STOP" #define CMD_RXFILTER_STOP "RXFILTER-STOP" #define CMD_RXFILTER_ADD "RXFILTER-ADD" #define CMD_RXFILTER_START "RXFILTER-START" #define CMD_RXFILTER_REMOVE "RXFILTER-REMOVE" #define CMD_SETBAND "SETBAND" #define CMD_BTCOEXMODE "BTCOEXMODE" #define CMD_WLS_BATCHING "WLS_BATCHING" #define CMD_SET_AP_WPS_P2P_IE "SET_AP_WPS_P2P_IE" #define CMD_GTK_REKEY_OFFLOAD "GTK_REKEY_OFFLOAD" static int sprdwl_priv_cmd(struct net_device *ndev, struct ifreq *ifr) { #ifdef CONFIG_COMPAT struct compat_android_wifi_priv_cmd { compat_caddr_t buf; int used_len; int total_len; }; #endif /* CONFIG_COMPAT */ int n_clients; struct sprdwl_vif *vif = netdev_priv(ndev); struct sprdwl_priv *priv = vif->priv; struct android_wifi_priv_cmd priv_cmd; char *command = NULL, *country = NULL; u16 interval = 0; int max_len; u8 feat = 0, status = 0; u8 addr[ETH_ALEN] = {0}, *mac_addr = NULL, *tmp, *mac_list; int ret = 0, skip, counter, index, value; if (!ifr->ifr_data) return -EINVAL; #ifdef CONFIG_COMPAT #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)) if (in_compat_syscall()) #else if (is_compat_task()) #endif { struct compat_android_wifi_priv_cmd compat_priv_cmd; if (copy_from_user(&compat_priv_cmd, ifr->ifr_data, sizeof(struct compat_android_wifi_priv_cmd))) { return -EFAULT; } priv_cmd.buf = compat_ptr(compat_priv_cmd.buf); priv_cmd.used_len = compat_priv_cmd.used_len; priv_cmd.total_len = compat_priv_cmd.total_len; } else #endif /* CONFIG_COMPAT */ { if (copy_from_user(&priv_cmd, ifr->ifr_data, sizeof(priv_cmd))) return -EFAULT; } /*add length check to avoid invalid NULL ptr*/ if (!priv_cmd.total_len) { wl_ndev_log(L_INFO, ndev, "%s: priv cmd total len is invalid\n", __func__); return -EINVAL; } /* fix max_len for mv300(total_len = 4096) */ max_len = priv_cmd.total_len > MAX_PRIV_CMD_LEN ? MAX_PRIV_CMD_LEN : priv_cmd.total_len; command = kmalloc(max_len, GFP_KERNEL); if (!command) return -ENOMEM; if (copy_from_user(command, priv_cmd.buf, max_len)) { ret = -EFAULT; goto out; } if (!strncasecmp(command, CMD_BLACKLIST_ENABLE, strlen(CMD_BLACKLIST_ENABLE))) { skip = strlen(CMD_BLACKLIST_ENABLE) + 1; str2mac(command + skip, addr); if (!is_valid_ether_addr(addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: block %pM\n", __func__, addr); ret = sprdwl_set_blacklist(priv, vif->ctx_id, SPRDWL_SUBCMD_ADD, 1, addr); } else if (!strncasecmp(command, CMD_BLACKLIST_DISABLE, strlen(CMD_BLACKLIST_DISABLE))) { skip = strlen(CMD_BLACKLIST_DISABLE) + 1; str2mac(command + skip, addr); if (!is_valid_ether_addr(addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: unblock %pM\n", __func__, addr); ret = sprdwl_set_blacklist(priv, vif->ctx_id, SPRDWL_SUBCMD_DEL, 1, addr); } else if (!strncasecmp(command, CMD_ADD_WHITELIST, strlen(CMD_ADD_WHITELIST))) { skip = strlen(CMD_ADD_WHITELIST) + 1; str2mac(command + skip, addr); if (!is_valid_ether_addr(addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: add whitelist %pM\n", __func__, addr); ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_ADD, 1, addr); } else if (!strncasecmp(command, CMD_DEL_WHITELIST, strlen(CMD_DEL_WHITELIST))) { skip = strlen(CMD_DEL_WHITELIST) + 1; str2mac(command + skip, addr); if (!is_valid_ether_addr(addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: delete whitelist %pM\n", __func__, addr); ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_DEL, 1, addr); } else if (!strncasecmp(command, CMD_ENABLE_WHITELIST, strlen(CMD_ENABLE_WHITELIST))) { skip = strlen(CMD_ENABLE_WHITELIST) + 1; counter = command[skip]; wl_ndev_log(L_INFO, ndev, "%s: enable whitelist counter : %d\n", __func__, counter); if (!counter) { ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_ENABLE, 0, NULL); goto out; } mac_addr = kmalloc(ETH_ALEN * counter, GFP_KERNEL); mac_list = mac_addr; if (IS_ERR(mac_addr)) { ret = -ENOMEM; goto out; } tmp = command + skip + 1; for (index = 0; index < counter; index++) { str2mac(tmp, mac_addr); if (!is_valid_ether_addr(mac_addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: enable whitelist %pM\n", __func__, mac_addr); mac_addr += ETH_ALEN; tmp += 18; } ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_ENABLE, counter, mac_list); kfree(mac_list); } else if (!strncasecmp(command, CMD_DISABLE_WHITELIST, strlen(CMD_DISABLE_WHITELIST))) { skip = strlen(CMD_DISABLE_WHITELIST) + 1; counter = command[skip]; wl_ndev_log(L_INFO, ndev, "%s: disable whitelist counter : %d\n", __func__, counter); if (!counter) { ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_DISABLE, 0, NULL); goto out; } mac_addr = kmalloc(ETH_ALEN * counter, GFP_KERNEL); mac_list = mac_addr; if (IS_ERR(mac_addr)) { ret = -ENOMEM; goto out; } tmp = command + skip + 1; for (index = 0; index < counter; index++) { str2mac(tmp, mac_addr); if (!is_valid_ether_addr(mac_addr)) goto out; wl_ndev_log(L_INFO, ndev, "%s: disable whitelist %pM\n", __func__, mac_addr); mac_addr += ETH_ALEN; tmp += 18; } ret = sprdwl_set_whitelist(priv, vif->ctx_id, SPRDWL_SUBCMD_DISABLE, counter, mac_list); kfree(mac_list); } else if (!strncasecmp(command, CMD_11V_GET_CFG, strlen(CMD_11V_GET_CFG))) { /* deflaut CP support all featrue */ if (max_len < (strlen(CMD_11V_GET_CFG) + 4)) { ret = -ENOMEM; goto out; } memset(command, 0, max_len); if (priv->fw_std & SPRDWL_STD_11V) feat = priv->wnm_ft_support; sprintf(command, "%s %d", CMD_11V_GET_CFG, feat); wl_ndev_log(L_INFO, ndev, "%s: get 11v feat\n", __func__); if (copy_to_user(priv_cmd.buf, command, max_len)) { wl_ndev_log(L_ERR, ndev, "%s: get 11v copy failed\n", __func__); ret = -EFAULT; goto out; } } else if (!strncasecmp(command, CMD_11V_SET_CFG, strlen(CMD_11V_SET_CFG))) { int skip = strlen(CMD_11V_SET_CFG) + 1; int cfg = command[skip]; wl_ndev_log(L_INFO, ndev, "%s: 11v cfg %d\n", __func__, cfg); sprdwl_set_11v_feature_support(priv, vif->ctx_id, cfg); } else if (!strncasecmp(command, CMD_11V_WNM_SLEEP, strlen(CMD_11V_WNM_SLEEP))) { int skip = strlen(CMD_11V_WNM_SLEEP) + 1; status = command[skip]; if (status) interval = command[skip + 1]; wl_ndev_log(L_INFO, ndev, "%s: 11v sleep, status %d, interval %d\n", __func__, status, interval); sprdwl_set_11v_sleep_mode(priv, vif->ctx_id, status, interval); } else if (!strncasecmp(command, CMD_SET_COUNTRY, strlen(CMD_SET_COUNTRY))) { skip = strlen(CMD_SET_COUNTRY) + 1; country = command + skip; if (!country || strlen(country) != SPRDWL_COUNTRY_CODE_LEN) { wl_ndev_log(L_ERR, ndev, "%s: invalid country code\n", __func__); ret = -EINVAL; goto out; } wl_ndev_log(L_INFO, ndev, "%s country code:%c%c\n", __func__, toupper(country[0]), toupper(country[1])); ret = regulatory_hint(priv->wiphy, country); } else if (!strncasecmp(command, CMD_SET_MAX_CLIENTS, strlen(CMD_SET_MAX_CLIENTS))) { skip = strlen(CMD_SET_MAX_CLIENTS) + 1; ret = kstrtou32(command+skip, 10, &n_clients); if (ret < 0) { ret = -EINVAL; goto out; } ret = sprdwl_set_max_clients_allowed(priv, vif->ctx_id, n_clients); } else if (!strncasecmp(command, CMD_SETSUSPENDMODE, strlen(CMD_SETSUSPENDMODE))) { skip = strlen(CMD_SETSUSPENDMODE) + 1; ret = kstrtoint(command + skip, 0, &value); if (ret) goto out; wl_ndev_log(L_INFO, ndev, "%s: set suspend mode,value : %d\n", __func__, value); ret = sprdwl_power_save(priv, vif->ctx_id, SPRDWL_SCREEN_ON_OFF, value); } else { #ifdef OTT_UWE if (!strncasecmp(command, CMD_BTCOEXSCAN_STOP, strlen(CMD_BTCOEXSCAN_STOP))) ret = 0; else if (!strncasecmp(command, CMD_BTCOEXSCAN_START, strlen(CMD_BTCOEXSCAN_START))) ret = 0; else if (!strncasecmp(command, CMD_RXFILTER_STOP, strlen(CMD_RXFILTER_STOP))) ret = 0; else if (!strncasecmp(command, CMD_RXFILTER_ADD, strlen(CMD_RXFILTER_ADD))) ret = 0; else if (!strncasecmp(command, CMD_RXFILTER_START, strlen(CMD_RXFILTER_START))) ret = 0; else if (!strncasecmp(command, CMD_RXFILTER_REMOVE, strlen(CMD_RXFILTER_REMOVE))) ret = 0; else if (!strncasecmp(command, CMD_BTCOEXMODE, strlen(CMD_BTCOEXMODE))) ret = 0; else if (!strncasecmp(command, CMD_WLS_BATCHING, strlen(CMD_WLS_BATCHING))) ret = 0; else if (!strncasecmp(command, CMD_SETBAND, strlen(CMD_SETBAND))) ret = 0; else if (!strncasecmp(command, CMD_SET_AP_WPS_P2P_IE, strlen(CMD_SET_AP_WPS_P2P_IE))) ret = 0; else if (!strncasecmp(command, CMD_GTK_REKEY_OFFLOAD, strlen(CMD_GTK_REKEY_OFFLOAD))) ret = 0; else { wl_ndev_log(L_ERR, ndev, "sprdbg: %s command(%s) not support\n", __func__, command); ret = -ENOTSUPP; } #else wl_ndev_log(L_ERR, ndev, "sprdbg: %s command(%s) not support\n", __func__, command); ret = -ENOTSUPP; #endif } out: kfree(command); return ret; } static int sprdwl_set_power_save(struct net_device *ndev, struct ifreq *ifr) { struct sprdwl_vif *vif = netdev_priv(ndev); struct sprdwl_priv *priv = vif->priv; struct android_wifi_priv_cmd priv_cmd; char *command = NULL; int ret = 0, skip, value; int max_len = 0; if (!ifr->ifr_data) return -EINVAL; if (copy_from_user(&priv_cmd, ifr->ifr_data, sizeof(priv_cmd))) return -EFAULT; /*add length check to avoid invalid NULL ptr*/ if (!priv_cmd.total_len) { wl_ndev_log(L_INFO, ndev, "%s: priv cmd total len is invalid\n", __func__); return -EINVAL; } /* fix max_len for mv300(total_len = 4096) */ max_len = priv_cmd.total_len > MAX_PRIV_CMD_LEN ? MAX_PRIV_CMD_LEN : priv_cmd.total_len; command = kmalloc(max_len, GFP_KERNEL); if (!command) return -ENOMEM; if (copy_from_user(command, priv_cmd.buf, max_len)) { ret = -EFAULT; goto out; } if (!strncasecmp(command, CMD_SETSUSPENDMODE, strlen(CMD_SETSUSPENDMODE))) { skip = strlen(CMD_SETSUSPENDMODE) + 1; ret = kstrtoint(command + skip, 0, &value); if (ret) goto out; wl_ndev_log(L_INFO, ndev, "%s: set suspend mode,value : %d\n", __func__, value); ret = sprdwl_power_save(priv, vif->ctx_id, SPRDWL_SCREEN_ON_OFF, value); } else if (!strncasecmp(command, CMD_SET_FCC_CHANNEL, strlen(CMD_SET_FCC_CHANNEL))) { skip = strlen(CMD_SET_FCC_CHANNEL) + 1; ret = kstrtoint(command + skip, 0, &value); if (ret) goto out; wl_ndev_log(L_INFO, ndev, "%s: set fcc channel,value : %d\n", __func__, value); ret = sprdwl_power_save(priv, vif->ctx_id, SPRDWL_SET_FCC_CHANNEL, value); } else { wl_ndev_log(L_ERR, ndev, "%s command not support\n", __func__); ret = -ENOTSUPP; } out: kfree(command); return ret; } static int sprdwl_set_tlv(struct net_device *ndev, struct ifreq *ifr) { struct sprdwl_vif *vif = netdev_priv(ndev); struct sprdwl_priv *priv = vif->priv; struct android_wifi_priv_cmd priv_cmd; struct sprdwl_tlv_data *tlv; int ret; int max_len = 0; if (!ifr->ifr_data) return -EINVAL; if (copy_from_user(&priv_cmd, ifr->ifr_data, sizeof(priv_cmd))) return -EFAULT; if (priv_cmd.total_len < sizeof(*tlv)) return -EINVAL; max_len = priv_cmd.total_len > MAX_PRIV_CMD_LEN ? MAX_PRIV_CMD_LEN : priv_cmd.total_len; tlv = kmalloc(max_len, GFP_KERNEL); if (!tlv) return -ENOMEM; if (copy_from_user(tlv, priv_cmd.buf, max_len)) { ret = -EFAULT; goto out; } /*vowifi case, should send delba*/ if (tlv->type == IOCTL_TLV_TP_VOWIFI_INFO && vif->sm_state == SPRDWL_CONNECTED && (is_valid_ether_addr(vif->bssid))) { struct sprdwl_intf *intf = NULL; struct sprdwl_peer_entry *peer_entry = NULL; struct vowifi_info *info = (struct vowifi_info *)(tlv->data); peer_entry = sprdwl_find_peer_entry_using_addr(vif, vif->bssid); intf = (struct sprdwl_intf *)vif->priv->hw_priv; if (intf && peer_entry) { wl_info("lut:%d, vowifi_enabled, txba_map:%lu\n", peer_entry->lut_index, peer_entry->ba_tx_done_map); if ((tlv->len != 0) && (info->data == 0)) { peer_entry->vowifi_enabled = 0; } else { u16 tid = qos_index_2_tid(SPRDWL_AC_VO); peer_entry->vowifi_enabled = 1; peer_entry->vowifi_pkt_cnt = 0; if (test_bit(tid, &peer_entry->ba_tx_done_map)) sprdwl_tx_delba(intf, peer_entry, SPRDWL_AC_VO); } } } ret = sprdwl_set_tlv_data(priv, vif->ctx_id, tlv, max_len); if (ret) wl_ndev_log(L_ERR, ndev, "%s set tlv(type=%#x) error\n", __func__, tlv->type); out: kfree(tlv); return ret; } #define SPRDWLIOCTL (SIOCDEVPRIVATE + 1) #define SPRDWLGETSSID (SIOCDEVPRIVATE + 2) #define SPRDWLSETFCC (SIOCDEVPRIVATE + 3) #define SPRDWLSETSUSPEND (SIOCDEVPRIVATE + 4) #define SPRDWLSETCOUNTRY (SIOCDEVPRIVATE + 5) #define SPRDWLSETTLV (SIOCDEVPRIVATE + 7) static int sprdwl_ioctl(struct net_device *ndev, struct ifreq *req, int cmd) { struct sprdwl_vif *vif = netdev_priv(ndev); struct iwreq *wrq = (struct iwreq *)req; switch (cmd) { case SPRDWLIOCTL: case SPRDWLSETCOUNTRY: return sprdwl_priv_cmd(ndev, req); case SPRDWLGETSSID: if (vif->ssid_len > 0) { if (copy_to_user(wrq->u.essid.pointer, vif->ssid, vif->ssid_len)) return -EFAULT; wrq->u.essid.length = vif->ssid_len; } else { wl_ndev_log(L_ERR, ndev, "SSID len is zero\n"); return -EFAULT; } break; case SPRDWLSETFCC: case SPRDWLSETSUSPEND: return sprdwl_set_power_save(ndev, req); case SPRDWLSETTLV: return sprdwl_set_tlv(ndev, req); default: wl_ndev_log(L_ERR, ndev, "Unsupported IOCTL %d\n", cmd); return -ENOTSUPP; } return 0; } static bool mc_address_changed(struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); struct netdev_hw_addr *ha; u8 mc_count, index; u8 *mac_addr; bool found; mc_count = netdev_mc_count(ndev); if (mc_count != vif->mc_filter->mac_num) return true; mac_addr = vif->mc_filter->mac_addr; netdev_for_each_mc_addr(ha, ndev) { found = false; for (index = 0; index < vif->mc_filter->mac_num; index++) { if (!memcmp(ha->addr, mac_addr, ETH_ALEN)) { found = true; break; } mac_addr += ETH_ALEN; } if (!found) return true; } return false; } #define SPRDWL_RX_MODE_MULTICAST 1 static void sprdwl_set_multicast(struct net_device *ndev) { struct sprdwl_vif *vif = netdev_priv(ndev); struct sprdwl_priv *priv = vif->priv; struct sprdwl_work *work; struct netdev_hw_addr *ha; u8 mc_count; u8 *mac_addr; mc_count = netdev_mc_count(ndev); wl_ndev_log(L_DBG, ndev, "%s multicast address num: %d\n", __func__, mc_count); if (mc_count > priv->max_mc_mac_addrs) return; vif->mc_filter->mc_change = false; if ((ndev->flags & IFF_MULTICAST) && (mc_address_changed(ndev))) { mac_addr = vif->mc_filter->mac_addr; netdev_for_each_mc_addr(ha, ndev) { wl_ndev_log(L_DBG, ndev, "%s set mac: %pM\n", __func__, ha->addr); if ((ha->addr[0] != 0x33 || ha->addr[1] != 0x33) && (ha->addr[0] != 0x01 || ha->addr[1] != 0x00 || ha->addr[2] != 0x5e || ha->addr[3] > 0x7f)) { wl_ndev_log(L_INFO, ndev, "%s invalid addr\n", __func__); return; } ether_addr_copy(mac_addr, ha->addr); mac_addr += ETH_ALEN; } vif->mc_filter->mac_num = mc_count; vif->mc_filter->mc_change = true; } else if (!(ndev->flags & IFF_MULTICAST) && vif->mc_filter->mac_num) { vif->mc_filter->mac_num = 0; vif->mc_filter->mc_change = true; } work = sprdwl_alloc_work(0); if (!work) { wl_ndev_log(L_ERR, ndev, "%s out of memory\n", __func__); return; } work->vif = vif; work->id = SPRDWL_WORK_MC_FILTER; vif->mc_filter->subtype = SPRDWL_RX_MODE_MULTICAST; sprdwl_queue_work(vif->priv, work); } static int sprdwl_set_mac(struct net_device *dev, void *addr) { struct sprdwl_vif *vif = netdev_priv(dev); struct sockaddr *sa = (struct sockaddr *)addr; if (!dev) { netdev_err(dev, "Invalid net device\n"); } netdev_info(dev, "start set random mac: %pM\n", sa->sa_data); if (is_multicast_ether_addr(sa->sa_data)) { netdev_err(dev, "invalid, it is multicast addr: %pM\n", sa->sa_data); return -EINVAL; } if (vif->mode == SPRDWL_MODE_STATION) { if (!is_zero_ether_addr(sa->sa_data)) { vif->has_rand_mac = true; memcpy(vif->random_mac, sa->sa_data, ETH_ALEN); memcpy(dev->dev_addr, sa->sa_data, ETH_ALEN); } else { vif->has_rand_mac = false; netdev_info(dev, "need clear random mac for sta/softap mode\n"); memset(vif->random_mac, 0, ETH_ALEN); memcpy(dev->dev_addr, vif->mac, ETH_ALEN); } } /*return success to pass vts test*/ return 0; } static struct net_device_ops sprdwl_netdev_ops = { .ndo_init = sprdwl_init, .ndo_uninit = sprdwl_uninit, .ndo_open = sprdwl_open, .ndo_stop = sprdwl_close, .ndo_start_xmit = sprdwl_start_xmit, .ndo_get_stats = sprdwl_get_stats, .ndo_tx_timeout = sprdwl_tx_timeout, .ndo_do_ioctl = sprdwl_ioctl, .ndo_set_mac_address = sprdwl_set_mac, }; static int sprdwl_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *ndev; struct sprdwl_vif *vif; struct sprdwl_peer_entry *entry; struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; if (!ifa || !(ifa->ifa_dev->dev)) return NOTIFY_DONE; if (ifa->ifa_dev->dev->netdev_ops != &sprdwl_netdev_ops) return NOTIFY_DONE; ndev = ifa->ifa_dev->dev; vif = netdev_priv(ndev); switch (vif->wdev.iftype) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: if (event == NETDEV_UP) { entry = sprdwl_find_peer_entry_using_addr(vif, vif->bssid); if (entry != NULL) { if (entry->ctx_id == vif->ctx_id) entry->ip_acquired = 1; else wl_err("ctx_id(%d) mismatch\n", entry->ctx_id); } else { wl_err("failed to find entry\n"); } sprdwl_notify_ip(vif->priv, vif->ctx_id, SPRDWL_IPV4, (u8 *)&ifa->ifa_address); } break; default: break; } return NOTIFY_DONE; } static struct notifier_block sprdwl_inetaddr_cb = { .notifier_call = sprdwl_inetaddr_event }; static int sprdwl_inetaddr6_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *ndev; struct sprdwl_vif *vif; struct inet6_ifaddr *inet6_ifa = (struct inet6_ifaddr *)ptr; struct sprdwl_work *work; u8 *ipv6_addr; if (!inet6_ifa || !(inet6_ifa->idev->dev)) return NOTIFY_DONE; if (inet6_ifa->idev->dev->netdev_ops != &sprdwl_netdev_ops) return NOTIFY_DONE; ndev = inet6_ifa->idev->dev; vif = netdev_priv(ndev); switch (vif->wdev.iftype) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: if (event == NETDEV_UP) { work = sprdwl_alloc_work(SPRDWL_IPV6_ADDR_LEN); if (!work) { wl_ndev_log(L_ERR, ndev, "%s out of memory\n", __func__); return NOTIFY_DONE; } work->vif = vif; work->id = SPRDWL_WORK_NOTIFY_IP; ipv6_addr = (u8 *)work->data; memcpy(ipv6_addr, (u8 *)&inet6_ifa->addr, SPRDWL_IPV6_ADDR_LEN); sprdwl_queue_work(vif->priv, work); } break; default: break; } return NOTIFY_DONE; } static struct notifier_block sprdwl_inet6addr_cb = { .notifier_call = sprdwl_inetaddr6_event }; static int write_mac_addr(char *mac_file, u8 *addr) { struct file *fp = 0; mm_segment_t old_fs; char buf[18]; loff_t pos = 0; /*open file*/ fp = filp_open(mac_file, O_CREAT|O_RDWR, 777); if (IS_ERR(fp)) { wl_err("can't create WIFI MAC file!\n"); return -ENOENT; } /*format MAC address*/ sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); /*save old fs: should be USER_DS*/ old_fs = get_fs(); /*change it to KERNEL_DS*/ set_fs(KERNEL_DS); /*write file*/ vfs_write(fp, buf, sizeof(buf), &pos); /*close file*/ filp_close(fp, NULL); /*restore to old fs*/ set_fs(old_fs); return 0; } #ifdef CUSTOMIZE_WIFI_MAC_FILE #define WIFI_MAC_ADDR_PATH CUSTOMIZE_WIFI_MAC_FILE #else #define WIFI_MAC_ADDR_PATH "/data/misc/wifi/wifimac.txt" #endif static int sprdwl_get_mac_from_file(struct sprdwl_vif *vif, u8 *addr) { struct file *fp = 0; u8 buf[64] = { 0 }; mm_segment_t fs; loff_t *pos; char tmp_mac_file[256] = {0}; snprintf(tmp_mac_file, 255, "%s.%s", WIFI_MAC_ADDR_PATH, "tmp"); fp = filp_open(WIFI_MAC_ADDR_PATH, O_RDONLY, 0); if (IS_ERR(fp)) { wl_err("WIFI MAC can't be found wifimac.txt!\n"); fp = filp_open(tmp_mac_file, O_RDONLY, 0); if (IS_ERR(fp)) { wl_err("WIFI MAC can't found in temp file!\n"); goto random_mac; } } fs = get_fs(); set_fs(KERNEL_DS); pos = &fp->f_pos; vfs_read(fp, buf, sizeof(buf), pos); filp_close(fp, NULL); set_fs(fs); str2mac(buf, addr); if (!is_valid_ether_addr(addr)) { wl_ndev_log(L_ERR, vif->ndev, "%s invalid MAC address (%pM)\n", __func__, addr); return -EINVAL; } if (is_local_ether_addr(addr)) { netdev_warn(vif->ndev, "%s Warning: Assigning a locally valid " "MAC address (%pM) to a device\n", __func__, addr); netdev_warn(vif->ndev, "%s You should not set the 2nd rightmost " "bit in the first byte of the MAC\n", __func__); vif->local_mac_flag = 1; } else vif->local_mac_flag = 0; return 0; random_mac: random_ether_addr(addr); wl_warn("%s use random MAC address\n", __func__); /* initialize MAC addr with specific OUI */ addr[0] = 0x40; addr[1] = 0x45; addr[2] = 0xda; /*write random mac to WIFI FILE*/ write_mac_addr(tmp_mac_file, addr); return 0; } static void sprdwl_set_mac_addr(struct sprdwl_vif *vif, u8 *pending_addr, u8 *addr) { int default_mac_valid = 0; enum nl80211_iftype type = vif->wdev.iftype; struct sprdwl_priv *priv = vif->priv; if (!addr) { return; } else if (priv && is_valid_ether_addr(priv->mac_addr)) { ether_addr_copy(addr, priv->mac_addr); #ifdef STA_SOFTAP_SCC_MODE default_mac_valid = 1; #endif } else if (pending_addr && is_valid_ether_addr(pending_addr)) { ether_addr_copy(addr, pending_addr); } else if (priv && is_valid_ether_addr(priv->default_mac)) { ether_addr_copy(addr, priv->default_mac); default_mac_valid = 1; } else { sprdwl_get_mac_from_file(vif, addr); } if (!priv) { netdev_err(vif->ndev, "%s get pirv failed\n", __func__); return; } switch (type) { case NL80211_IFTYPE_STATION: ether_addr_copy(priv->default_mac, addr); break; case NL80211_IFTYPE_AP: if (default_mac_valid) { addr[0] ^= 0x10; addr[0] |= 0x2; } else ether_addr_copy(priv->default_mac, addr); break; case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_GO: addr[4] ^= 0x80; case NL80211_IFTYPE_P2P_DEVICE: addr[0] ^= 0x02; break; default: break; } } void init_scan_list(struct sprdwl_vif *vif) { if (!list_empty(&vif->scan_head_ptr)) { /*clean scan list if not empty first*/ clean_scan_list(vif); } INIT_LIST_HEAD(&vif->scan_head_ptr); } void clean_scan_list(struct sprdwl_vif *vif) { struct scan_result *node, *pos; int count = 0; list_for_each_entry_safe(node, pos, &vif->scan_head_ptr, list) { list_del(&node->list); kfree(node); count++; } wl_info("delete scan node num:%d\n", count); } #ifdef ACS_SUPPORT void clean_survey_info_list(struct sprdwl_vif *vif) { struct sprdwl_bssid *bssid = NULL, *pos_bssid = NULL; struct sprdwl_survey_info *info = NULL, *pos_info = NULL; list_for_each_entry_safe(info, pos_info, &vif->survey_info_list, survey_list) { list_del(&info->survey_list); if (!list_empty(&info->bssid_list)) { list_for_each_entry_safe(bssid, pos_bssid, &info->bssid_list, list) { list_del(&bssid->list); kfree(bssid); bssid = NULL; } } kfree(info); info = NULL; } } static unsigned short cal_total_beacon(struct sprdwl_vif *vif, struct sprdwl_survey_info *info) { unsigned short total_beacon = 0; short pos_chan, chan; total_beacon += info->beacon_num; chan = (short)info->chan; if (chan > 0 && chan < 15) { /* Calculate overlapping channels */ list_for_each_entry(info, &vif->survey_info_list, survey_list) { pos_chan = (short)info->chan; if (pos_chan > (chan - 4) && pos_chan < (chan + 4) && pos_chan != chan) { total_beacon += info->beacon_num; } } } wl_ndev_log(L_DBG, vif->ndev, "survey chan: %d, total beacon: %d!\n", chan, total_beacon); return total_beacon; } /* Transfer beacons to survey info */ void transfer_survey_info(struct sprdwl_vif *vif) { struct ieee80211_channel *channel = NULL; struct wiphy *wiphy = vif->wdev.wiphy; struct sprdwl_survey_info *info = NULL; unsigned int freq; unsigned short total_beacon = 0; list_for_each_entry(info, &vif->survey_info_list, survey_list) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) freq = ieee80211_channel_to_frequency(info->chan, info->chan <= CH_MAX_2G_CHANNEL ? NL80211_BAND_2GHZ : NL80211_BAND_5GHZ); #else freq = ieee80211_channel_to_frequency(info->chan, info->chan <= CH_MAX_2G_CHANNEL ? IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ); #endif channel = ieee80211_get_channel(wiphy, freq); if (channel) { total_beacon = cal_total_beacon(vif, info); info->cca_busy_time = (total_beacon < 20) ? total_beacon : 20; info->noise = -95 + ((total_beacon < 30) ? total_beacon : 30); info->channel = channel; } freq = 0; channel = NULL; total_beacon = 0; } } static bool find_bssid(struct sprdwl_survey_info *info, unsigned char *nbssid) { struct sprdwl_bssid *bssid = NULL; int ret = false; if (!list_empty(&info->bssid_list)) { list_for_each_entry(bssid, &info->bssid_list, list) { if (!memcmp(bssid->bssid, nbssid, 6)) { ret = true; break; } } } return ret; } static struct sprdwl_survey_info *find_survey_info(struct sprdwl_vif *vif, unsigned short chan) { struct sprdwl_survey_info *info = NULL, *result = NULL; if (!list_empty(&vif->survey_info_list)) { list_for_each_entry(info, &vif->survey_info_list, survey_list) { if (chan == info->chan) { result = info; break; } } } return result; } void acs_scan_result(struct sprdwl_vif *vif, u16 chan, struct ieee80211_mgmt *mgmt) { struct sprdwl_survey_info *info = NULL; struct sprdwl_bssid *bssid = NULL; info = find_survey_info(vif, chan); if (info) { if (!find_bssid(info, mgmt->bssid)) { bssid = kmalloc(sizeof(*bssid), GFP_KERNEL); if (bssid) { ether_addr_copy(bssid->bssid, mgmt->bssid); list_add_tail(&bssid->list, &info->bssid_list); info->beacon_num++; } else { wl_ndev_log(L_ERR, vif->ndev, "%s no memory for bssid!\n", __func__); } } } } #endif /* ACS_SUPPORT */ static void sprdwl_init_vif(struct sprdwl_priv *priv, struct sprdwl_vif *vif, const char *name) { WARN_ON(strlen(name) >= sizeof(vif->name)); strcpy(vif->name, name); vif->priv = priv; vif->sm_state = SPRDWL_DISCONNECTED; #ifdef ACS_SUPPORT /* Init ACS */ INIT_LIST_HEAD(&vif->survey_info_list); #endif INIT_LIST_HEAD(&vif->scan_head_ptr); } static void sprdwl_deinit_vif(struct sprdwl_vif *vif) { sprdwl_scan_done(vif, true); sprdwl_sched_scan_done(vif, true); /* We have to clear all the work which * is belong to the vif we are going to remove. */ if (vif->sm_state == SPRDWL_CONNECTING || vif->sm_state == SPRDWL_CONNECTED || vif->sm_state == SPRDWL_DISCONNECTING) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) cfg80211_disconnected(vif->ndev, 3, NULL, 0, false, GFP_KERNEL); #else/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)*/ cfg80211_disconnected(vif->ndev, 3, NULL, 0, GFP_KERNEL); #endif/*LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)*/ sprdwl_cancle_work(vif->priv, vif); if (vif->ref > 0) { int cnt = 0; unsigned long timeout = jiffies + msecs_to_jiffies(1000); do { usleep_range(2000, 2500); cnt++; if (time_after(jiffies, timeout)) { wl_ndev_log(L_ERR, vif->ndev, "%s timeout cnt %d\n", __func__, cnt); break; } } while (vif->ref > 0); wl_ndev_log(L_DBG, vif->ndev, "cnt %d\n", cnt); } } #ifndef CONFIG_P2P_INTF static struct sprdwl_vif *sprdwl_register_wdev(struct sprdwl_priv *priv, const char *name, enum nl80211_iftype type, u8 *addr) { struct sprdwl_vif *vif; struct wireless_dev *wdev; vif = kzalloc(sizeof(*vif), GFP_KERNEL); if (!vif) return ERR_PTR(-ENOMEM); /* initialize vif stuff */ sprdwl_init_vif(priv, vif, name); /* initialize wdev stuff */ wdev = &vif->wdev; wdev->wiphy = priv->wiphy; wdev->iftype = type; sprdwl_set_mac_addr(vif, addr, wdev->address); wl_info("iface '%s'(%pM) type %d added\n", name, wdev->address, type); return vif; } #endif static void sprdwl_unregister_wdev(struct sprdwl_vif *vif) { wl_info("iface '%s' deleted\n", vif->name); cfg80211_unregister_wdev(&vif->wdev); /* cfg80211_unregister_wdev use list_del_rcu to delete wdev, * so we can not free vif immediately, must wait until an * RCU grace period has elapsed. */ synchronize_rcu(); sprdwl_deinit_vif(vif); kfree(vif); } static struct sprdwl_vif *sprdwl_register_netdev(struct sprdwl_priv *priv, const char *name, enum nl80211_iftype type, u8 *addr) { struct net_device *ndev; struct wireless_dev *wdev; struct sprdwl_vif *vif; int ret; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)) ndev = alloc_netdev(sizeof(*vif), name, NET_NAME_UNKNOWN, ether_setup); #else ndev = alloc_netdev(sizeof(*vif), name, ether_setup); #endif if (!ndev) { wl_err("%s failed to alloc net_device!\n", __func__); return ERR_PTR(-ENOMEM); } /* initialize vif stuff */ vif = netdev_priv(ndev); vif->ndev = ndev; sprdwl_init_vif(priv, vif, name); /* initialize wdev stuff */ wdev = &vif->wdev; wdev->netdev = ndev; wdev->wiphy = priv->wiphy; wdev->iftype = type; /* initialize ndev stuff */ ndev->ieee80211_ptr = wdev; if (priv->fw_capa & SPRDWL_CAPA_MC_FILTER) { wl_info("\tMulticast Filter supported\n"); vif->mc_filter = kzalloc(sizeof(struct sprdwl_mc_filter) + priv->max_mc_mac_addrs * ETH_ALEN, GFP_KERNEL); if (!vif->mc_filter) { ret = -ENOMEM; goto err; } sprdwl_netdev_ops.ndo_set_rx_mode = sprdwl_set_multicast; } ndev->netdev_ops = &sprdwl_netdev_ops; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) ndev->priv_destructor = free_netdev; #else ndev->destructor = free_netdev; #endif ndev->needed_headroom = priv->skb_head_len; ndev->watchdog_timeo = 2 * HZ; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) ndev->features |= NETIF_F_CSUM_MASK; #else ndev->features |= NETIF_F_ALL_CSUM; #endif #ifdef RX_NAPI ndev->features |= NETIF_F_GRO; #endif/*RX_NAPI*/ ndev->features |= NETIF_F_SG; SET_NETDEV_DEV(ndev, wiphy_dev(priv->wiphy)); sprdwl_set_mac_addr(vif, addr, ndev->dev_addr); #ifdef CONFIG_P2P_INTF if (type == NL80211_IFTYPE_P2P_DEVICE) wdev->iftype = NL80211_IFTYPE_STATION; #endif /* register new Ethernet interface */ ret = register_netdevice(ndev); if (ret) { wl_ndev_log(L_ERR, ndev, "failed to regitster netdev(%d)!\n", ret); goto err; } wl_info("iface '%s'(%pM) type %d added\n", ndev->name, ndev->dev_addr, type); return vif; err: sprdwl_deinit_vif(vif); free_netdev(ndev); return ERR_PTR(ret); } static void sprdwl_unregister_netdev(struct sprdwl_vif *vif) { wl_info("iface '%s' deleted\n", vif->ndev->name); if (vif->priv->fw_capa & SPRDWL_CAPA_MC_FILTER) kfree(vif->mc_filter); sprdwl_deinit_vif(vif); unregister_netdevice(vif->ndev); } struct wireless_dev *sprdwl_add_iface(struct sprdwl_priv *priv, const char *name, enum nl80211_iftype type, u8 *addr) { struct sprdwl_vif *vif; #ifndef CONFIG_P2P_INTF if (type == NL80211_IFTYPE_P2P_DEVICE) vif = sprdwl_register_wdev(priv, name, type, addr); else vif = sprdwl_register_netdev(priv, name, type, addr); #else vif = sprdwl_register_netdev(priv, name, type, addr); #endif if (IS_ERR(vif)) { wl_err("failed to add iface '%s'\n", name); return (void *)vif; } init_completion(&vif->disconnect_completed); #ifdef DFS_MASTER sprdwl_init_dfs_master(vif); #endif spin_lock_bh(&priv->list_lock); list_add_tail(&vif->vif_node, &priv->vif_list); spin_unlock_bh(&priv->list_lock); return &vif->wdev; } int sprdwl_del_iface(struct sprdwl_priv *priv, struct sprdwl_vif *vif) { #ifdef DFS_MASTER sprdwl_deinit_dfs_master(vif); #endif if (!vif->ndev) sprdwl_unregister_wdev(vif); else sprdwl_unregister_netdev(vif); return 0; } static void sprdwl_del_all_ifaces(struct sprdwl_priv *priv) { struct sprdwl_vif *vif; next_intf: spin_lock_bh(&priv->list_lock); list_for_each_entry_reverse(vif, &priv->vif_list, vif_node) { list_del(&vif->vif_node); spin_unlock_bh(&priv->list_lock); rtnl_lock(); sprdwl_del_iface(priv, vif); rtnl_unlock(); goto next_intf; } spin_unlock_bh(&priv->list_lock); } static void sprdwl_init_debugfs(struct sprdwl_priv *priv) { if (!priv->wiphy->debugfsdir) return; priv->debugfs = debugfs_create_dir("sprdwl_wifi", priv->wiphy->debugfsdir); if (IS_ERR_OR_NULL(priv->debugfs)) return; sprdwl_intf_debugfs(priv, priv->debugfs); } int sprdwl_core_init(struct device *dev, struct sprdwl_priv *priv) { struct wiphy *wiphy = priv->wiphy; struct wireless_dev *wdev; int ret; ret = sprdwl_sync_version(priv); if (ret) { wl_err("SYNC CMD ERROR!!\n"); goto out; } sprdwl_download_ini(priv); sprdwl_tcp_ack_init(priv); sprdwl_get_fw_info(priv); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) && defined(RTT_SUPPORT) sprdwl_ftm_init(priv); #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) && defined(RTT_SUPPORT) */ sprdwl_setup_wiphy(wiphy, priv); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) sprdwl_vendor_init(wiphy); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ set_wiphy_dev(wiphy, dev); ret = wiphy_register(wiphy); if (ret) { wl_err("failed to regitster wiphy(%d)!\n", ret); goto out; } sprdwl_init_debugfs(priv); rtnl_lock(); wdev = sprdwl_add_iface(priv, "wlan%d", NL80211_IFTYPE_STATION, NULL); rtnl_unlock(); if (IS_ERR(wdev)) { wiphy_unregister(wiphy); ret = -ENXIO; goto out; } #ifdef STA_SOFTAP_SCC_MODE rtnl_lock(); wdev = sprdwl_add_iface(priv, "wlan%d", NL80211_IFTYPE_AP, NULL); rtnl_unlock(); if (IS_ERR(wdev)) { wiphy_unregister(wiphy); ret = -ENXIO; goto out; } #endif #ifdef CONFIG_P2P_INTF rtnl_lock(); wdev = sprdwl_add_iface(priv, "p2p%d", NL80211_IFTYPE_P2P_DEVICE, NULL); rtnl_unlock(); if (IS_ERR(wdev)) { wiphy_unregister(wiphy); ret = -ENXIO; goto out; } #endif #ifdef RX_NAPI sprdwl_rx_napi_init(wdev->netdev, ((struct sprdwl_intf *)priv->hw_priv)); #endif/*RX_NAPI*/ #if defined(UWE5621_FTR) qos_enable(1); #endif sprdwl_init_npi(); ret = register_inetaddr_notifier(&sprdwl_inetaddr_cb); if (ret) wl_err("%s failed to register inetaddr notifier(%d)!\n", __func__, ret); if (priv->fw_capa & SPRDWL_CAPA_NS_OFFLOAD) { wl_info("\tIPV6 NS Offload supported\n"); ret = register_inet6addr_notifier(&sprdwl_inet6addr_cb); if (ret) wl_err("%s failed to register inet6addr notifier(%d)!\n", __func__, ret); } trace_info_init(); ret = marlin_reset_register_notify(priv->if_ops->force_exit, priv->hw_priv); if (ret) { wl_err("%s failed to register wcn cp rest notify(%d)!\n", __func__, ret); } out: return ret; } int sprdwl_core_deinit(struct sprdwl_priv *priv) { marlin_reset_unregister_notify(); unregister_inetaddr_notifier(&sprdwl_inetaddr_cb); if (priv->fw_capa & SPRDWL_CAPA_NS_OFFLOAD) unregister_inet6addr_notifier(&sprdwl_inet6addr_cb); sprdwl_deinit_npi(); #if defined(UWE5621_FTR) qos_enable(0); #endif #ifdef RX_NAPI sprdwl_rx_napi_deinit((struct sprdwl_intf *)priv->hw_priv); #endif/*RX_NAPI*/ sprdwl_del_all_ifaces(priv); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) sprdwl_vendor_deinit(priv->wiphy); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ wiphy_unregister(priv->wiphy); sprdwl_cmd_wake_upall(); sprdwl_tcp_ack_deinit(priv); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) && defined(RTT_SUPPORT) sprdwl_ftm_deinit(priv); #endif /* (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) && defined(RTT_SUPPORT)*/ trace_info_deinit(); return 0; } unsigned int wfa_cap; module_param(wfa_cap, uint, 0000); MODULE_PARM_DESC(wfa_cap, "set capability for WFA test"); unsigned int tcp_ack_drop_cnt = SPRDWL_TCP_ACK_DROP_CNT; /* Maybe you need S_IRUGO | S_IWUSR for debug */ module_param(tcp_ack_drop_cnt, uint, 0000); MODULE_PARM_DESC(tcp_ack_drop_cnt, "valid values: [1, 13]"); #ifdef TCP_ACK_DROP_SUPPORT unsigned int tcp_ack_drop_enable = 1; module_param(tcp_ack_drop_enable, uint, 0000); MODULE_PARM_DESC(tcp_ack_drop_enable, "valid values: [0, 1]"); #else const unsigned int tcp_ack_drop_enable; #endif