/** ****************************************************************************** * * @file ecrnx_main.c * * @brief Entry point of the ECRNX driver * * Copyright (C) ESWIN 2015-2020 * ****************************************************************************** */ #include #include #include #include #include #include #include "ecrnx_defs.h" #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) #include #include #endif #include "ecrnx_msg_tx.h" #include "ecrnx_tx.h" #include "reg_access.h" #include "hal_desc.h" #include "ecrnx_debugfs.h" #include "ecrnx_cfgfile.h" #include "ecrnx_radar.h" #include "ecrnx_version.h" #ifdef CONFIG_ECRNX_BFMER #include "ecrnx_bfmer.h" #endif //(CONFIG_ECRNX_BFMER) #include "ecrnx_tdls.h" #include "ecrnx_events.h" #include "ecrnx_compat.h" #include "ecrnx_rx.h" #include "ecrnx_p2p.h" #include "ecrnx_debugfs_custom.h" #include "ecrnx_calibration_data.h" #include "eswin_utils.h" #include "ecrnx_debugfs_func.h" static struct ieee80211_rate ecrnx_ratetable[] = { RATE(10, 0x00, 0), RATE(20, 0x01, IEEE80211_RATE_SHORT_PREAMBLE), RATE(55, 0x02, IEEE80211_RATE_SHORT_PREAMBLE), RATE(110, 0x03, IEEE80211_RATE_SHORT_PREAMBLE), RATE(60, 0x04, 0), RATE(90, 0x05, 0), RATE(120, 0x06, 0), RATE(180, 0x07, 0), RATE(240, 0x08, 0), RATE(360, 0x09, 0), RATE(480, 0x0A, 0), RATE(540, 0x0B, 0), }; /* The channels indexes here are not used anymore */ static struct ieee80211_channel ecrnx_2ghz_channels[] = { CHAN(2412), CHAN(2417), CHAN(2422), CHAN(2427), CHAN(2432), CHAN(2437), CHAN(2442), CHAN(2447), CHAN(2452), CHAN(2457), CHAN(2462), CHAN(2467), CHAN(2472), CHAN(2484), // Extra channels defined only to be used for PHY measures. // Enabled only if custregd and custchan parameters are set CHAN(2390), CHAN(2400), CHAN(2410), CHAN(2420), CHAN(2430), CHAN(2440), CHAN(2450), CHAN(2460), CHAN(2470), CHAN(2480), CHAN(2490), CHAN(2500), CHAN(2510), }; #ifdef CONFIG_ECRNX_5G static struct ieee80211_channel ecrnx_5ghz_channels[] = { CHAN(5180), // 36 - 20MHz CHAN(5200), // 40 - 20MHz CHAN(5220), // 44 - 20MHz CHAN(5240), // 48 - 20MHz CHAN(5260), // 52 - 20MHz CHAN(5280), // 56 - 20MHz CHAN(5300), // 60 - 20MHz CHAN(5320), // 64 - 20MHz CHAN(5500), // 100 - 20MHz CHAN(5520), // 104 - 20MHz CHAN(5540), // 108 - 20MHz CHAN(5560), // 112 - 20MHz CHAN(5580), // 116 - 20MHz CHAN(5600), // 120 - 20MHz CHAN(5620), // 124 - 20MHz CHAN(5640), // 128 - 20MHz CHAN(5660), // 132 - 20MHz CHAN(5680), // 136 - 20MHz CHAN(5700), // 140 - 20MHz CHAN(5720), // 144 - 20MHz CHAN(5745), // 149 - 20MHz CHAN(5765), // 153 - 20MHz CHAN(5785), // 157 - 20MHz CHAN(5805), // 161 - 20MHz CHAN(5825), // 165 - 20MHz // Extra channels defined only to be used for PHY measures. // Enabled only if custregd and custchan parameters are set CHAN(5190), CHAN(5210), CHAN(5230), CHAN(5250), CHAN(5270), CHAN(5290), CHAN(5310), CHAN(5330), CHAN(5340), CHAN(5350), CHAN(5360), CHAN(5370), CHAN(5380), CHAN(5390), CHAN(5400), CHAN(5410), CHAN(5420), CHAN(5430), CHAN(5440), CHAN(5450), CHAN(5460), CHAN(5470), CHAN(5480), CHAN(5490), CHAN(5510), CHAN(5530), CHAN(5550), CHAN(5570), CHAN(5590), CHAN(5610), CHAN(5630), CHAN(5650), CHAN(5670), CHAN(5690), CHAN(5710), CHAN(5730), CHAN(5750), CHAN(5760), CHAN(5770), CHAN(5780), CHAN(5790), CHAN(5800), CHAN(5810), CHAN(5820), CHAN(5830), CHAN(5840), CHAN(5850), CHAN(5860), CHAN(5870), CHAN(5880), CHAN(5890), CHAN(5900), CHAN(5910), CHAN(5920), CHAN(5930), CHAN(5940), CHAN(5950), CHAN(5960), CHAN(5970), }; #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) #if CONFIG_ECRNX_HE static struct ieee80211_sband_iftype_data ecrnx_he_capa = { .types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), .he_cap = ECRNX_HE_CAPABILITIES, }; #endif #endif static struct ieee80211_supported_band ecrnx_band_2GHz = { .channels = ecrnx_2ghz_channels, .n_channels = ARRAY_SIZE(ecrnx_2ghz_channels) - 13, // -13 to exclude extra channels .bitrates = ecrnx_ratetable, .n_bitrates = ARRAY_SIZE(ecrnx_ratetable), .ht_cap = ECRNX_HT_CAPABILITIES, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) #if CONFIG_ECRNX_HE .iftype_data = &ecrnx_he_capa, .n_iftype_data = 1, #endif #endif }; #ifdef CONFIG_ECRNX_5G static struct ieee80211_supported_band ecrnx_band_5GHz = { .channels = ecrnx_5ghz_channels, .n_channels = ARRAY_SIZE(ecrnx_5ghz_channels) - 59, // -59 to exclude extra channels .bitrates = &ecrnx_ratetable[4], .n_bitrates = ARRAY_SIZE(ecrnx_ratetable) - 4, .ht_cap = ECRNX_HT_CAPABILITIES, .vht_cap = ECRNX_VHT_CAPABILITIES, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0) #if CONFIG_ECRNX_HE .iftype_data = &ecrnx_he_capa, .n_iftype_data = 1, #endif #endif }; #endif static struct ieee80211_iface_limit ecrnx_limits[] = { { .max = NX_VIRT_DEV_MAX, .types = BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_STATION)} }; static struct ieee80211_iface_limit ecrnx_limits_dfs[] = { { .max = NX_VIRT_DEV_MAX, .types = BIT(NL80211_IFTYPE_AP)} }; static const struct ieee80211_iface_combination ecrnx_combinations[] = { { .limits = ecrnx_limits, .n_limits = ARRAY_SIZE(ecrnx_limits), .num_different_channels = NX_CHAN_CTXT_CNT, .max_interfaces = NX_VIRT_DEV_MAX, }, /* Keep this combination as the last one */ { .limits = ecrnx_limits_dfs, .n_limits = ARRAY_SIZE(ecrnx_limits_dfs), .num_different_channels = 1, .max_interfaces = NX_VIRT_DEV_MAX, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) .radar_detect_widths = (BIT(NL80211_CHAN_WIDTH_20_NOHT) | BIT(NL80211_CHAN_WIDTH_20) | BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80)), #endif } }; /* There isn't a lot of sense in it, but you can transmit anything you like */ static struct ieee80211_txrx_stypes ecrnx_default_mgmt_stypes[NUM_NL80211_IFTYPES] = { [NL80211_IFTYPE_STATION] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4)), }, [NL80211_IFTYPE_AP] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_DISASSOC >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4) | BIT(IEEE80211_STYPE_ACTION >> 4)), }, [NL80211_IFTYPE_AP_VLAN] = { /* copy AP */ .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_DISASSOC >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4) | BIT(IEEE80211_STYPE_ACTION >> 4)), }, [NL80211_IFTYPE_P2P_CLIENT] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4)), }, [NL80211_IFTYPE_P2P_GO] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4) | BIT(IEEE80211_STYPE_DISASSOC >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4) | BIT(IEEE80211_STYPE_ACTION >> 4)), }, [NL80211_IFTYPE_P2P_DEVICE] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_PROBE_REQ >> 4)), }, [NL80211_IFTYPE_MESH_POINT] = { .tx = 0xffff, .rx = (BIT(IEEE80211_STYPE_ACTION >> 4) | BIT(IEEE80211_STYPE_AUTH >> 4) | BIT(IEEE80211_STYPE_DEAUTH >> 4)), }, }; static u32 cipher_suites[] = { WLAN_CIPHER_SUITE_WEP40, WLAN_CIPHER_SUITE_WEP104, WLAN_CIPHER_SUITE_TKIP, WLAN_CIPHER_SUITE_CCMP, 0, // reserved entries to enable AES-CMAC and/or SMS4 0, 0, 0, 0, }; #define NB_RESERVED_CIPHER 5; static const int ecrnx_ac2hwq[1][NL80211_NUM_ACS] = { { [NL80211_TXQ_Q_VO] = ECRNX_HWQ_VO, [NL80211_TXQ_Q_VI] = ECRNX_HWQ_VI, [NL80211_TXQ_Q_BE] = ECRNX_HWQ_BE, [NL80211_TXQ_Q_BK] = ECRNX_HWQ_BK } }; const int ecrnx_tid2hwq[IEEE80211_NUM_TIDS] = { ECRNX_HWQ_BE, ECRNX_HWQ_BK, ECRNX_HWQ_BK, ECRNX_HWQ_BE, ECRNX_HWQ_VI, ECRNX_HWQ_VI, ECRNX_HWQ_VO, ECRNX_HWQ_VO, /* TID_8 is used for management frames */ ECRNX_HWQ_VO, /* At the moment, all others TID are mapped to BE */ ECRNX_HWQ_BE, ECRNX_HWQ_BE, ECRNX_HWQ_BE, ECRNX_HWQ_BE, ECRNX_HWQ_BE, ECRNX_HWQ_BE, ECRNX_HWQ_BE, }; static const int ecrnx_hwq2uapsd[NL80211_NUM_ACS] = { [ECRNX_HWQ_VO] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VO, [ECRNX_HWQ_VI] = IEEE80211_WMM_IE_STA_QOSINFO_AC_VI, [ECRNX_HWQ_BE] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BE, [ECRNX_HWQ_BK] = IEEE80211_WMM_IE_STA_QOSINFO_AC_BK, }; /********************************************************************* * helper *********************************************************************/ struct ecrnx_sta *ecrnx_get_sta(struct ecrnx_hw *ecrnx_hw, const u8 *mac_addr) { int i; for (i = 0; i < NX_REMOTE_STA_MAX; i++) { struct ecrnx_sta *sta = &ecrnx_hw->sta_table[i]; if (sta->valid && (memcmp(mac_addr, &sta->mac_addr, 6) == 0)) return sta; } return NULL; } void ecrnx_enable_wapi(struct ecrnx_hw *ecrnx_hw) { cipher_suites[ecrnx_hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_SMS4; ecrnx_hw->wiphy->n_cipher_suites ++; ecrnx_hw->wiphy->flags |= WIPHY_FLAG_CONTROL_PORT_PROTOCOL; } void ecrnx_enable_mfp(struct ecrnx_hw *ecrnx_hw) { cipher_suites[ecrnx_hw->wiphy->n_cipher_suites] = WLAN_CIPHER_SUITE_AES_CMAC; ecrnx_hw->wiphy->n_cipher_suites ++; } void ecrnx_enable_gcmp(struct ecrnx_hw *ecrnx_hw) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_CCMP_256; cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP; cipher_suites[ecrnx_hw->wiphy->n_cipher_suites++] = WLAN_CIPHER_SUITE_GCMP_256; #endif } u8 *ecrnx_build_bcn(struct ecrnx_bcn *bcn, struct cfg80211_beacon_data *new) { u8 *buf, *pos; if (new->head) { u8 *head = kmalloc(new->head_len, GFP_KERNEL); if (!head) return NULL; if (bcn->head) kfree(bcn->head); bcn->head = head; bcn->head_len = new->head_len; memcpy(bcn->head, new->head, new->head_len); } if (new->tail) { u8 *tail = kmalloc(new->tail_len, GFP_KERNEL); if (!tail) return NULL; if (bcn->tail) kfree(bcn->tail); bcn->tail = tail; bcn->tail_len = new->tail_len; memcpy(bcn->tail, new->tail, new->tail_len); } if (!bcn->head) return NULL; bcn->tim_len = 6; bcn->len = bcn->head_len + bcn->tail_len + bcn->ies_len + bcn->tim_len; buf = kmalloc(bcn->len, GFP_KERNEL); if (!buf) return NULL; // Build the beacon buffer pos = buf; memcpy(pos, bcn->head, bcn->head_len); pos += bcn->head_len; *pos++ = WLAN_EID_TIM; *pos++ = 4; *pos++ = 0; *pos++ = bcn->dtim; *pos++ = 0; *pos++ = 0; if (bcn->tail) { memcpy(pos, bcn->tail, bcn->tail_len); pos += bcn->tail_len; } if (bcn->ies) { memcpy(pos, bcn->ies, bcn->ies_len); } return buf; } static void ecrnx_del_bcn(struct ecrnx_bcn *bcn) { if (bcn->head) { kfree(bcn->head); bcn->head = NULL; } bcn->head_len = 0; if (bcn->tail) { kfree(bcn->tail); bcn->tail = NULL; } bcn->tail_len = 0; if (bcn->ies) { kfree(bcn->ies); bcn->ies = NULL; } bcn->ies_len = 0; bcn->tim_len = 0; bcn->dtim = 0; bcn->len = 0; } /** * Link channel ctxt to a vif and thus increments count for this context. */ void ecrnx_chanctx_link(struct ecrnx_vif *vif, u8 ch_idx, struct cfg80211_chan_def *chandef) { struct ecrnx_chanctx *ctxt; if (ch_idx >= NX_CHAN_CTXT_CNT) { WARN(1, "Invalid channel ctxt id %d", ch_idx); return; } vif->ch_index = ch_idx; ctxt = &vif->ecrnx_hw->chanctx_table[ch_idx]; ctxt->count++; // For now chandef is NULL for STATION interface if (chandef) { if (!ctxt->chan_def.chan) ctxt->chan_def = *chandef; else { // TODO. check that chandef is the same as the one already // set for this ctxt } } } /** * Unlink channel ctxt from a vif and thus decrements count for this context */ void ecrnx_chanctx_unlink(struct ecrnx_vif *vif) { struct ecrnx_chanctx *ctxt; if (vif->ch_index == ECRNX_CH_NOT_SET) return; ctxt = &vif->ecrnx_hw->chanctx_table[vif->ch_index]; if (ctxt->count == 0) { WARN(1, "Chan ctxt ref count is already 0"); } else { ctxt->count--; } if (ctxt->count == 0) { if (vif->ch_index == vif->ecrnx_hw->cur_chanctx) { /* If current chan ctxt is no longer linked to a vif disable radar detection (no need to check if it was activated) */ ecrnx_radar_detection_enable(&vif->ecrnx_hw->radar, ECRNX_RADAR_DETECT_DISABLE, ECRNX_RADAR_RIU); } /* set chan to null, so that if this ctxt is relinked to a vif that don't have channel information, don't use wrong information */ ctxt->chan_def.chan = NULL; } vif->ch_index = ECRNX_CH_NOT_SET; } int ecrnx_chanctx_valid(struct ecrnx_hw *ecrnx_hw, u8 ch_idx) { if (ch_idx >= NX_CHAN_CTXT_CNT || ecrnx_hw->chanctx_table[ch_idx].chan_def.chan == NULL) { return 0; } return 1; } static void ecrnx_del_csa(struct ecrnx_vif *vif) { struct ecrnx_hw *ecrnx_hw = vif->ecrnx_hw; struct ecrnx_csa *csa = vif->ap.csa; if (!csa) return; ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &csa->elem); ecrnx_del_bcn(&csa->bcn); kfree(csa); vif->ap.csa = NULL; } static void ecrnx_csa_finish(struct work_struct *ws) { struct ecrnx_csa *csa = container_of(ws, struct ecrnx_csa, work); struct ecrnx_vif *vif = csa->vif; struct ecrnx_hw *ecrnx_hw = vif->ecrnx_hw; int error = csa->status; if (!error) error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, csa->elem.dma_addr, csa->bcn.len, csa->bcn.head_len, csa->bcn.tim_len, NULL); if (error){ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)) cfg80211_stop_iface(ecrnx_hw->wiphy, &vif->wdev, GFP_KERNEL); #else cfg80211_disconnected(vif->ndev, 0, NULL, 0, 0, GFP_KERNEL); #endif } else { mutex_lock(&vif->wdev.mtx); __acquire(&vif->wdev.mtx); spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_unlink(vif); ecrnx_chanctx_link(vif, csa->ch_idx, &csa->chandef); if (ecrnx_hw->cur_chanctx == csa->ch_idx) { ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); ecrnx_txq_vif_start(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); } else ecrnx_txq_vif_stop(vif, ECRNX_TXQ_STOP_CHAN, ecrnx_hw); spin_unlock_bh(&ecrnx_hw->cb_lock); cfg80211_ch_switch_notify(vif->ndev, &csa->chandef); mutex_unlock(&vif->wdev.mtx); __release(&vif->wdev.mtx); } ecrnx_del_csa(vif); } /** * ecrnx_external_auth_enable - Enable external authentication on a vif * * @vif: VIF on which external authentication must be enabled * * External authentication requires to start TXQ for unknown STA in * order to send auth frame pusehd by user space. * Note: It is assumed that fw is on the correct channel. */ void ecrnx_external_auth_enable(struct ecrnx_vif *vif) { vif->sta.flags |= ECRNX_STA_EXT_AUTH; ecrnx_txq_unk_vif_init(vif); ecrnx_txq_start(ecrnx_txq_vif_get(vif, NX_UNK_TXQ_TYPE), 0); } /** * ecrnx_external_auth_disable - Disable external authentication on a vif * * @vif: VIF on which external authentication must be disabled */ void ecrnx_external_auth_disable(struct ecrnx_vif *vif) { if (!(vif->sta.flags & ECRNX_STA_EXT_AUTH)) return; vif->sta.flags &= ~ECRNX_STA_EXT_AUTH; ecrnx_txq_unk_vif_deinit(vif); } /** * ecrnx_update_mesh_power_mode - * * @vif: mesh VIF for which power mode is updated * * Does nothing if vif is not a mesh point interface. * Since firmware doesn't support one power save mode per link select the * most "active" power mode among all mesh links. * Indeed as soon as we have to be active on one link we might as well be * active on all links. * * If there is no link then the power mode for next peer is used; */ void ecrnx_update_mesh_power_mode(struct ecrnx_vif *vif) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) enum nl80211_mesh_power_mode mesh_pm; struct ecrnx_sta *sta; struct mesh_config mesh_conf; struct mesh_update_cfm cfm; u32 mask; if (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MESH_POINT) return; if (list_empty(&vif->ap.sta_list)) { mesh_pm = vif->ap.next_mesh_pm; } else { mesh_pm = NL80211_MESH_POWER_DEEP_SLEEP; list_for_each_entry(sta, &vif->ap.sta_list, list) { if (sta->valid && (sta->mesh_pm < mesh_pm)) { mesh_pm = sta->mesh_pm; } } } if (mesh_pm == vif->ap.mesh_pm) return; mask = BIT(NL80211_MESHCONF_POWER_MODE - 1); mesh_conf.power_mode = mesh_pm; if (ecrnx_send_mesh_update_req(vif->ecrnx_hw, vif, mask, &mesh_conf, &cfm) || cfm.status) return; vif->ap.mesh_pm = mesh_pm; #endif } void ecrnx_save_assoc_info_for_ft(struct ecrnx_vif *vif, struct cfg80211_connect_params *sme) { int ies_len = sme->ie_len + sme->ssid_len + 2; u8 *pos; if (!vif->sta.ft_assoc_ies) { if (!cfg80211_find_ie(WLAN_EID_MOBILITY_DOMAIN, sme->ie, sme->ie_len)) return; vif->sta.ft_assoc_ies_len = ies_len; vif->sta.ft_assoc_ies = kmalloc(ies_len, GFP_KERNEL); } else if (vif->sta.ft_assoc_ies_len < ies_len) { kfree(vif->sta.ft_assoc_ies); vif->sta.ft_assoc_ies = kmalloc(ies_len, GFP_KERNEL); } if (!vif->sta.ft_assoc_ies) return; pos = vif->sta.ft_assoc_ies; *pos++ = WLAN_EID_SSID; *pos++ = sme->ssid_len; memcpy(pos, sme->ssid, sme->ssid_len); pos += sme->ssid_len; memcpy(pos, sme->ie, sme->ie_len); vif->sta.ft_assoc_ies_len = ies_len; } /** * ecrnx_rsne_to_connect_params - Initialise cfg80211_connect_params from * RSN element. * * @rsne: RSN element * @sme: Structure cfg80211_connect_params to initialize * * The goal is only to initialize enough for ecrnx_send_sm_connect_req */ int ecrnx_rsne_to_connect_params(const struct ecrnx_element *rsne, struct cfg80211_connect_params *sme) { int len = rsne->datalen; int clen; const u8 *pos = rsne->data ; if (len < 8) return 1; sme->crypto.control_port_no_encrypt = false; sme->crypto.control_port = true; sme->crypto.control_port_ethertype = cpu_to_be16(ETH_P_PAE); pos += 2; sme->crypto.cipher_group = ntohl(*((u32 *)pos)); pos += 4; clen = le16_to_cpu(*((u16 *)pos)) * 4; pos += 2; len -= 8; if (len < clen + 2) return 1; // only need one cipher suite sme->crypto.n_ciphers_pairwise = 1; sme->crypto.ciphers_pairwise[0] = ntohl(*((u32 *)pos)); pos += clen; len -= clen; // no need for AKM clen = le16_to_cpu(*((u16 *)pos)) * 4; pos += 2; len -= 2; if (len < clen) return 1; pos += clen; len -= clen; if (len < 4) return 0; pos += 2; clen = le16_to_cpu(*((u16 *)pos)) * 16; len -= 4; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (len > clen) sme->mfp = NL80211_MFP_REQUIRED; #endif return 0; } /********************************************************************* * netdev callbacks ********************************************************************/ /** * int (*ndo_open)(struct net_device *dev); * This function is called when network device transistions to the up * state. * * - Start FW if this is the first interface opened * - Add interface at fw level */ static int ecrnx_open(struct net_device *dev) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; struct mm_add_if_cfm add_if_cfm; int error = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); // Check if it is the first opened VIF if (ecrnx_hw->vif_started == 0) { // Start the FW if ((error = ecrnx_send_start(ecrnx_hw))) return error; /* Device is now started */ set_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags); } if (ecrnx_vif->up) { netdev_info(dev, "Started repeatedly"); return error; } if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN) { /* For AP_vlan use same fw and drv indexes. We ensure that this index will not be used by fw for another vif by taking index >= NX_VIRT_DEV_MAX */ add_if_cfm.inst_nbr = ecrnx_vif->drv_vif_index; netif_tx_stop_all_queues(dev); } else { /* Forward the information to the LMAC, * p2p value not used in FMAC configuration, iftype is sufficient */ if ((error = ecrnx_send_add_if(ecrnx_hw, dev->dev_addr, ECRNX_VIF_TYPE(ecrnx_vif), false, &add_if_cfm))) return error; if (add_if_cfm.status != 0) { ECRNX_PRINT_CFM_ERR(add_if); return -EIO; } } /* Save the index retrieved from LMAC */ spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_vif->vif_index = add_if_cfm.inst_nbr; ecrnx_vif->up = true; ecrnx_hw->vif_started++; ecrnx_hw->vif_table[add_if_cfm.inst_nbr] = ecrnx_vif; memset(ecrnx_hw->vif_table[add_if_cfm.inst_nbr]->rx_pn, 0, TID_MAX * sizeof(uint64_t)); spin_unlock_bh(&ecrnx_hw->cb_lock); if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MONITOR) { ecrnx_hw->monitor_vif = ecrnx_vif->vif_index; if (ecrnx_vif->ch_index != ECRNX_CH_NOT_SET) { //Configure the monitor channel error = ecrnx_send_config_monitor_req(ecrnx_hw, &ecrnx_hw->chanctx_table[ecrnx_vif->ch_index].chan_def, NULL); } } netif_carrier_off(dev); return error; } /** * int (*ndo_stop)(struct net_device *dev); * This function is called when network device transistions to the down * state. * * - Remove interface at fw level * - Reset FW if this is the last interface opened */ static int ecrnx_close(struct net_device *dev) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = ecrnx_vif->ecrnx_hw; ECRNX_DBG(ECRNX_FN_ENTRY_STR); netdev_info(dev, "CLOSE"); ecrnx_radar_cancel_cac(&ecrnx_hw->radar); spin_lock_bh(&ecrnx_hw->scan_req_lock); /* Abort scan request on the vif */ if (ecrnx_hw->scan_request && ecrnx_hw->scan_request->wdev == &ecrnx_vif->wdev) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) struct cfg80211_scan_info info = { .aborted = true, }; cfg80211_scan_done(ecrnx_hw->scan_request, &info); #else cfg80211_scan_done(ecrnx_hw->scan_request, true); #endif ecrnx_hw->scan_request = NULL; } spin_unlock_bh(&ecrnx_hw->scan_req_lock); ecrnx_send_remove_if(ecrnx_hw, ecrnx_vif->vif_index); if (ecrnx_hw->roc && (ecrnx_hw->roc->vif == ecrnx_vif)) { kfree(ecrnx_hw->roc); /* Initialize RoC element pointer to NULL, indicate that RoC can be started */ ecrnx_hw->roc = NULL; } /* Ensure that we won't process disconnect ind */ spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_vif->up = false; if (netif_carrier_ok(dev)) { if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION || ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_CLIENT) { if (ecrnx_vif->sta.ft_assoc_ies) { kfree(ecrnx_vif->sta.ft_assoc_ies); ecrnx_vif->sta.ft_assoc_ies = NULL; ecrnx_vif->sta.ft_assoc_ies_len = 0; } cfg80211_disconnected(dev, WLAN_REASON_DEAUTH_LEAVING, NULL, 0, true, GFP_ATOMIC); if (ecrnx_vif->sta.ap) { ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_vif->sta.ap); ecrnx_txq_tdls_vif_deinit(ecrnx_vif); } netif_tx_stop_all_queues(dev); netif_carrier_off(dev); } else if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN) { netif_carrier_off(dev); } else { netdev_warn(dev, "AP not stopped when disabling interface"); } } ecrnx_hw->vif_table[ecrnx_vif->vif_index] = NULL; spin_unlock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_unlink(ecrnx_vif); if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_MONITOR) ecrnx_hw->monitor_vif = ECRNX_INVALID_VIF; ecrnx_hw->vif_started--; if (ecrnx_hw->vif_started == 0) { #ifndef CONFIG_ECRNX_ESWIN /* This also lets both ipc sides remain in sync before resetting */ ecrnx_ipc_tx_drain(ecrnx_hw); #endif ecrnx_send_reset(ecrnx_hw); // Set parameters to firmware ecrnx_send_me_config_req(ecrnx_hw); // Set channel parameters to firmware ecrnx_send_me_chan_config_req(ecrnx_hw); clear_bit(ECRNX_DEV_STARTED, &ecrnx_hw->flags); } return 0; } /** * struct net_device_stats* (*ndo_get_stats)(struct net_device *dev); * Called when a user wants to get the network device usage * statistics. Drivers must do one of the following: * 1. Define @ndo_get_stats64 to fill in a zero-initialised * rtnl_link_stats64 structure passed by the caller. * 2. Define @ndo_get_stats to update a net_device_stats structure * (which should normally be dev->stats) and return a pointer to * it. The structure may be changed asynchronously only if each * field is written atomically. * 3. Update dev->stats asynchronously and atomically, and define * neither operation. */ static struct net_device_stats *ecrnx_get_stats(struct net_device *dev) { struct ecrnx_vif *vif = netdev_priv(dev); return &vif->net_stats; } /** * u16 (*ndo_select_queue)(struct net_device *dev, struct sk_buff *skb, * struct net_device *sb_dev); * Called to decide which queue to when device supports multiple * transmit queues. */ u16 ecrnx_select_queue(struct net_device *dev, struct sk_buff *skb, struct net_device *sb_dev) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); return ecrnx_select_txq(ecrnx_vif, skb); } /** * int (*ndo_set_mac_address)(struct net_device *dev, void *addr); * This function is called when the Media Access Control address * needs to be changed. If this interface is not defined, the * mac address can not be changed. */ static int ecrnx_set_mac_address(struct net_device *dev, void *addr) { struct sockaddr *sa = addr; int ret; ret = eth_mac_addr(dev, sa); return ret; } int ecrnx_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { //struct iwreq *wrq = (struct iwreq *)rq; int ret = 0; switch (cmd) { case (SIOCDEVPRIVATE+1): //ret = ecrnx_android_priv_cmd(dev, rq, cmd); break; default: ret = -EOPNOTSUPP; break; } return ret; } static const struct net_device_ops ecrnx_netdev_ops = { .ndo_open = ecrnx_open, .ndo_stop = ecrnx_close, .ndo_start_xmit = ecrnx_start_xmit, .ndo_get_stats = ecrnx_get_stats, .ndo_select_queue = ecrnx_select_queue, .ndo_set_mac_address = ecrnx_set_mac_address, .ndo_do_ioctl = ecrnx_ioctl, // .ndo_set_features = ecrnx_set_features, // .ndo_set_rx_mode = ecrnx_set_multicast_list, }; static const struct net_device_ops ecrnx_netdev_monitor_ops = { .ndo_open = ecrnx_open, .ndo_stop = ecrnx_close, .ndo_get_stats = ecrnx_get_stats, .ndo_set_mac_address = ecrnx_set_mac_address, }; #ifdef CONFIG_WIRELESS_EXT extern const struct iw_handler_def ecrnx_wext_handler_def; #endif static void ecrnx_netdev_setup(struct net_device *dev) { ether_setup(dev); dev->priv_flags &= ~IFF_TX_SKB_SHARING; dev->netdev_ops = &ecrnx_netdev_ops; #ifdef CONFIG_WIRELESS_EXT dev->wireless_handlers = &ecrnx_wext_handler_def; #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) dev->destructor = free_netdev; #else dev->needs_free_netdev = true; #endif dev->watchdog_timeo = ECRNX_TX_LIFETIME_MS; dev->needed_headroom = ECRNX_TX_MAX_HEADROOM; #ifdef CONFIG_ECRNX_AMSDUS_TX dev->needed_headroom = max(dev->needed_headroom, (unsigned short)(sizeof(struct ecrnx_amsdu_txhdr) + sizeof(struct ethhdr) + 4 + sizeof(rfc1042_header) + 2)); #endif /* CONFIG_ECRNX_AMSDUS_TX */ dev->hw_features = 0; } /********************************************************************* * Cfg80211 callbacks (and helper) *********************************************************************/ static struct wireless_dev *ecrnx_interface_add(struct ecrnx_hw *ecrnx_hw, const char *name, unsigned char name_assign_type, enum nl80211_iftype type, struct vif_params *params) { struct net_device *ndev; struct ecrnx_vif *vif; int min_idx, max_idx; int vif_idx = -1; int i; // Look for an available VIF if (type == NL80211_IFTYPE_AP_VLAN) { min_idx = NX_VIRT_DEV_MAX; max_idx = NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX; } else { min_idx = 0; max_idx = NX_VIRT_DEV_MAX; } for (i = min_idx; i < max_idx; i++) { if ((ecrnx_hw->avail_idx_map) & BIT(i)) { vif_idx = i; break; } } if (vif_idx < 0) return NULL; #ifndef CONFIG_ECRNX_MON_DATA list_for_each_entry(vif, &ecrnx_hw->vifs, list) { // Check if monitor interface already exists or type is monitor if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) || (type == NL80211_IFTYPE_MONITOR)) { wiphy_err(ecrnx_hw->wiphy, "Monitor+Data interface support (MON_DATA) disabled\n"); return NULL; } } #endif ndev = alloc_netdev_mqs(sizeof(*vif), name, name_assign_type, ecrnx_netdev_setup, NX_NB_NDEV_TXQ, 1); if (!ndev) return NULL; vif = netdev_priv(ndev); ndev->ieee80211_ptr = &vif->wdev; vif->wdev.wiphy = ecrnx_hw->wiphy; vif->ecrnx_hw = ecrnx_hw; vif->ndev = ndev; vif->drv_vif_index = vif_idx; SET_NETDEV_DEV(ndev, wiphy_dev(vif->wdev.wiphy)); vif->wdev.netdev = ndev; vif->wdev.iftype = type; vif->up = false; vif->ch_index = ECRNX_CH_NOT_SET; vif->generation = 0; memset(&vif->net_stats, 0, sizeof(vif->net_stats)); memset(vif->rx_pn, 0, TID_MAX * sizeof(uint64_t)); switch (type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: vif->sta.flags = 0; vif->sta.ap = NULL; vif->sta.tdls_sta = NULL; vif->sta.ft_assoc_ies = NULL; vif->sta.ft_assoc_ies_len = 0; break; case NL80211_IFTYPE_MESH_POINT: INIT_LIST_HEAD(&vif->ap.mpath_list); INIT_LIST_HEAD(&vif->ap.proxy_list); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) vif->ap.mesh_pm = NL80211_MESH_POWER_ACTIVE; vif->ap.next_mesh_pm = NL80211_MESH_POWER_ACTIVE; #endif break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: INIT_LIST_HEAD(&vif->ap.sta_list); memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn)); vif->ap.flags = 0; break; case NL80211_IFTYPE_AP_VLAN: { struct ecrnx_vif *master_vif; bool found = false; list_for_each_entry(master_vif, &ecrnx_hw->vifs, list) { if ((ECRNX_VIF_TYPE(master_vif) == NL80211_IFTYPE_AP) #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) && !(!memcmp(master_vif->ndev->dev_addr, params->macaddr, ETH_ALEN)) #endif ) { found=true; break; } } if (!found) goto err; vif->ap_vlan.master = master_vif; vif->ap_vlan.sta_4a = NULL; break; } case NL80211_IFTYPE_MONITOR: ndev->type = ARPHRD_IEEE80211_RADIOTAP; ndev->netdev_ops = &ecrnx_netdev_monitor_ops; break; default: break; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (type == NL80211_IFTYPE_AP_VLAN) memcpy(ndev->dev_addr, params->macaddr, ETH_ALEN); else #endif { memcpy(ndev->dev_addr, ecrnx_hw->wiphy->perm_addr, ETH_ALEN); ndev->dev_addr[5] ^= vif_idx; } if (params) { vif->use_4addr = params->use_4addr; ndev->ieee80211_ptr->use_4addr = params->use_4addr; } else vif->use_4addr = false; if (register_netdevice(ndev)) goto err; spin_lock_bh(&ecrnx_hw->cb_lock); list_add_tail(&vif->list, &ecrnx_hw->vifs); spin_unlock_bh(&ecrnx_hw->cb_lock); ecrnx_hw->avail_idx_map &= ~BIT(vif_idx); //#if defined(CONFIG_ECRNX_ESWIN_SDIO) || defined(CONFIG_ECRNX_ESWIN_USB) init_waitqueue_head(&vif->rxdataq); //#endif return &vif->wdev; err: free_netdev(ndev); return NULL; } /* * @brief Retrieve the ecrnx_sta object allocated for a given MAC address * and a given role. */ static struct ecrnx_sta *ecrnx_retrieve_sta(struct ecrnx_hw *ecrnx_hw, struct ecrnx_vif *ecrnx_vif, u8 *addr, __le16 fc, bool ap) { if (ap) { /* only deauth, disassoc and action are bufferable MMPDUs */ bool bufferable = ieee80211_is_deauth(fc) || ieee80211_is_disassoc(fc) || ieee80211_is_action(fc); /* Check if the packet is bufferable or not */ if (bufferable) { /* Check if address is a broadcast or a multicast address */ if (is_broadcast_ether_addr(addr) || is_multicast_ether_addr(addr)) { /* Returned STA pointer */ struct ecrnx_sta *ecrnx_sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; if (ecrnx_sta->valid) return ecrnx_sta; } else { /* Returned STA pointer */ struct ecrnx_sta *ecrnx_sta; /* Go through list of STAs linked with the provided VIF */ list_for_each_entry(ecrnx_sta, &ecrnx_vif->ap.sta_list, list) { if (ecrnx_sta->valid && ether_addr_equal(ecrnx_sta->mac_addr, addr)) { /* Return the found STA */ return ecrnx_sta; } } } } } else { return ecrnx_vif->sta.ap; } return NULL; } /** * @add_virtual_intf: create a new virtual interface with the given name, * must set the struct wireless_dev's iftype. Beware: You must create * the new netdev in the wiphy's network namespace! Returns the struct * wireless_dev, or an ERR_PTR. For P2P device wdevs, the driver must * also set the address member in the wdev. */ static struct wireless_dev *ecrnx_cfg80211_add_iface(struct wiphy *wiphy, const char *name, unsigned char name_assign_type, enum nl80211_iftype type, struct vif_params *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct wireless_dev *wdev; wdev = ecrnx_interface_add(ecrnx_hw, name, name_assign_type, type, params); if (!wdev) return ERR_PTR(-EINVAL); return wdev; } /** * @del_virtual_intf: remove the virtual interface */ static int ecrnx_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev) { struct net_device *dev = wdev->netdev; struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); netdev_info(dev, "Remove Interface"); if (dev->reg_state == NETREG_REGISTERED) { ECRNX_DBG("%s-%d:unregister_netdevice \n", __func__, __LINE__); /* Will call ecrnx_close if interface is UP */ unregister_netdevice(dev); } spin_lock_bh(&ecrnx_hw->cb_lock); list_del(&ecrnx_vif->list); spin_unlock_bh(&ecrnx_hw->cb_lock); ecrnx_hw->avail_idx_map |= BIT(ecrnx_vif->drv_vif_index); ecrnx_vif->ndev = NULL; /* Clear the priv in adapter */ dev->ieee80211_ptr = NULL; return 0; } static int ecrnx_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev); static int ecrnx_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, u16 reason_code); /** * @change_virtual_intf: change type/configuration of virtual interface, * keep the struct wireless_dev's iftype updated. */ static int ecrnx_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *dev, enum nl80211_iftype type, struct vif_params *params) { #ifndef CONFIG_ECRNX_MON_DATA struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); #endif struct ecrnx_vif *vif = netdev_priv(dev); ECRNX_PRINT("%s:dev:0x%p, type:%d, vif->up:%d \n", __func__, dev, type, vif->up); if (vif->up) { if((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_GO)) { ecrnx_cfg80211_stop_ap(wiphy, dev); } else if((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { ecrnx_cfg80211_disconnect(wiphy, dev, WLAN_REASON_DEAUTH_LEAVING); } ECRNX_ERR("ecrnx_cfg80211_change_iface: -EBUSY \n"); ecrnx_close(dev); //return (-EBUSY); } #ifndef CONFIG_ECRNX_MON_DATA if ((type == NL80211_IFTYPE_MONITOR) && (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR)) { struct ecrnx_vif *vif_el; list_for_each_entry(vif_el, &ecrnx_hw->vifs, list) { // Check if data interface already exists if ((vif_el != vif) && (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MONITOR)) { wiphy_err(ecrnx_hw->wiphy, "Monitor+Data interface support (MON_DATA) disabled\n"); return -EIO; } } } #endif // Reset to default case (i.e. not monitor) dev->type = ARPHRD_ETHER; dev->netdev_ops = &ecrnx_netdev_ops; switch (type) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: vif->sta.flags = 0; vif->sta.ap = NULL; vif->sta.tdls_sta = NULL; vif->sta.ft_assoc_ies = NULL; vif->sta.ft_assoc_ies_len = 0; break; case NL80211_IFTYPE_MESH_POINT: INIT_LIST_HEAD(&vif->ap.mpath_list); INIT_LIST_HEAD(&vif->ap.proxy_list); break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: INIT_LIST_HEAD(&vif->ap.sta_list); memset(&vif->ap.bcn, 0, sizeof(vif->ap.bcn)); vif->ap.flags = 0; break; case NL80211_IFTYPE_AP_VLAN: return -EPERM; case NL80211_IFTYPE_MONITOR: dev->type = ARPHRD_IEEE80211_RADIOTAP; dev->netdev_ops = &ecrnx_netdev_monitor_ops; break; default: break; } vif->generation = 0; vif->wdev.iftype = type; if (params->use_4addr != -1) vif->use_4addr = params->use_4addr; if (!vif->up) { ecrnx_open(dev); } return 0; } /* * GavinGao * Used as P2P_DEVICE mode */ static int ecrnx_cfg80211_start_p2p_device(struct wiphy *wiphy, struct wireless_dev *wdev) { ECRNX_PRINT("rwnx_cfg80211_start_p2p_device\n"); return 0; } static void ecrnx_cfg80211_stop_p2p_device(struct wiphy *wiphy, struct wireless_dev *wdev) { //TODO } /* Used as P2P_DEVICE mode*/ /** * @scan: Request to do a scan. If returning zero, the scan request is given * the driver, and will be valid until passed to cfg80211_scan_done(). * For scan results, call cfg80211_inform_bss(); you can call this outside * the scan/scan_done bracket too. */ static int ecrnx_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = container_of(request->wdev, struct ecrnx_vif, wdev); int error; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if(!ecrnx_hw->scan_request){ ecrnx_hw->scan_request = request; ECRNX_PRINT("%s:scan_request:0x%p \n", __func__, request); if ((error = ecrnx_send_scanu_req(ecrnx_hw, ecrnx_vif, request))){ ECRNX_PRINT("scan message send error!!\n"); ecrnx_hw->scan_request = NULL; return error; } }else{ ECRNX_PRINT("scan is already running!!\n"); } ECRNX_DBG("send finish:ecrnx_cfg80211_scan \n"); return 0; } static void ecrnx_cfg80211_abort_scan(struct wiphy *wiphy, struct wireless_dev *wdev) { u8_l ret = 0; struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = container_of(wdev, struct ecrnx_vif, wdev); //mutex_lock(&ecrnx_hw->mutex); ECRNX_PRINT("%s:ecrnx_hw->scan_request:0x%p \n", __func__, ecrnx_hw->scan_request); if(!ecrnx_hw->scan_request){ ECRNX_ERR("no scan is running, don't need abort! \n"); goto out; } if(wdev->iftype != NL80211_IFTYPE_STATION){ ECRNX_ERR("abort scan ignored, iftype(%d)\n", wdev->iftype); goto out; } if(wdev != ecrnx_hw->scan_request->wdev){ ECRNX_ERR("abort scan was called on the wrong iface\n"); goto out; } ret = ecrnx_send_scanu_cancel_req(ecrnx_hw, ecrnx_vif); out: //mutex_unlock(&ecrnx_hw->mutex); return; } /** * @add_key: add a key with the given parameters. @mac_addr will be %NULL * when adding a group key. */ static int ecrnx_cfg80211_add_key(struct wiphy *wiphy, struct net_device *netdev, u8 key_index, bool pairwise, const u8 *mac_addr, struct key_params *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(netdev); int i, error = 0; struct mm_key_add_cfm key_add_cfm; u8_l cipher = 0; struct ecrnx_sta *sta = NULL; struct ecrnx_key *ecrnx_key; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (mac_addr) { sta = ecrnx_get_sta(ecrnx_hw, mac_addr); if (!sta) return -EINVAL; ecrnx_key = &sta->key; } else ecrnx_key = &vif->key[key_index]; /* Retrieve the cipher suite selector */ switch (params->cipher) { case WLAN_CIPHER_SUITE_WEP40: cipher = MAC_CIPHER_WEP40; break; case WLAN_CIPHER_SUITE_WEP104: cipher = MAC_CIPHER_WEP104; break; case WLAN_CIPHER_SUITE_TKIP: cipher = MAC_CIPHER_TKIP; break; case WLAN_CIPHER_SUITE_CCMP: cipher = MAC_CIPHER_CCMP; break; case WLAN_CIPHER_SUITE_AES_CMAC: cipher = MAC_CIPHER_BIP_CMAC_128; break; case WLAN_CIPHER_SUITE_SMS4: { // Need to reverse key order u8 tmp, *key = (u8 *)params->key; cipher = MAC_CIPHER_WPI_SMS4; for (i = 0; i < WPI_SUBKEY_LEN/2; i++) { tmp = key[i]; key[i] = key[WPI_SUBKEY_LEN - 1 - i]; key[WPI_SUBKEY_LEN - 1 - i] = tmp; } for (i = 0; i < WPI_SUBKEY_LEN/2; i++) { tmp = key[i + WPI_SUBKEY_LEN]; key[i + WPI_SUBKEY_LEN] = key[WPI_KEY_LEN - 1 - i]; key[WPI_KEY_LEN - 1 - i] = tmp; } break; } case WLAN_CIPHER_SUITE_GCMP: cipher = MAC_CIPHER_GCMP_128; break; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) case WLAN_CIPHER_SUITE_GCMP_256: cipher = MAC_CIPHER_GCMP_256; break; case WLAN_CIPHER_SUITE_CCMP_256: cipher = MAC_CIPHER_CCMP_256; break; #endif default: return -EINVAL; } if ((error = ecrnx_send_key_add(ecrnx_hw, vif->vif_index, (sta ? sta->sta_idx : 0xFF), pairwise, (u8 *)params->key, params->key_len, key_index, cipher, &key_add_cfm))) return error; if (key_add_cfm.status != 0) { ECRNX_PRINT_CFM_ERR(key_add); return -EIO; } /* Save the index retrieved from LMAC */ ecrnx_key->hw_idx = key_add_cfm.hw_key_idx; return 0; } /** * @get_key: get information about the key with the given parameters. * @mac_addr will be %NULL when requesting information for a group * key. All pointers given to the @callback function need not be valid * after it returns. This function should return an error if it is * not possible to retrieve the key, -ENOENT if it doesn't exist. * */ static int ecrnx_cfg80211_get_key(struct wiphy *wiphy, struct net_device *netdev, u8 key_index, bool pairwise, const u8 *mac_addr, void *cookie, void (*callback)(void *cookie, struct key_params*)) { ECRNX_DBG(ECRNX_FN_ENTRY_STR); return -1; } /** * @del_key: remove a key given the @mac_addr (%NULL for a group key) * and @key_index, return -ENOENT if the key doesn't exist. */ static int ecrnx_cfg80211_del_key(struct wiphy *wiphy, struct net_device *netdev, u8 key_index, bool pairwise, const u8 *mac_addr) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(netdev); int error; struct ecrnx_sta *sta = NULL; struct ecrnx_key *ecrnx_key; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (mac_addr) { sta = ecrnx_get_sta(ecrnx_hw, mac_addr); if (!sta) return -EINVAL; ecrnx_key = &sta->key; } else ecrnx_key = &vif->key[key_index]; error = ecrnx_send_key_del(ecrnx_hw, ecrnx_key->hw_idx); return error; } /** * @set_default_key: set the default key on an interface */ static int ecrnx_cfg80211_set_default_key(struct wiphy *wiphy, struct net_device *netdev, u8 key_index, bool unicast, bool multicast) { ECRNX_DBG(ECRNX_FN_ENTRY_STR); return 0; } /** * @set_default_mgmt_key: set the default management frame key on an interface */ static int ecrnx_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, struct net_device *netdev, u8 key_index) { return 0; } /** * @connect: Connect to the ESS with the specified parameters. When connected, * call cfg80211_connect_result() with status code %WLAN_STATUS_SUCCESS. * If the connection fails for some reason, call cfg80211_connect_result() * with the status from the AP. * (invoked with the wireless_dev mutex held) */ static int ecrnx_cfg80211_connect(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_connect_params *sme) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct sm_connect_cfm sm_connect_cfm; int error = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); /* For SHARED-KEY authentication, must install key first */ if (sme->auth_type == NL80211_AUTHTYPE_SHARED_KEY && sme->key) { struct key_params key_params; key_params.key = sme->key; key_params.seq = NULL; key_params.key_len = sme->key_len; key_params.seq_len = 0; key_params.cipher = sme->crypto.cipher_group; ecrnx_cfg80211_add_key(wiphy, dev, sme->key_idx, false, NULL, &key_params); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) else if ((sme->auth_type == NL80211_AUTHTYPE_SAE) && !(sme->flags & CONNECT_REQ_EXTERNAL_AUTH_SUPPORT)) { netdev_err(dev, "Doesn't support SAE without external authentication\n"); return -EINVAL; } #endif /* Forward the information to the LMAC */ if ((error = ecrnx_send_sm_connect_req(ecrnx_hw, ecrnx_vif, sme, &sm_connect_cfm))) return error; ECRNX_PRINT("%s:bssid:%pM, send status:%d\n", __func__, sme->bssid, sm_connect_cfm.status); // Check the status switch (sm_connect_cfm.status) { case CO_OK: ecrnx_save_assoc_info_for_ft(ecrnx_vif, sme); error = 0; break; case CO_BUSY: error = -EINPROGRESS; break; case CO_BAD_PARAM: error = -EINVAL; break; case CO_OP_IN_PROGRESS: error = -EALREADY; break; default: error = -EIO; break; } return error; } /** * @disconnect: Disconnect from the BSS/ESS. * (invoked with the wireless_dev mutex held) */ static int ecrnx_cfg80211_disconnect(struct wiphy *wiphy, struct net_device *dev, u16 reason_code) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); ECRNX_PRINT("%s:dev:0x%p, vif_index:%d, reason_code:%d \n", __func__, dev, ecrnx_vif->vif_index, reason_code); return(ecrnx_send_sm_disconnect_req(ecrnx_hw, ecrnx_vif, reason_code)); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) /** * @external_auth: indicates result of offloaded authentication processing from * user space */ static int ecrnx_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_external_auth_params *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); if (!(ecrnx_vif->sta.flags & ECRNX_STA_EXT_AUTH)) return -EINVAL; ecrnx_external_auth_disable(ecrnx_vif); return ecrnx_send_sm_external_auth_required_rsp(ecrnx_hw, ecrnx_vif, params->status); } #endif /** * @add_station: Add a new station. */ static int ecrnx_cfg80211_add_station(struct wiphy *wiphy, struct net_device *dev, const u8 *mac, struct station_parameters *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct me_sta_add_cfm me_sta_add_cfm; int error = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); WARN_ON(ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP_VLAN); /* Do not add TDLS station */ if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) return 0; /* Indicate we are in a STA addition process - This will allow handling * potential PS mode change indications correctly */ set_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); /* Forward the information to the LMAC */ if ((error = ecrnx_send_me_sta_add(ecrnx_hw, params, mac, ecrnx_vif->vif_index, &me_sta_add_cfm))) return error; // Check the status switch (me_sta_add_cfm.status) { case CO_OK: { struct ecrnx_sta *sta = &ecrnx_hw->sta_table[me_sta_add_cfm.sta_idx]; ECRNX_PRINT("%s-%d:sta:0x%p, sta_idx:%d \n", __func__, __LINE__, sta, me_sta_add_cfm.sta_idx); int tid; sta->aid = params->aid; memset(sta->rx_pn, 0, TID_MAX * sizeof(uint64_t)); sta->sta_idx = me_sta_add_cfm.sta_idx; sta->ch_idx = ecrnx_vif->ch_index; sta->vif_idx = ecrnx_vif->vif_index; sta->vlan_idx = sta->vif_idx; sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0; sta->ht = params->ht_capa ? 1 : 0; sta->vht = params->vht_capa ? 1 : 0; sta->acm = 0; sta->listen_interval = params->listen_interval; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (params->local_pm != NL80211_MESH_POWER_UNKNOWN) sta->mesh_pm = params->local_pm; else sta->mesh_pm = ecrnx_vif->ap.next_mesh_pm; #endif ecrnx_update_mesh_power_mode(ecrnx_vif); for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) { int uapsd_bit = ecrnx_hwq2uapsd[ecrnx_tid2hwq[tid]]; if (params->uapsd_queues & uapsd_bit) sta->uapsd_tids |= 1 << tid; else sta->uapsd_tids &= ~(1 << tid); } memcpy(sta->mac_addr, mac, ETH_ALEN); ecrnx_dbgfs_register_sta(ecrnx_hw, sta); /* Ensure that we won't process PS change or channel switch ind*/ spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_txq_sta_init(ecrnx_hw, sta, ecrnx_txq_vif_get_status(ecrnx_vif)); ecrnx_rx_reord_sta_init(ecrnx_hw, ecrnx_vif, sta->sta_idx); list_add_tail(&sta->list, &ecrnx_vif->ap.sta_list); ecrnx_vif->generation++; sta->valid = true; ecrnx_ps_bh_enable(ecrnx_hw, sta, sta->ps.active || me_sta_add_cfm.pm_state); spin_unlock_bh(&ecrnx_hw->cb_lock); #ifdef CONFIG_ECRNX_ANDRIOD if((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) { struct station_info sinfo; u8 ie_offset; if((!is_multicast_sta(sta->sta_idx)) && (sta->mac_addr)) { memset(&sinfo, 0, sizeof(sinfo)); #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) sinfo.filled = STATION_INFO_ASSOC_REQ_IES; #endif sinfo.assoc_req_ies = NULL; sinfo.assoc_req_ies_len = 0; ECRNX_PRINT("%s-%d:sta:0x%x,sta->mac_addr:%pM \n", __func__, __LINE__, sta, sta->mac_addr); cfg80211_new_sta(ecrnx_vif->ndev, sta->mac_addr, &sinfo, GFP_ATOMIC); } } #endif error = 0; #ifdef CONFIG_ECRNX_BFMER if (ecrnx_hw->mod_params->bfmer) ecrnx_send_bfmer_enable(ecrnx_hw, sta, params->vht_capa); ecrnx_mu_group_sta_init(sta, params->vht_capa); #endif /* CONFIG_ECRNX_BFMER */ #define PRINT_STA_FLAG(f) \ (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "") #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s%s", sta->sta_idx, mac, PRINT_STA_FLAG(AUTHORIZED), PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME), PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED), PRINT_STA_FLAG(TDLS_PEER), PRINT_STA_FLAG(ASSOCIATED)); #else netdev_info(dev, "Add sta %d (%pM) flags=%s%s%s%s%s%s", sta->sta_idx, mac, PRINT_STA_FLAG(AUTHORIZED), PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME), PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED), PRINT_STA_FLAG(TDLS_PEER)); #endif #undef PRINT_STA_FLAG #if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) ecrnx_debugfs_add_station_in_ap_mode(ecrnx_hw, sta, params); #endif break; } default: error = -EBUSY; break; } clear_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); return error; } /** * @del_station: Remove a station */ static int ecrnx_cfg80211_del_station(struct wiphy *wiphy, struct net_device *dev, #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) u8 *mac #elif (LINUX_VERSION_CODE < KERNEL_VERSION(3, 19, 0)) const u8 *mac #else struct station_del_parameters *params #endif ) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_sta *cur, *tmp; int error = 0, found = 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) const u8 *mac = NULL; if (params) mac = params->mac; #endif if(list_empty(&ecrnx_vif->ap.sta_list)) { goto end; } list_for_each_entry_safe(cur, tmp, &ecrnx_vif->ap.sta_list, list) { if ((!mac) || (!memcmp(cur->mac_addr, mac, ETH_ALEN))) { netdev_info(dev, "Del sta %d (%pM)", cur->sta_idx, cur->mac_addr); ECRNX_PRINT("%s-%d:cur_list:0x%p, vif_list:0x%p, mac:%pM\n", __func__, __LINE__, &cur->list, &ecrnx_vif->ap.sta_list, mac); /* Ensure that we won't process PS change ind */ spin_lock_bh(&ecrnx_hw->cb_lock); cur->ps.active = false; cur->valid = false; spin_unlock_bh(&ecrnx_hw->cb_lock); if (cur->vif_idx != cur->vlan_idx) { struct ecrnx_vif *vlan_vif; vlan_vif = ecrnx_hw->vif_table[cur->vlan_idx]; if (vlan_vif->up) { if ((ECRNX_VIF_TYPE(vlan_vif) == NL80211_IFTYPE_AP_VLAN) && (vlan_vif->use_4addr)) { vlan_vif->ap_vlan.sta_4a = NULL; } else { WARN(1, "Deleting sta belonging to VLAN other than AP_VLAN 4A"); } } } ecrnx_txq_sta_deinit(ecrnx_hw, cur); ecrnx_rx_reord_sta_deinit(ecrnx_hw, cur->sta_idx, true); error = ecrnx_send_me_sta_del(ecrnx_hw, cur->sta_idx, false); if ((error != 0) && (error != -EPIPE)){ ECRNX_WARN("del sta msg send fail, error code:%d \n", error); } #ifdef CONFIG_ECRNX_ANDRIOD if((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) { if((!is_multicast_sta(cur->sta_idx)) && params) { if(params->mac){ ECRNX_PRINT("%s-%d:vif:%d, mac:%pM \n", __func__, __LINE__, ECRNX_VIF_TYPE(ecrnx_vif), params->mac); cfg80211_del_sta(ecrnx_vif->ndev, params->mac, GFP_ATOMIC); } } } #endif #ifdef CONFIG_ECRNX_BFMER // Disable Beamformer if supported ecrnx_bfmer_report_del(ecrnx_hw, cur); ecrnx_mu_group_sta_del(ecrnx_hw, cur); #endif /* CONFIG_ECRNX_BFMER */ list_del(&cur->list); ecrnx_vif->generation++; ecrnx_dbgfs_unregister_sta(ecrnx_hw, cur); #if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) ecrnx_debugfs_sta_in_ap_del(cur->sta_idx); #endif found ++; break; } } end: if ((!found) && (mac)) return -ENOENT; ecrnx_update_mesh_power_mode(ecrnx_vif); return 0; } /** * @change_station: Modify a given station. Note that flags changes are not much * validated in cfg80211, in particular the auth/assoc/authorized flags * might come to the driver in invalid combinations -- make sure to check * them, also against the existing state! Drivers must call * cfg80211_check_station_change() to validate the information. */ static int ecrnx_cfg80211_change_station(struct wiphy *wiphy, struct net_device *dev, const u8 *mac, struct station_parameters *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_sta *sta; sta = ecrnx_get_sta(ecrnx_hw, mac); if (!sta) { /* Add the TDLS station */ if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct me_sta_add_cfm me_sta_add_cfm; int error = 0; /* Indicate we are in a STA addition process - This will allow handling * potential PS mode change indications correctly */ set_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); /* Forward the information to the LMAC */ if ((error = ecrnx_send_me_sta_add(ecrnx_hw, params, mac, ecrnx_vif->vif_index, &me_sta_add_cfm))) return error; // Check the status switch (me_sta_add_cfm.status) { case CO_OK: { int tid; sta = &ecrnx_hw->sta_table[me_sta_add_cfm.sta_idx]; memset(&sta->rx_pn, 0, TID_MAX * sizeof(uint64_t)); sta->aid = params->aid; sta->sta_idx = me_sta_add_cfm.sta_idx; sta->ch_idx = ecrnx_vif->ch_index; sta->vif_idx = ecrnx_vif->vif_index; sta->vlan_idx = sta->vif_idx; sta->qos = (params->sta_flags_set & BIT(NL80211_STA_FLAG_WME)) != 0; sta->ht = params->ht_capa ? 1 : 0; sta->vht = params->vht_capa ? 1 : 0; sta->acm = 0; for (tid = 0; tid < NX_NB_TXQ_PER_STA; tid++) { int uapsd_bit = ecrnx_hwq2uapsd[ecrnx_tid2hwq[tid]]; if (params->uapsd_queues & uapsd_bit) sta->uapsd_tids |= 1 << tid; else sta->uapsd_tids &= ~(1 << tid); } memcpy(sta->mac_addr, mac, ETH_ALEN); ecrnx_dbgfs_register_sta(ecrnx_hw, sta); /* Ensure that we won't process PS change or channel switch ind*/ spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_txq_sta_init(ecrnx_hw, sta, ecrnx_txq_vif_get_status(ecrnx_vif)); ecrnx_rx_reord_sta_init(ecrnx_hw, ecrnx_vif, sta->sta_idx); if (ecrnx_vif->tdls_status == TDLS_SETUP_RSP_TX) { ecrnx_vif->tdls_status = TDLS_LINK_ACTIVE; sta->tdls.initiator = true; sta->tdls.active = true; } /* Set TDLS channel switch capability */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if ((params->ext_capab[3] & WLAN_EXT_CAPA4_TDLS_CHAN_SWITCH) && !ecrnx_vif->tdls_chsw_prohibited) #else if (!ecrnx_vif->tdls_chsw_prohibited) #endif sta->tdls.chsw_allowed = true; ecrnx_vif->sta.tdls_sta = sta; sta->valid = true; spin_unlock_bh(&ecrnx_hw->cb_lock); #ifdef CONFIG_ECRNX_BFMER if (ecrnx_hw->mod_params->bfmer) ecrnx_send_bfmer_enable(ecrnx_hw, sta, params->vht_capa); ecrnx_mu_group_sta_init(sta, NULL); #endif /* CONFIG_ECRNX_BFMER */ #define PRINT_STA_FLAG(f) \ (params->sta_flags_set & BIT(NL80211_STA_FLAG_##f) ? "["#f"]" : "") #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) netdev_info(dev, "Add %s TDLS sta %d (%pM) flags=%s%s%s%s%s%s%s", sta->tdls.initiator ? "initiator" : "responder", sta->sta_idx, mac, PRINT_STA_FLAG(AUTHORIZED), PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME), PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED), PRINT_STA_FLAG(TDLS_PEER), PRINT_STA_FLAG(ASSOCIATED)); #else netdev_info(dev, "Add %s TDLS sta %d (%pM) flags=%s%s%s%s%s%s", sta->tdls.initiator ? "initiator" : "responder", sta->sta_idx, mac, PRINT_STA_FLAG(AUTHORIZED), PRINT_STA_FLAG(SHORT_PREAMBLE), PRINT_STA_FLAG(WME), PRINT_STA_FLAG(MFP), PRINT_STA_FLAG(AUTHENTICATED), PRINT_STA_FLAG(TDLS_PEER)); #endif #undef PRINT_STA_FLAG break; } default: error = -EBUSY; break; } clear_bit(ECRNX_DEV_ADDING_STA, &ecrnx_hw->flags); } else { return -EINVAL; } } if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)) ecrnx_send_me_set_control_port_req(ecrnx_hw, (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) != 0, sta->sta_idx); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MESH_POINT) { if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) { if (params->plink_state < NUM_NL80211_PLINK_STATES) { ecrnx_send_mesh_peer_update_ntf(ecrnx_hw, vif, sta->sta_idx, params->plink_state); } } if (params->local_pm != NL80211_MESH_POWER_UNKNOWN) { sta->mesh_pm = params->local_pm; ecrnx_update_mesh_power_mode(vif); } } #endif if (params->vlan) { uint8_t vlan_idx; vif = netdev_priv(params->vlan); vlan_idx = vif->vif_index; if (sta->vlan_idx != vlan_idx) { struct ecrnx_vif *old_vif; old_vif = ecrnx_hw->vif_table[sta->vlan_idx]; ecrnx_txq_sta_switch_vif(sta, old_vif, vif); sta->vlan_idx = vlan_idx; if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_AP_VLAN) && (vif->use_4addr)) { WARN((vif->ap_vlan.sta_4a), "4A AP_VLAN interface with more than one sta"); vif->ap_vlan.sta_4a = sta; } if ((ECRNX_VIF_TYPE(old_vif) == NL80211_IFTYPE_AP_VLAN) && (old_vif->use_4addr)) { old_vif->ap_vlan.sta_4a = NULL; } } } return 0; } /** * @start_ap: Start acting in AP mode defined by the parameters. */ static int ecrnx_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_ap_settings *settings) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct apm_start_cfm apm_start_cfm; struct ecrnx_ipc_elem_var elem; struct ecrnx_sta *sta; int error = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); /* Forward the information to the LMAC */ if ((error = ecrnx_send_apm_start_req(ecrnx_hw, ecrnx_vif, settings, &apm_start_cfm, &elem))) goto end; // Check the status switch (apm_start_cfm.status) { case CO_OK: { u8 txq_status = 0; ecrnx_vif->ap.bcmc_index = apm_start_cfm.bcmc_idx; ecrnx_vif->ap.flags = 0; ecrnx_vif->ap.bcn_interval = settings->beacon_interval; sta = &ecrnx_hw->sta_table[apm_start_cfm.bcmc_idx]; sta->valid = true; sta->aid = 0; sta->sta_idx = apm_start_cfm.bcmc_idx; sta->ch_idx = apm_start_cfm.ch_idx; sta->vif_idx = ecrnx_vif->vif_index; sta->qos = false; sta->acm = 0; sta->ps.active = false; sta->listen_interval = 5; ecrnx_mu_group_sta_init(sta, NULL); spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_link(ecrnx_vif, apm_start_cfm.ch_idx, &settings->chandef); if (ecrnx_hw->cur_chanctx != apm_start_cfm.ch_idx) { txq_status = ECRNX_TXQ_STOP_CHAN; } ecrnx_txq_vif_init(ecrnx_hw, ecrnx_vif, txq_status); spin_unlock_bh(&ecrnx_hw->cb_lock); netif_tx_start_all_queues(dev); netif_carrier_on(dev); error = 0; /* If the AP channel is already the active, we probably skip radar activation on MM_CHANNEL_SWITCH_IND (unless another vif use this ctxt). In anycase retest if radar detection must be activated */ if (txq_status == 0) { ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); } break; } case CO_BUSY: error = -EINPROGRESS; break; case CO_OP_IN_PROGRESS: error = -EALREADY; break; default: error = -EIO; break; } if (error) { netdev_info(dev, "Failed to start AP (%d)", error); } else { netdev_info(dev, "AP started: ch=%d, bcmc_idx=%d", ecrnx_vif->ch_index, ecrnx_vif->ap.bcmc_index); } end: ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); return error; } /** * @change_beacon: Change the beacon parameters for an access point mode * interface. This should reject the call when AP mode wasn't started. */ static int ecrnx_cfg80211_change_beacon(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_beacon_data *info) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_bcn *bcn = &vif->ap.bcn; struct ecrnx_ipc_elem_var elem; u8 *buf; int error = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); // Build the beacon buf = ecrnx_build_bcn(bcn, info); if (!buf) return -ENOMEM; // Sync buffer for FW if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &elem, bcn->len, DMA_TO_DEVICE, buf, NULL, NULL))) return error; // Forward the information to the LMAC error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, elem.dma_addr, bcn->len, bcn->head_len, bcn->tim_len, NULL); ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); return error; } /** * * @stop_ap: Stop being an AP, including stopping beaconing. */ static int ecrnx_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_sta *sta; ecrnx_radar_cancel_cac(&ecrnx_hw->radar); ecrnx_send_apm_stop_req(ecrnx_hw, ecrnx_vif); spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_unlink(ecrnx_vif); spin_unlock_bh(&ecrnx_hw->cb_lock); /* delete any remaining STA*/ while (!list_empty(&ecrnx_vif->ap.sta_list)) { ecrnx_cfg80211_del_station(wiphy, dev, NULL); } /* delete BC/MC STA */ sta = &ecrnx_hw->sta_table[ecrnx_vif->ap.bcmc_index]; ecrnx_txq_vif_deinit(ecrnx_hw, ecrnx_vif); ecrnx_del_bcn(&ecrnx_vif->ap.bcn); ecrnx_del_csa(ecrnx_vif); netif_tx_stop_all_queues(dev); netif_carrier_off(dev); netdev_info(dev, "AP Stopped"); return 0; } /** * @set_monitor_channel: Set the monitor mode channel for the device. If other * interfaces are active this callback should reject the configuration. * If no interfaces are active or the device is down, the channel should * be stored for when a monitor interface becomes active. * * Also called internaly with chandef set to NULL simply to retrieve the channel * configured at firmware level. */ static int ecrnx_cfg80211_set_monitor_channel(struct wiphy *wiphy, struct cfg80211_chan_def *chandef) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif; struct me_config_monitor_cfm cfm; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (ecrnx_hw->monitor_vif == ECRNX_INVALID_VIF) return -EINVAL; ecrnx_vif = ecrnx_hw->vif_table[ecrnx_hw->monitor_vif]; // Do nothing if monitor interface is already configured with the requested channel if (ecrnx_chanctx_valid(ecrnx_hw, ecrnx_vif->ch_index)) { struct ecrnx_chanctx *ctxt; ctxt = &ecrnx_vif->ecrnx_hw->chanctx_table[ecrnx_vif->ch_index]; if (chandef && cfg80211_chandef_identical(&ctxt->chan_def, chandef)) return 0; } // Always send command to firmware. It allows to retrieve channel context index // and its configuration. if (ecrnx_send_config_monitor_req(ecrnx_hw, chandef, &cfm)) return -EIO; // Always re-set channel context info ecrnx_chanctx_unlink(ecrnx_vif); // If there is also a STA interface not yet connected then monitor interface // will only have a channel context after the connection of the STA interface. if (cfm.chan_index != ECRNX_CH_NOT_SET) { struct cfg80211_chan_def mon_chandef; if (ecrnx_hw->vif_started > 1) { // In this case we just want to update the channel context index not // the channel configuration ecrnx_chanctx_link(ecrnx_vif, cfm.chan_index, NULL); return -EBUSY; } mon_chandef.chan = ieee80211_get_channel(wiphy, cfm.chan.prim20_freq); mon_chandef.center_freq1 = cfm.chan.center1_freq; mon_chandef.center_freq2 = cfm.chan.center2_freq; mon_chandef.width = chnl2bw[cfm.chan.type]; ecrnx_chanctx_link(ecrnx_vif, cfm.chan_index, &mon_chandef); } return 0; } /** * @probe_client: probe an associated client, must return a cookie that it * later passes to cfg80211_probe_status(). */ int ecrnx_cfg80211_probe_client(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, u64 *cookie) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_sta *sta = NULL; struct apm_probe_client_cfm cfm; if ((ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP) && (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_AP_VLAN) && (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_P2P_GO) && (ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_MESH_POINT)) return -EINVAL; list_for_each_entry(sta, &vif->ap.sta_list, list) { if (sta->valid && ether_addr_equal(sta->mac_addr, peer)) break; } if (!sta) return -EINVAL; ecrnx_send_apm_probe_req(ecrnx_hw, vif, sta, &cfm); if (cfm.status != CO_OK) return -EINVAL; *cookie = (u64)cfm.probe_id; return 0; } /** * @set_wiphy_params: Notify that wiphy parameters have changed; * @changed bitfield (see &enum wiphy_params_flags) describes which values * have changed. The actual parameter values are available in * struct wiphy. If returning an error, no value should be changed. */ static int ecrnx_cfg80211_set_wiphy_params(struct wiphy *wiphy, u32 changed) { return 0; } /** * @set_tx_power: set the transmit power according to the parameters, * the power passed is in mBm, to get dBm use MBM_TO_DBM(). The * wdev may be %NULL if power was set for the wiphy, and will * always be %NULL unless the driver supports per-vif TX power * (as advertised by the nl80211 feature flag.) */ static int ecrnx_cfg80211_set_tx_power(struct wiphy *wiphy, struct wireless_dev *wdev, enum nl80211_tx_power_setting type, int mbm) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif; s8 pwr; int res = 0; if (type == NL80211_TX_POWER_AUTOMATIC) { pwr = 0x7f; } else { pwr = MBM_TO_DBM(mbm); } if (wdev) { vif = container_of(wdev, struct ecrnx_vif, wdev); res = ecrnx_send_set_power(ecrnx_hw, vif->vif_index, pwr, NULL); } else { list_for_each_entry(vif, &ecrnx_hw->vifs, list) { res = ecrnx_send_set_power(ecrnx_hw, vif->vif_index, pwr, NULL); if (res) break; } } return res; } /** * @set_power_mgmt: set the power save to one of those two modes: * Power-save off * Power-save on - Dynamic mode */ static int ecrnx_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, bool enabled, int timeout) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); u8 ps_mode; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (timeout >= 0) netdev_info(dev, "Ignore timeout value %d", timeout); if (!(ecrnx_hw->version_cfm.features & BIT(MM_FEAT_PS_BIT))) enabled = false; if (enabled) { /* Switch to Dynamic Power Save */ ps_mode = MM_PS_MODE_ON_DYN; } else { /* Exit Power Save */ ps_mode = MM_PS_MODE_OFF; } return ecrnx_send_me_set_ps_mode(ecrnx_hw, ps_mode); } static int ecrnx_cfg80211_set_txq_params(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_txq_params *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); u8 hw_queue, aifs, cwmin, cwmax; u32 param; ECRNX_DBG(ECRNX_FN_ENTRY_STR); hw_queue = ecrnx_ac2hwq[0][params->ac]; aifs = params->aifs; cwmin = fls(params->cwmin); cwmax = fls(params->cwmax); /* Store queue information in general structure */ param = (u32) (aifs << 0); param |= (u32) (cwmin << 4); param |= (u32) (cwmax << 8); param |= (u32) (params->txop) << 12; /* Send the MM_SET_EDCA_REQ message to the FW */ return ecrnx_send_set_edca(ecrnx_hw, hw_queue, param, false, ecrnx_vif->vif_index); } /** * @remain_on_channel: Request the driver to remain awake on the specified * channel for the specified duration to complete an off-channel * operation (e.g., public action frame exchange). When the driver is * ready on the requested channel, it must indicate this with an event * notification by calling cfg80211_ready_on_channel(). */ static int ecrnx_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, u64 *cookie) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); struct ecrnx_roc *roc; int error; unsigned long timer = 0; ECRNX_DBG(ECRNX_FN_ENTRY_STR); /* For debug purpose (use ftrace kernel option) */ trace_roc(ecrnx_vif->vif_index, chan->center_freq, duration); /* Check that no other RoC procedure has been launched */ if (ecrnx_hw->roc) { ECRNX_ERR("%s-%d,statu error!!!, duration:%d \n", __func__, __LINE__, duration); //exp_report(ecrnx_hw); //wait for slave confirm //ecrnx_hw->p2p_listen.rxdatas = 0; #ifdef CONFIG_ECRNX_P2P timer = wait_event_interruptible_timeout(ecrnx_hw->p2p_listen.rxdataq, (ecrnx_hw->roc == NULL), HZ/2); if (timer) ECRNX_PRINT("wait_event: wake up!!! timer:%ld \n", timer); else ECRNX_PRINT("wait_event: timout!!!\n"); #endif //return -EBUSY; } /* Allocate a temporary RoC element */ roc = kmalloc(sizeof(struct ecrnx_roc), GFP_KERNEL); /* Verify that element has well been allocated */ if (!roc) return -ENOMEM; /* Initialize the RoC information element */ roc->vif = ecrnx_vif; roc->chan = chan; roc->duration = duration; roc->internal = false; roc->on_chan = false; /* Initialize the OFFCHAN TX queue to allow off-channel transmissions */ ecrnx_txq_offchan_init(ecrnx_vif); /* Forward the information to the FMAC */ ecrnx_hw->roc = roc; error = ecrnx_send_roc(ecrnx_hw, ecrnx_vif, chan, duration); /* If no error, keep all the information for handling of end of procedure */ if (error == 0) { /* Set the cookie value */ *cookie = (u64)(ecrnx_hw->roc_cookie); #ifdef CONFIG_ECRNX_P2P if(ecrnx_vif->mgmt_reg_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4)) { if(ecrnx_send_p2p_start_listen_req(ecrnx_hw, ecrnx_vif, duration)) ECRNX_ERR("P2P: start_listen failed\n"); } #endif } else { kfree(roc); ecrnx_hw->roc = NULL; ecrnx_txq_offchan_deinit(ecrnx_vif); } return error; } /** * @cancel_remain_on_channel: Cancel an on-going remain-on-channel operation. * This allows the operation to be terminated prior to timeout based on * the duration value. */ static int ecrnx_cfg80211_cancel_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); int error; ECRNX_DBG(ECRNX_FN_ENTRY_STR); /* For debug purpose (use ftrace kernel option) */ trace_cancel_roc(ecrnx_vif->vif_index); /* Check if a RoC procedure is pending */ if (!ecrnx_hw->roc) return 0; #ifdef CONFIG_ECRNX_P2P //if(ecrnx_vif->mgmt_reg_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4)) { error = ecrnx_send_p2p_cancel_listen_req(ecrnx_hw, ecrnx_vif); if(error == 0) ECRNX_PRINT("P2P: cancel_listen OK!!!\n"); else ECRNX_ERR("P2P: cancel_listen failed, error=%d\n", error); } #endif /* Forward the information to the FMAC */ return ecrnx_send_cancel_roc(ecrnx_hw); } /** * @dump_survey: get site survey information. */ static int ecrnx_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *netdev, int idx, struct survey_info *info) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ieee80211_supported_band *sband; struct ecrnx_survey_info *ecrnx_survey; //ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (idx >= ARRAY_SIZE(ecrnx_hw->survey)) return -ENOENT; ecrnx_survey = &ecrnx_hw->survey[idx]; // Check if provided index matches with a supported 2.4GHz channel sband = wiphy->bands[NL80211_BAND_2GHZ]; if (sband && idx >= sband->n_channels) { idx -= sband->n_channels; sband = NULL; } if (!sband) { #ifdef CONFIG_ECRNX_5G // Check if provided index matches with a supported 5GHz channel sband = wiphy->bands[NL80211_BAND_5GHZ]; #endif if (!sband || idx >= sband->n_channels) return -ENOENT; } // Fill the survey info->channel = &sband->channels[idx]; info->filled = ecrnx_survey->filled; if (ecrnx_survey->filled != 0) { SURVEY_TIME(info) = (u64)ecrnx_survey->chan_time_ms; SURVEY_TIME(info) = (u64)ecrnx_survey->chan_time_busy_ms; info->noise = ecrnx_survey->noise_dbm; // Set the survey report as not used ecrnx_survey->filled = 0; } return 0; } /** * @get_channel: Get the current operating channel for the virtual interface. * For monitor interfaces, it should return %NULL unless there's a single * current monitoring channel. */ int ecrnx_cfg80211_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_chan_def *chandef) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = container_of(wdev, struct ecrnx_vif, wdev); struct ecrnx_chanctx *ctxt; if (!ecrnx_vif->up) { return -ENODATA; } if (ecrnx_vif->vif_index == ecrnx_hw->monitor_vif) { //retrieve channel from firmware ecrnx_cfg80211_set_monitor_channel(wiphy, NULL); } //Check if channel context is valid if(!ecrnx_chanctx_valid(ecrnx_hw, ecrnx_vif->ch_index)){ return -ENODATA; } ctxt = &ecrnx_hw->chanctx_table[ecrnx_vif->ch_index]; *chandef = ctxt->chan_def; return 0; } /** * @mgmt_tx: Transmit a management frame. */ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) static int ecrnx_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, u64 *cookie) #else static int ecrnx_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, bool offchan, unsigned int wait, const u8* buf, size_t len, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)) bool no_cck, #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)) bool dont_wait_for_ack, #endif u64 *cookie) #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0) */ { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); struct ecrnx_sta *ecrnx_sta; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) struct ieee80211_channel *channel = params->chan; const u8 *buf = params->buf; bool offchan = false; #endif struct ieee80211_mgmt *mgmt = (void *)buf; bool ap = false; /* Check if provided VIF is an AP or a STA one */ switch (ECRNX_VIF_TYPE(ecrnx_vif)) { case NL80211_IFTYPE_AP_VLAN: ecrnx_vif = ecrnx_vif->ap_vlan.master; break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_MESH_POINT: ap = true; break; case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: default: break; } /* Get STA on which management frame has to be sent */ ecrnx_sta = ecrnx_retrieve_sta(ecrnx_hw, ecrnx_vif, mgmt->da, mgmt->frame_control, ap); trace_mgmt_tx((channel) ? channel->center_freq : 0, ecrnx_vif->vif_index, (ecrnx_sta) ? ecrnx_sta->sta_idx : 0xFF, mgmt); if (ap || ecrnx_sta) goto send_frame; /* Not an AP interface sending frame to unknown STA: * This is allowed for external authetication */ if ((ecrnx_vif->sta.flags & ECRNX_STA_EXT_AUTH) && ieee80211_is_auth(mgmt->frame_control)) goto send_frame; if(ieee80211_is_probe_resp(mgmt->frame_control)) goto p2p_send_frame; /* Otherwise ROC is needed */ if (!channel) return -EINVAL; /* Check that a RoC is already pending */ if (ecrnx_hw->roc) { /* Get VIF used for current ROC */ /* Check if RoC channel is the same than the required one */ if ((ecrnx_hw->roc->vif != ecrnx_vif) || (ecrnx_hw->roc->chan->center_freq != channel->center_freq)) return -EINVAL; } else { u64 cookie; int error; /* Start a ROC procedure for 30ms */ error = ecrnx_cfg80211_remain_on_channel(wiphy, wdev, channel, 30, &cookie); if (error) return error; /* Need to keep in mind that RoC has been launched internally in order to * avoid to call the cfg80211 callback once expired */ ecrnx_hw->roc->internal = true; #ifdef CONFIG_ECRNX_P2P ecrnx_hw->p2p_listen.rxdatas = 0; wait_event_interruptible_timeout(ecrnx_hw->p2p_listen.rxdataq, ecrnx_hw->p2p_listen.rxdatas, HZ); #endif } p2p_send_frame: #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) offchan = true; #endif send_frame: #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0)) return ecrnx_start_mgmt_xmit(ecrnx_vif, ecrnx_sta, params, offchan, cookie); #else return ecrnx_start_mgmt_xmit(ecrnx_vif, ecrnx_sta, channel, offchan, wait, buf, len, no_cck, dont_wait_for_ack, cookie); #endif } /** * @start_radar_detection: Start radar detection in the driver. */ static int ecrnx_cfg80211_start_radar_detection(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_chan_def *chandef, u32 cac_time_ms) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct apm_start_cac_cfm cfm; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)) ecrnx_radar_start_cac(&ecrnx_hw->radar, cac_time_ms, ecrnx_vif); #endif ecrnx_send_apm_start_cac_req(ecrnx_hw, ecrnx_vif, chandef, &cfm); if (cfm.status == CO_OK) { spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_link(ecrnx_vif, cfm.ch_idx, chandef); if (ecrnx_hw->cur_chanctx == ecrnx_vif->ch_index) ecrnx_radar_detection_enable(&ecrnx_hw->radar, ECRNX_RADAR_DETECT_REPORT, ECRNX_RADAR_RIU); spin_unlock_bh(&ecrnx_hw->cb_lock); } else { return -EIO; } return 0; } /** * @update_ft_ies: Provide updated Fast BSS Transition information to the * driver. If the SME is in the driver/firmware, this information can be * used in building Authentication and Reassociation Request frames. */ static int ecrnx_cfg80211_update_ft_ies(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_update_ft_ies_params *ftie) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(dev); const struct ecrnx_element *rsne = NULL, *mde = NULL, *fte = NULL, *elem; bool ft_in_non_rsn = false; int fties_len = 0; u8 *ft_assoc_ies, *pos; if ((ECRNX_VIF_TYPE(vif) != NL80211_IFTYPE_STATION) || (vif->sta.ft_assoc_ies == NULL)) return 0; for_each_ecrnx_element(elem, ftie->ie, ftie->ie_len) { if (elem->id == WLAN_EID_RSN) rsne = elem; else if (elem->id == WLAN_EID_MOBILITY_DOMAIN) mde = elem; else if (elem->id == WLAN_EID_FAST_BSS_TRANSITION) fte = elem; else netdev_warn(dev, "Unexpected FT element %d\n", elem->id); } if (!mde) { netdev_warn(dev, "Didn't find Mobility_Domain Element\n"); return 0; } else if (!rsne && !fte) { ft_in_non_rsn = true; } else if (!rsne || !fte) { netdev_warn(dev, "Didn't find RSN or Fast Transition Element\n"); return 0; } for_each_ecrnx_element(elem, vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len) { if ((elem->id == WLAN_EID_RSN) || (elem->id == WLAN_EID_MOBILITY_DOMAIN) || (elem->id == WLAN_EID_FAST_BSS_TRANSITION)) fties_len += elem->datalen + sizeof(struct ecrnx_element); } ft_assoc_ies = kmalloc(vif->sta.ft_assoc_ies_len - fties_len + ftie->ie_len, GFP_KERNEL); if (!ft_assoc_ies) { netdev_warn(dev, "Fail to allocate buffer for association elements"); } pos = ft_assoc_ies; for_each_ecrnx_element(elem, vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len) { if (elem->id == WLAN_EID_RSN) { if (ft_in_non_rsn) { netdev_warn(dev, "Found RSN element in non RSN FT"); goto abort; } else if (!rsne) { netdev_warn(dev, "Found several RSN element"); goto abort; } else { memcpy(pos, rsne, sizeof(*rsne) + rsne->datalen); pos += sizeof(*rsne) + rsne->datalen; rsne = NULL; } } else if (elem->id == WLAN_EID_MOBILITY_DOMAIN) { if (!mde) { netdev_warn(dev, "Found several Mobility Domain element"); goto abort; } else { memcpy(pos, mde, sizeof(*mde) + mde->datalen); pos += sizeof(*mde) + mde->datalen; mde = NULL; } } else if (elem->id == WLAN_EID_FAST_BSS_TRANSITION) { if (ft_in_non_rsn) { netdev_warn(dev, "Found Fast Transition element in non RSN FT"); goto abort; } else if (!fte) { netdev_warn(dev, "found several Fast Transition element"); goto abort; } else { memcpy(pos, fte, sizeof(*fte) + fte->datalen); pos += sizeof(*fte) + fte->datalen; fte = NULL; } } else { if (fte && !mde) { memcpy(pos, fte, sizeof(*fte) + fte->datalen); pos += sizeof(*fte) + fte->datalen; fte = NULL; } memcpy(pos, elem, sizeof(*elem) + elem->datalen); pos += sizeof(*elem) + elem->datalen; } } if (fte) { memcpy(pos, fte, sizeof(*fte) + fte->datalen); pos += sizeof(*fte) + fte->datalen; fte = NULL; } kfree(vif->sta.ft_assoc_ies); vif->sta.ft_assoc_ies = ft_assoc_ies; vif->sta.ft_assoc_ies_len = pos - ft_assoc_ies; if (vif->sta.flags & ECRNX_STA_FT_OVER_DS) { struct sm_connect_cfm sm_connect_cfm; struct cfg80211_connect_params sme; memset(&sme, 0, sizeof(sme)); rsne = cfg80211_find_ecrnx_elem(WLAN_EID_RSN, vif->sta.ft_assoc_ies, vif->sta.ft_assoc_ies_len); if (rsne && ecrnx_rsne_to_connect_params(rsne, &sme)) { netdev_warn(dev, "FT RSN parsing failed\n"); return 0; } sme.ssid_len = vif->sta.ft_assoc_ies[1]; sme.ssid = &vif->sta.ft_assoc_ies[2]; sme.bssid = vif->sta.ft_target_ap; sme.ie = &vif->sta.ft_assoc_ies[2 + sme.ssid_len]; sme.ie_len = vif->sta.ft_assoc_ies_len - (2 + sme.ssid_len); sme.auth_type = NL80211_AUTHTYPE_FT; ecrnx_send_sm_connect_req(ecrnx_hw, vif, &sme, &sm_connect_cfm); vif->sta.flags &= ~ECRNX_STA_FT_OVER_DS; } else if (vif->sta.flags & ECRNX_STA_FT_OVER_AIR) { uint8_t ssid_len; vif->sta.flags &= ~ECRNX_STA_FT_OVER_AIR; ssid_len = vif->sta.ft_assoc_ies[1] + 2; if (ecrnx_send_sm_ft_auth_rsp(ecrnx_hw, vif, &vif->sta.ft_assoc_ies[ssid_len], vif->sta.ft_assoc_ies_len - ssid_len)) netdev_err(dev, "FT Over Air: Failed to send updated assoc elem\n"); } return 0; abort: kfree(ft_assoc_ies); return 0; } /** * @set_cqm_rssi_config: Configure connection quality monitor RSSI threshold. */ static int ecrnx_cfg80211_set_cqm_rssi_config(struct wiphy *wiphy, struct net_device *dev, int32_t rssi_thold, uint32_t rssi_hyst) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); return ecrnx_send_cfg_rssi_req(ecrnx_hw, ecrnx_vif->vif_index, rssi_thold, rssi_hyst); } /** * * @channel_switch: initiate channel-switch procedure (with CSA). Driver is * responsible for veryfing if the switch is possible. Since this is * inherently tricky driver may decide to disconnect an interface later * with cfg80211_stop_iface(). This doesn't mean driver can accept * everything. It should do it's best to verify requests and reject them * as soon as possible. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) static int ecrnx_cfg80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_csa_settings *params) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_ipc_elem_var elem; struct ecrnx_bcn *bcn, *bcn_after; struct ecrnx_csa *csa; u16 csa_oft[BCN_MAX_CSA_CPT]; u8 *buf; int i, error = 0; if (vif->ap.csa) return -EBUSY; if (params->n_counter_offsets_beacon > BCN_MAX_CSA_CPT) return -EINVAL; /* Build the new beacon with CSA IE */ bcn = &vif->ap.bcn; buf = ecrnx_build_bcn(bcn, ¶ms->beacon_csa); if (!buf) return -ENOMEM; memset(csa_oft, 0, sizeof(csa_oft)); for (i = 0; i < params->n_counter_offsets_beacon; i++) { csa_oft[i] = params->counter_offsets_beacon[i] + bcn->head_len + bcn->tim_len; } /* If count is set to 0 (i.e anytime after this beacon) force it to 2 */ if (params->count == 0) { params->count = 2; for (i = 0; i < params->n_counter_offsets_beacon; i++) { buf[csa_oft[i]] = 2; } } if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &elem, bcn->len, DMA_TO_DEVICE, buf, NULL, NULL))) { goto end; } /* Build the beacon to use after CSA. It will only be sent to fw once CSA is over, but do it before sending the beacon as it must be ready when CSA is finished. */ csa = kzalloc(sizeof(struct ecrnx_csa), GFP_KERNEL); if (!csa) { error = -ENOMEM; goto end; } bcn_after = &csa->bcn; buf = ecrnx_build_bcn(bcn_after, ¶ms->beacon_after); if (!buf) { error = -ENOMEM; ecrnx_del_csa(vif); goto end; } if ((error = ecrnx_ipc_elem_var_allocs(ecrnx_hw, &csa->elem, bcn_after->len, DMA_TO_DEVICE, buf, NULL, NULL))) { goto end; } vif->ap.csa = csa; csa->vif = vif; csa->chandef = params->chandef; /* Send new Beacon. FW will extract channel and count from the beacon */ error = ecrnx_send_bcn_change(ecrnx_hw, vif->vif_index, elem.dma_addr, bcn->len, bcn->head_len, bcn->tim_len, csa_oft); if (error) { ecrnx_del_csa(vif); goto end; } else { INIT_WORK(&csa->work, ecrnx_csa_finish); cfg80211_ch_switch_started_notify(dev, &csa->chandef, params->count); } end: ecrnx_ipc_elem_var_deallocs(ecrnx_hw, &elem); return error; } #endif /* * @tdls_mgmt: prepare TDLS action frame packets and forward them to FW */ static int ecrnx_cfg80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, u8 action_code, u8 dialog_token, u16 status_code, u32 peer_capability, bool initiator, const u8 *buf, size_t len) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); int ret = 0; /* make sure we support TDLS */ if (!(wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)) return -ENOTSUPP; /* make sure we are in station mode (and connected) */ if ((ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_STATION) || (!ecrnx_vif->up) || (!ecrnx_vif->sta.ap)) return -ENOTSUPP; /* only one TDLS link is supported */ if ((action_code == WLAN_TDLS_SETUP_REQUEST) && (ecrnx_vif->sta.tdls_sta) && (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE)) { ECRNX_ERR("%s: only one TDLS link is supported!\n", __func__); return -ENOTSUPP; } if ((action_code == WLAN_TDLS_DISCOVERY_REQUEST) && (ecrnx_hw->mod_params->ps_on)) { ECRNX_ERR("%s: discovery request is not supported when " "power-save is enabled!\n", __func__); return -ENOTSUPP; } switch (action_code) { case WLAN_TDLS_SETUP_RESPONSE: /* only one TDLS link is supported */ if ((status_code == 0) && (ecrnx_vif->sta.tdls_sta) && (ecrnx_vif->tdls_status == TDLS_LINK_ACTIVE)) { ECRNX_ERR("%s: only one TDLS link is supported!\n", __func__); status_code = WLAN_STATUS_REQUEST_DECLINED; } /* fall-through */ case WLAN_TDLS_SETUP_REQUEST: case WLAN_TDLS_TEARDOWN: case WLAN_TDLS_DISCOVERY_REQUEST: case WLAN_TDLS_SETUP_CONFIRM: case WLAN_PUB_ACTION_TDLS_DISCOVER_RES: ret = ecrnx_tdls_send_mgmt_packet_data(ecrnx_hw, ecrnx_vif, peer, action_code, dialog_token, status_code, peer_capability, initiator, buf, len, 0, NULL); break; default: ECRNX_ERR("%s: Unknown TDLS mgmt/action frame %pM\n", __func__, peer); ret = -EOPNOTSUPP; break; } if (action_code == WLAN_TDLS_SETUP_REQUEST) { ecrnx_vif->tdls_status = TDLS_SETUP_REQ_TX; } else if (action_code == WLAN_TDLS_SETUP_RESPONSE) { ecrnx_vif->tdls_status = TDLS_SETUP_RSP_TX; } else if ((action_code == WLAN_TDLS_SETUP_CONFIRM) && (ret == CO_OK)) { ecrnx_vif->tdls_status = TDLS_LINK_ACTIVE; /* Set TDLS active */ ecrnx_vif->sta.tdls_sta->tdls.active = true; } return ret; } /* * @tdls_oper: execute TDLS operation */ static int ecrnx_cfg80211_tdls_oper(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); int error; if (oper != NL80211_TDLS_DISABLE_LINK) return 0; if (!ecrnx_vif->sta.tdls_sta) { ECRNX_ERR("%s: TDLS station %pM does not exist\n", __func__, peer); return -ENOLINK; } if (memcmp(ecrnx_vif->sta.tdls_sta->mac_addr, peer, ETH_ALEN) == 0) { /* Disable Channel Switch */ if (!ecrnx_send_tdls_cancel_chan_switch_req(ecrnx_hw, ecrnx_vif, ecrnx_vif->sta.tdls_sta, NULL)) ecrnx_vif->sta.tdls_sta->tdls.chsw_en = false; netdev_info(dev, "Del TDLS sta %d (%pM)", ecrnx_vif->sta.tdls_sta->sta_idx, ecrnx_vif->sta.tdls_sta->mac_addr); /* Ensure that we won't process PS change ind */ spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_vif->sta.tdls_sta->ps.active = false; ecrnx_vif->sta.tdls_sta->valid = false; spin_unlock_bh(&ecrnx_hw->cb_lock); ecrnx_txq_sta_deinit(ecrnx_hw, ecrnx_vif->sta.tdls_sta); error = ecrnx_send_me_sta_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta->sta_idx, true); if ((error != 0) && (error != -EPIPE)) return error; #ifdef CONFIG_ECRNX_BFMER // Disable Beamformer if supported ecrnx_bfmer_report_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta); ecrnx_mu_group_sta_del(ecrnx_hw, ecrnx_vif->sta.tdls_sta); #endif /* CONFIG_ECRNX_BFMER */ /* Set TDLS not active */ ecrnx_vif->sta.tdls_sta->tdls.active = false; ecrnx_dbgfs_unregister_sta(ecrnx_hw, ecrnx_vif->sta.tdls_sta); // Remove TDLS station ecrnx_vif->tdls_status = TDLS_LINK_IDLE; ecrnx_vif->sta.tdls_sta = NULL; } return 0; } /* * @tdls_channel_switch: enable TDLS channel switch */ static int ecrnx_cfg80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev, const u8 *addr, u8 oper_class, struct cfg80211_chan_def *chandef) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_sta *ecrnx_sta = ecrnx_vif->sta.tdls_sta; struct tdls_chan_switch_cfm cfm; int error; if ((!ecrnx_sta) || (memcmp(addr, ecrnx_sta->mac_addr, ETH_ALEN))) { ECRNX_ERR("%s: TDLS station %pM doesn't exist\n", __func__, addr); return -ENOLINK; } if (!ecrnx_sta->tdls.chsw_allowed) { ECRNX_ERR("%s: TDLS station %pM does not support TDLS channel switch\n", __func__, addr); return -ENOTSUPP; } error = ecrnx_send_tdls_chan_switch_req(ecrnx_hw, ecrnx_vif, ecrnx_sta, ecrnx_sta->tdls.initiator, oper_class, chandef, &cfm); if (error) return error; if (!cfm.status) { ecrnx_sta->tdls.chsw_en = true; return 0; } else { ECRNX_ERR("%s: TDLS channel switch already enabled and only one is supported\n", __func__); return -EALREADY; } } /* * @tdls_cancel_channel_switch: disable TDLS channel switch */ static void ecrnx_cfg80211_tdls_cancel_channel_switch(struct wiphy *wiphy, struct net_device *dev, const u8 *addr) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_sta *ecrnx_sta = ecrnx_vif->sta.tdls_sta; struct tdls_cancel_chan_switch_cfm cfm; if (!ecrnx_sta) return; if (!ecrnx_send_tdls_cancel_chan_switch_req(ecrnx_hw, ecrnx_vif, ecrnx_sta, &cfm)) ecrnx_sta->tdls.chsw_en = false; } /** * @change_bss: Modify parameters for a given BSS (mainly for AP mode). */ static int ecrnx_cfg80211_change_bss(struct wiphy *wiphy, struct net_device *dev, struct bss_parameters *params) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); int res = -EOPNOTSUPP; if (((ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_AP) || (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_P2P_GO)) && (params->ap_isolate > -1)) { if (params->ap_isolate) ecrnx_vif->ap.flags |= ECRNX_AP_ISOLATE; else ecrnx_vif->ap.flags &= ~ECRNX_AP_ISOLATE; res = 0; } return res; } /** * @get_station: get station information for the station identified by @mac */ static int ecrnx_fill_station_info(struct ecrnx_sta *sta, struct ecrnx_vif *vif, struct station_info *sinfo) { struct ecrnx_sta_stats *stats = &sta->stats; struct rx_vector_1 *rx_vect1 = &stats->last_rx.rx_vect1; // Generic info sinfo->generation = vif->generation; //sinfo->inactive_time = jiffies_to_msecs(jiffies - stats->last_act); sinfo->rx_bytes = stats->rx_bytes; sinfo->tx_bytes = stats->tx_bytes; sinfo->tx_packets = stats->tx_pkts; sinfo->rx_packets = stats->rx_pkts; sinfo->signal = rx_vect1->rssi1; sinfo->tx_failed = 1; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) switch (rx_vect1->ch_bw) { case PHY_CHNL_BW_20: sinfo->rxrate.bw = RATE_INFO_BW_20; break; case PHY_CHNL_BW_40: sinfo->rxrate.bw = RATE_INFO_BW_40; break; case PHY_CHNL_BW_80: sinfo->rxrate.bw = RATE_INFO_BW_80; break; case PHY_CHNL_BW_160: sinfo->rxrate.bw = RATE_INFO_BW_160; break; default: #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) sinfo->rxrate.bw = RATE_INFO_BW_HE_RU; #else sinfo->rxrate.bw = RATE_INFO_BW_160; #endif break; } #endif switch (rx_vect1->format_mod) { case FORMATMOD_NON_HT: case FORMATMOD_NON_HT_DUP_OFDM: sinfo->rxrate.flags = 0; sinfo->rxrate.legacy = legrates_lut[rx_vect1->leg_rate].rate; break; case FORMATMOD_HT_MF: case FORMATMOD_HT_GF: sinfo->rxrate.flags = RATE_INFO_FLAGS_MCS; if (rx_vect1->ht.short_gi) sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; sinfo->rxrate.mcs = rx_vect1->ht.mcs; break; case FORMATMOD_VHT: sinfo->rxrate.flags = RATE_INFO_FLAGS_VHT_MCS; if (rx_vect1->vht.short_gi) sinfo->rxrate.flags |= RATE_INFO_FLAGS_SHORT_GI; sinfo->rxrate.mcs = rx_vect1->vht.mcs; sinfo->rxrate.nss = rx_vect1->vht.nss; break; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) #if CONFIG_ECRNX_HE case FORMATMOD_HE_MU: sinfo->rxrate.he_ru_alloc = rx_vect1->he.ru_size; break; case FORMATMOD_HE_SU: case FORMATMOD_HE_ER: case FORMATMOD_HE_TB: sinfo->rxrate.flags = RATE_INFO_FLAGS_HE_MCS; sinfo->rxrate.mcs = rx_vect1->he.mcs; sinfo->rxrate.nss = rx_vect1->he.nss; sinfo->rxrate.he_gi = rx_vect1->he.gi_type; sinfo->rxrate.he_dcm = rx_vect1->he.dcm; break; #endif #endif default : return -EINVAL; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) sinfo->filled |= (STATION_INFO_INACTIVE_TIME | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) STATION_INFO_RX_BYTES64 | STATION_INFO_TX_BYTES64 | #endif STATION_INFO_RX_PACKETS | STATION_INFO_TX_PACKETS | STATION_INFO_SIGNAL | STATION_INFO_RX_BITRATE); #else sinfo->filled = (BIT(NL80211_STA_INFO_RX_BYTES64) | BIT(NL80211_STA_INFO_TX_BYTES64) | BIT(NL80211_STA_INFO_RX_PACKETS) | BIT(NL80211_STA_INFO_TX_PACKETS) | BIT(NL80211_STA_INFO_SIGNAL) | BIT(NL80211_STA_INFO_TX_BITRATE) | BIT(NL80211_STA_INFO_TX_FAILED) | BIT(NL80211_STA_INFO_RX_BITRATE)); #endif // Mesh specific info if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MESH_POINT) { struct mesh_peer_info_cfm peer_info_cfm; if (ecrnx_send_mesh_peer_info_req(vif->ecrnx_hw, vif, sta->sta_idx, &peer_info_cfm)) return -ENOMEM; peer_info_cfm.last_bcn_age = peer_info_cfm.last_bcn_age / 1000; if (peer_info_cfm.last_bcn_age < sinfo->inactive_time) sinfo->inactive_time = peer_info_cfm.last_bcn_age; sinfo->llid = peer_info_cfm.local_link_id; sinfo->plid = peer_info_cfm.peer_link_id; sinfo->plink_state = peer_info_cfm.link_state; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) sinfo->local_pm = peer_info_cfm.local_ps_mode; sinfo->peer_pm = peer_info_cfm.peer_ps_mode; sinfo->nonpeer_pm = peer_info_cfm.non_peer_ps_mode; #endif sinfo->filled |= (BIT(NL80211_STA_INFO_LLID) | BIT(NL80211_STA_INFO_PLID) | BIT(NL80211_STA_INFO_PLINK_STATE) | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) BIT(NL80211_STA_INFO_LOCAL_PM) | BIT(NL80211_STA_INFO_PEER_PM) | BIT(NL80211_STA_INFO_NONPEER_PM)| #endif 0); } sinfo->txrate.legacy = 0x6818; return 0; } static int ecrnx_cfg80211_get_station(struct wiphy *wiphy, struct net_device *dev, #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 16, 0)) u8 *mac, #else const u8 *mac, #endif struct station_info *sinfo) { struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_sta *sta = NULL; if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) return -EINVAL; else if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { if (vif->sta.ap && ether_addr_equal(vif->sta.ap->mac_addr, mac)) sta = vif->sta.ap; } else { struct ecrnx_sta *sta_iter; list_for_each_entry(sta_iter, &vif->ap.sta_list, list) { if (sta_iter->valid && ether_addr_equal(sta_iter->mac_addr, mac)) { sta = sta_iter; break; } } } if (sta) return ecrnx_fill_station_info(sta, vif, sinfo); return -EINVAL; } int ecrnx_cfg80211_dump_station(struct wiphy *wiphy, struct net_device *dev, int idx, u8 *mac, struct station_info *sinfo) { struct ecrnx_vif *vif = netdev_priv(dev); struct ecrnx_sta *sta = NULL; if (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_MONITOR) return -EINVAL; else if ((ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_STATION) || (ECRNX_VIF_TYPE(vif) == NL80211_IFTYPE_P2P_CLIENT)) { if ((idx == 0) && vif->sta.ap && vif->sta.ap->valid) sta = vif->sta.ap; } else { struct ecrnx_sta *sta_iter; int i = 0; list_for_each_entry(sta_iter, &vif->ap.sta_list, list) { if (i == idx) { sta = sta_iter; break; } i++; } } if (sta == NULL) return -ENOENT; memcpy(mac, &sta->mac_addr, ETH_ALEN); return ecrnx_fill_station_info(sta, vif, sinfo); } /** * @add_mpath: add a fixed mesh path */ static int ecrnx_cfg80211_add_mpath(struct wiphy *wiphy, struct net_device *dev, const u8 *dst, const u8 *next_hop) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct mesh_path_update_cfm cfm; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, next_hop, &cfm); } /** * @del_mpath: delete a given mesh path */ static int ecrnx_cfg80211_del_mpath(struct wiphy *wiphy, struct net_device *dev, const u8 *dst) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct mesh_path_update_cfm cfm; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, NULL, &cfm); } /** * @change_mpath: change a given mesh path */ static int ecrnx_cfg80211_change_mpath(struct wiphy *wiphy, struct net_device *dev, const u8 *dst, const u8 *next_hop) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct mesh_path_update_cfm cfm; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; return ecrnx_send_mesh_path_update_req(ecrnx_hw, ecrnx_vif, dst, next_hop, &cfm); } /** * @get_mpath: get a mesh path for the given parameters */ static int ecrnx_cfg80211_get_mpath(struct wiphy *wiphy, struct net_device *dev, u8 *dst, u8 *next_hop, struct mpath_info *pinfo) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_mesh_path *mesh_path = NULL; struct ecrnx_mesh_path *cur; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; list_for_each_entry(cur, &ecrnx_vif->ap.mpath_list, list) { /* Compare the path target address and the provided destination address */ if (memcmp(dst, &cur->tgt_mac_addr, ETH_ALEN)) { continue; } mesh_path = cur; break; } if (mesh_path == NULL) return -ENOENT; /* Copy next HOP MAC address */ if (mesh_path->nhop_sta) memcpy(next_hop, &mesh_path->nhop_sta->mac_addr, ETH_ALEN); /* Fill path information */ pinfo->filled = 0; pinfo->generation = ecrnx_vif->generation; return 0; } /** * @dump_mpath: dump mesh path callback -- resume dump at index @idx */ static int ecrnx_cfg80211_dump_mpath(struct wiphy *wiphy, struct net_device *dev, int idx, u8 *dst, u8 *next_hop, struct mpath_info *pinfo) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_mesh_path *mesh_path = NULL; struct ecrnx_mesh_path *cur; int i = 0; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; list_for_each_entry(cur, &ecrnx_vif->ap.mpath_list, list) { if (i < idx) { i++; continue; } mesh_path = cur; break; } if (mesh_path == NULL) return -ENOENT; /* Copy target and next hop MAC address */ memcpy(dst, &mesh_path->tgt_mac_addr, ETH_ALEN); if (mesh_path->nhop_sta) memcpy(next_hop, &mesh_path->nhop_sta->mac_addr, ETH_ALEN); /* Fill path information */ pinfo->filled = 0; pinfo->generation = ecrnx_vif->generation; return 0; } /** * @get_mpp: get a mesh proxy path for the given parameters */ static int ecrnx_cfg80211_get_mpp(struct wiphy *wiphy, struct net_device *dev, u8 *dst, u8 *mpp, struct mpath_info *pinfo) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_mesh_proxy *mesh_proxy = NULL; struct ecrnx_mesh_proxy *cur; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; list_for_each_entry(cur, &ecrnx_vif->ap.proxy_list, list) { if (cur->local) { continue; } /* Compare the path target address and the provided destination address */ if (memcmp(dst, &cur->ext_sta_addr, ETH_ALEN)) { continue; } mesh_proxy = cur; break; } if (mesh_proxy == NULL) return -ENOENT; memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN); /* Fill path information */ pinfo->filled = 0; pinfo->generation = ecrnx_vif->generation; return 0; } /** * @dump_mpp: dump mesh proxy path callback -- resume dump at index @idx */ static int ecrnx_cfg80211_dump_mpp(struct wiphy *wiphy, struct net_device *dev, int idx, u8 *dst, u8 *mpp, struct mpath_info *pinfo) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_mesh_proxy *mesh_proxy = NULL; struct ecrnx_mesh_proxy *cur; int i = 0; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; list_for_each_entry(cur, &ecrnx_vif->ap.proxy_list, list) { if (cur->local) { continue; } if (i < idx) { i++; continue; } mesh_proxy = cur; break; } if (mesh_proxy == NULL) return -ENOENT; /* Copy target MAC address */ memcpy(dst, &mesh_proxy->ext_sta_addr, ETH_ALEN); memcpy(mpp, &mesh_proxy->proxy_addr, ETH_ALEN); /* Fill path information */ pinfo->filled = 0; pinfo->generation = ecrnx_vif->generation; return 0; } /** * @get_mesh_config: Get the current mesh configuration */ static int ecrnx_cfg80211_get_mesh_config(struct wiphy *wiphy, struct net_device *dev, struct mesh_config *conf) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; return 0; } /** * @update_mesh_config: Update mesh parameters on a running mesh. */ static int ecrnx_cfg80211_update_mesh_config(struct wiphy *wiphy, struct net_device *dev, u32 mask, const struct mesh_config *nconf) { struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct mesh_update_cfm cfm; int status; if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (mask & CO_BIT(NL80211_MESHCONF_POWER_MODE - 1)) { ecrnx_vif->ap.next_mesh_pm = nconf->power_mode; if (!list_empty(&ecrnx_vif->ap.sta_list)) { // If there are mesh links we don't want to update the power mode // It will be updated with ecrnx_update_mesh_power_mode() when the // ps mode of a link is updated or when a new link is added/removed mask &= ~BIT(NL80211_MESHCONF_POWER_MODE - 1); if (!mask) return 0; } } #endif status = ecrnx_send_mesh_update_req(ecrnx_hw, ecrnx_vif, mask, nconf, &cfm); if (!status && (cfm.status != 0)) status = -EINVAL; return status; } /** * @join_mesh: join the mesh network with the specified parameters * (invoked with the wireless_dev mutex held) */ static int ecrnx_cfg80211_join_mesh(struct wiphy *wiphy, struct net_device *dev, const struct mesh_config *conf, const struct mesh_setup *setup) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct mesh_start_cfm mesh_start_cfm; int error = 0; u8 txq_status = 0; /* STA for BC/MC traffic */ struct ecrnx_sta *sta; ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (ECRNX_VIF_TYPE(ecrnx_vif) != NL80211_IFTYPE_MESH_POINT) return -ENOTSUPP; /* Forward the information to the UMAC */ if ((error = ecrnx_send_mesh_start_req(ecrnx_hw, ecrnx_vif, conf, setup, &mesh_start_cfm))) { return error; } /* Check the status */ switch (mesh_start_cfm.status) { case CO_OK: #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) ecrnx_vif->ap.bcmc_index = mesh_start_cfm.bcmc_idx; ecrnx_vif->ap.bcn_interval = setup->beacon_interval; #endif ecrnx_vif->ap.flags = 0; ecrnx_vif->use_4addr = true; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) if (setup->user_mpm) ecrnx_vif->ap.flags |= ECRNX_AP_USER_MESH_PM; #endif sta = &ecrnx_hw->sta_table[mesh_start_cfm.bcmc_idx]; sta->valid = true; sta->aid = 0; sta->sta_idx = mesh_start_cfm.bcmc_idx; sta->ch_idx = mesh_start_cfm.ch_idx; sta->vif_idx = ecrnx_vif->vif_index; sta->qos = true; sta->acm = 0; sta->ps.active = false; sta->listen_interval = 5; ecrnx_mu_group_sta_init(sta, NULL); spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_link(ecrnx_vif, mesh_start_cfm.ch_idx, (struct cfg80211_chan_def *)(&setup->chandef)); if (ecrnx_hw->cur_chanctx != mesh_start_cfm.ch_idx) { txq_status = ECRNX_TXQ_STOP_CHAN; } ecrnx_txq_vif_init(ecrnx_hw, ecrnx_vif, txq_status); spin_unlock_bh(&ecrnx_hw->cb_lock); netif_tx_start_all_queues(dev); netif_carrier_on(dev); /* If the AP channel is already the active, we probably skip radar activation on MM_CHANNEL_SWITCH_IND (unless another vif use this ctxt). In anycase retest if radar detection must be activated */ if (ecrnx_hw->cur_chanctx == mesh_start_cfm.ch_idx) { ecrnx_radar_detection_enable_on_cur_channel(ecrnx_hw); } break; case CO_BUSY: error = -EINPROGRESS; break; default: error = -EIO; break; } /* Print information about the operation */ if (error) { netdev_info(dev, "Failed to start MP (%d)", error); } else { netdev_info(dev, "MP started: ch=%d, bcmc_idx=%d", ecrnx_vif->ch_index, ecrnx_vif->ap.bcmc_index); } return error; } /** * @leave_mesh: leave the current mesh network * (invoked with the wireless_dev mutex held) */ static int ecrnx_cfg80211_leave_mesh(struct wiphy *wiphy, struct net_device *dev) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); struct ecrnx_vif *ecrnx_vif = netdev_priv(dev); struct mesh_stop_cfm mesh_stop_cfm; int error = 0; error = ecrnx_send_mesh_stop_req(ecrnx_hw, ecrnx_vif, &mesh_stop_cfm); if (error == 0) { /* Check the status */ switch (mesh_stop_cfm.status) { case CO_OK: spin_lock_bh(&ecrnx_hw->cb_lock); ecrnx_chanctx_unlink(ecrnx_vif); ecrnx_radar_cancel_cac(&ecrnx_hw->radar); spin_unlock_bh(&ecrnx_hw->cb_lock); /* delete BC/MC STA */ ecrnx_txq_vif_deinit(ecrnx_hw, ecrnx_vif); ecrnx_del_bcn(&ecrnx_vif->ap.bcn); netif_tx_stop_all_queues(dev); netif_carrier_off(dev); break; default: error = -EIO; break; } } if (error) { netdev_info(dev, "Failed to stop MP"); } else { netdev_info(dev, "MP Stopped"); } return 0; } #ifdef CONFIG_ECRNX_P2P #if LINUX_VERSION_CODE > KERNEL_VERSION(5, 8, 0) static void ecrnx_cfg80211_update_mgmt_frame_registrations(struct wiphy *wiphy, struct wireless_dev *wdev, struct mgmt_frame_regs *upd) { struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); ECRNX_DBG(ECRNX_FN_ENTRY_STR); if (ECRNX_VIF_TYPE(ecrnx_vif) == NL80211_IFTYPE_STATION) { ecrnx_vif->mgmt_reg_stypes = upd->interface_stypes & BIT(IEEE80211_STYPE_PROBE_REQ >> 4); } } #else static void ecrnx_cfg80211_mgmt_frame_register(struct wiphy *wiphy, struct wireless_dev *wdev, u16 frame_type, bool reg) { struct ecrnx_vif *ecrnx_vif = netdev_priv(wdev->netdev); u16 mgmt_type; mgmt_type = (frame_type & IEEE80211_FCTL_STYPE) >> 4; if (reg) ecrnx_vif->mgmt_reg_stypes |= BIT(mgmt_type); else ecrnx_vif->mgmt_reg_stypes &= ~BIT(mgmt_type); } #endif #endif static struct cfg80211_ops ecrnx_cfg80211_ops = { .add_virtual_intf = ecrnx_cfg80211_add_iface, .del_virtual_intf = ecrnx_cfg80211_del_iface, .change_virtual_intf = ecrnx_cfg80211_change_iface, .start_p2p_device = ecrnx_cfg80211_start_p2p_device, .stop_p2p_device = ecrnx_cfg80211_stop_p2p_device, .scan = ecrnx_cfg80211_scan, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)) .abort_scan = ecrnx_cfg80211_abort_scan, #endif .connect = ecrnx_cfg80211_connect, .disconnect = ecrnx_cfg80211_disconnect, .add_key = ecrnx_cfg80211_add_key, .get_key = ecrnx_cfg80211_get_key, .del_key = ecrnx_cfg80211_del_key, .set_default_key = ecrnx_cfg80211_set_default_key, .set_default_mgmt_key = ecrnx_cfg80211_set_default_mgmt_key, .add_station = ecrnx_cfg80211_add_station, .del_station = ecrnx_cfg80211_del_station, .change_station = ecrnx_cfg80211_change_station, .mgmt_tx = ecrnx_cfg80211_mgmt_tx, .start_ap = ecrnx_cfg80211_start_ap, .change_beacon = ecrnx_cfg80211_change_beacon, .stop_ap = ecrnx_cfg80211_stop_ap, .set_monitor_channel = ecrnx_cfg80211_set_monitor_channel, .probe_client = ecrnx_cfg80211_probe_client, .set_wiphy_params = ecrnx_cfg80211_set_wiphy_params, .set_txq_params = ecrnx_cfg80211_set_txq_params, .set_tx_power = ecrnx_cfg80211_set_tx_power, // .get_tx_power = ecrnx_cfg80211_get_tx_power, .set_power_mgmt = ecrnx_cfg80211_set_power_mgmt, .get_station = ecrnx_cfg80211_get_station, .remain_on_channel = ecrnx_cfg80211_remain_on_channel, .cancel_remain_on_channel = ecrnx_cfg80211_cancel_remain_on_channel, .dump_survey = ecrnx_cfg80211_dump_survey, .get_channel = ecrnx_cfg80211_get_channel, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) .start_radar_detection = ecrnx_cfg80211_start_radar_detection, .update_ft_ies = ecrnx_cfg80211_update_ft_ies, #endif .set_cqm_rssi_config = ecrnx_cfg80211_set_cqm_rssi_config, #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) .channel_switch = ecrnx_cfg80211_channel_switch, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) .tdls_channel_switch = ecrnx_cfg80211_tdls_channel_switch, .tdls_cancel_channel_switch = ecrnx_cfg80211_tdls_cancel_channel_switch, #endif .tdls_mgmt = ecrnx_cfg80211_tdls_mgmt, .tdls_oper = ecrnx_cfg80211_tdls_oper, .change_bss = ecrnx_cfg80211_change_bss, #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) .external_auth = ecrnx_cfg80211_external_auth, #endif #ifdef CONFIG_ECRNX_P2P #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) .update_mgmt_frame_registrations = ecrnx_cfg80211_update_mgmt_frame_registrations, #else .mgmt_frame_register = ecrnx_cfg80211_mgmt_frame_register, #endif #endif }; /********************************************************************* * Init/Exit functions *********************************************************************/ static void ecrnx_wdev_unregister(struct ecrnx_hw *ecrnx_hw) { struct ecrnx_vif *ecrnx_vif, *tmp; rtnl_lock(); list_for_each_entry_safe(ecrnx_vif, tmp, &ecrnx_hw->vifs, list) { ecrnx_cfg80211_del_iface(ecrnx_hw->wiphy, &ecrnx_vif->wdev); } rtnl_unlock(); } static void ecrnx_set_vers(struct ecrnx_hw *ecrnx_hw) { u32 vers = ecrnx_hw->version_cfm.version_lmac; ECRNX_DBG(ECRNX_FN_ENTRY_STR); snprintf(ecrnx_hw->wiphy->fw_version, sizeof(ecrnx_hw->wiphy->fw_version), "%d.%d.%d.%d", (vers & (0xff << 24)) >> 24, (vers & (0xff << 16)) >> 16, (vers & (0xff << 8)) >> 8, (vers & (0xff << 0)) >> 0); ecrnx_hw->machw_type = ecrnx_machw_type(ecrnx_hw->version_cfm.version_machw_2); } static void ecrnx_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request) { struct ecrnx_hw *ecrnx_hw = wiphy_priv(wiphy); // For now trust all initiator ecrnx_radar_set_domain(&ecrnx_hw->radar, request->dfs_region); ecrnx_send_me_chan_config_req(ecrnx_hw); } static void ecrnx_enable_mesh(struct ecrnx_hw *ecrnx_hw) { struct wiphy *wiphy = ecrnx_hw->wiphy; if (!ecrnx_mod_params.mesh) return; ecrnx_cfg80211_ops.add_mpath = ecrnx_cfg80211_add_mpath; ecrnx_cfg80211_ops.del_mpath = ecrnx_cfg80211_del_mpath; ecrnx_cfg80211_ops.change_mpath = ecrnx_cfg80211_change_mpath; ecrnx_cfg80211_ops.get_mpath = ecrnx_cfg80211_get_mpath; ecrnx_cfg80211_ops.dump_mpath = ecrnx_cfg80211_dump_mpath; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) ecrnx_cfg80211_ops.get_mpp = ecrnx_cfg80211_get_mpp; ecrnx_cfg80211_ops.dump_mpp = ecrnx_cfg80211_dump_mpp; #endif ecrnx_cfg80211_ops.get_mesh_config = ecrnx_cfg80211_get_mesh_config; ecrnx_cfg80211_ops.update_mesh_config = ecrnx_cfg80211_update_mesh_config; ecrnx_cfg80211_ops.join_mesh = ecrnx_cfg80211_join_mesh; ecrnx_cfg80211_ops.leave_mesh = ecrnx_cfg80211_leave_mesh; wiphy->flags |= (WIPHY_FLAG_MESH_AUTH | WIPHY_FLAG_IBSS_RSN); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) wiphy->features |= NL80211_FEATURE_USERSPACE_MPM; #endif wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT); ecrnx_limits[0].types |= BIT(NL80211_IFTYPE_MESH_POINT); ecrnx_limits_dfs[0].types |= BIT(NL80211_IFTYPE_MESH_POINT); } int ecrnx_get_cal_result(struct ecrnx_hw *ecrnx_hw) { int ret; wifi_cal_data_t *result = &cal_result; ret = ecrnx_send_cal_result_get_req(ecrnx_hw, result); return ret; } void ecrnx_he_init(void) { ecrnx_he_cap.has_he = true; memset(&ecrnx_he_cap.he_cap_elem, 0, sizeof(struct ieee80211_he_cap_elem)); ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80 = cpu_to_le16(0xfffa); ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80 = cpu_to_le16(0xfffa); ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_160 = cpu_to_le16(0xffff); ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_160 = cpu_to_le16(0xffff); ecrnx_he_cap.he_mcs_nss_supp.rx_mcs_80p80 = cpu_to_le16(0xffff); ecrnx_he_cap.he_mcs_nss_supp.tx_mcs_80p80 = cpu_to_le16(0xffff); memset(ecrnx_he_cap.ppe_thres, 0, sizeof(u8)*IEEE80211_HE_PPE_THRES_MAX_LEN); } /** * */ bool register_drv_done = false; int ecrnx_cfg80211_init(void *ecrnx_plat, void **platform_data) { struct ecrnx_hw *ecrnx_hw; int ret = 0; struct wiphy *wiphy; struct wireless_dev *wdev; int i; ECRNX_DBG(ECRNX_FN_ENTRY_STR); /* create a new wiphy for use with cfg80211 */ wiphy = wiphy_new(&ecrnx_cfg80211_ops, sizeof(struct ecrnx_hw)); if (!wiphy) { dev_err(ecrnx_platform_get_dev(ecrnx_plat), "Failed to create new wiphy\n"); ret = -ENOMEM; goto err_out; } ecrnx_hw = wiphy_priv(wiphy); ecrnx_hw->wiphy = wiphy; ecrnx_hw->plat = ecrnx_plat; ecrnx_hw->dev = ecrnx_platform_get_dev(ecrnx_plat); ecrnx_hw->mod_params = &ecrnx_mod_params; ecrnx_hw->tcp_pacing_shift = 7; *platform_data = ecrnx_hw; /* set device pointer for wiphy */ set_wiphy_dev(wiphy, ecrnx_hw->dev); /* Create cache to allocate sw_txhdr */ ecrnx_hw->sw_txhdr_cache = KMEM_CACHE(ecrnx_sw_txhdr, 0); if (!ecrnx_hw->sw_txhdr_cache) { wiphy_err(wiphy, "Cannot allocate cache for sw TX header\n"); ret = -ENOMEM; goto err_cache; } if ((ret = ecrnx_parse_configfile(ecrnx_hw, ECRNX_CONFIG_FW_NAME))) { wiphy_err(wiphy, "ecrnx_parse_configfile failed\n"); goto err_config; } ecrnx_hw->vif_started = 0; ecrnx_hw->monitor_vif = ECRNX_INVALID_VIF; ecrnx_hw->scan_ie.addr = NULL; for (i = 0; i < NX_VIRT_DEV_MAX + NX_REMOTE_STA_MAX; i++) ecrnx_hw->avail_idx_map |= BIT(i); ecrnx_hwq_init(ecrnx_hw); ecrnx_txq_prepare(ecrnx_hw); ecrnx_mu_group_init(ecrnx_hw); /* Initialize RoC element pointer to NULL, indicate that RoC can be started */ ecrnx_hw->roc = NULL; /* Cookie can not be 0 */ ecrnx_hw->roc_cookie = 1; wiphy->mgmt_stypes = ecrnx_default_mgmt_stypes; wiphy->bands[NL80211_BAND_2GHZ] = &ecrnx_band_2GHz; #ifdef CONFIG_ECRNX_5G wiphy->bands[NL80211_BAND_5GHZ] = &ecrnx_band_5GHz; #endif wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP) | BIT(NL80211_IFTYPE_AP_VLAN) | BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_MONITOR); wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) WIPHY_FLAG_HAS_CHANNEL_SWITCH | #endif WIPHY_FLAG_4ADDR_STATION | WIPHY_FLAG_4ADDR_AP; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) wiphy->max_num_csa_counters = BCN_MAX_CSA_CPT; #endif wiphy->max_remain_on_channel_duration = ecrnx_hw->mod_params->roc_dur_max; #if 0 /* eswin:rm the feature of OBSS_SCAN which can cause the uplink stream shutdown */ wiphy->features |= NL80211_FEATURE_NEED_OBSS_SCAN | NL80211_FEATURE_SK_TX_STATUS | #else wiphy->features |= NL80211_FEATURE_SK_TX_STATUS | #endif NL80211_FEATURE_VIF_TXPOWER | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0) NL80211_FEATURE_ACTIVE_MONITOR | #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | #endif 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) wiphy->features |= NL80211_FEATURE_SAE; #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 20, 0) ecrnx_he_init(); #endif if (ecrnx_mod_params.tdls) /* TDLS support */ wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH; wiphy->iface_combinations = ecrnx_combinations; /* -1 not to include combination with radar detection, will be re-added in ecrnx_handle_dynparams if supported */ wiphy->n_iface_combinations = ARRAY_SIZE(ecrnx_combinations) - 1; wiphy->reg_notifier = ecrnx_reg_notifier; wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; wiphy->cipher_suites = cipher_suites; wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites) - NB_RESERVED_CIPHER; ecrnx_hw->ext_capa[0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING; ecrnx_hw->ext_capa[2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT; ecrnx_hw->ext_capa[4] = WLAN_EXT_CAPA5_QOS_MAP_SUPPORT; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) ecrnx_hw->ext_capa[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF; wiphy->extended_capabilities = ecrnx_hw->ext_capa; wiphy->extended_capabilities_mask = ecrnx_hw->ext_capa; wiphy->extended_capabilities_len = ARRAY_SIZE(ecrnx_hw->ext_capa); #endif #ifndef CONFIG_ECRNX_ESWIN tasklet_init(&ecrnx_hw->task, ecrnx_task, (unsigned long)ecrnx_hw); #endif INIT_LIST_HEAD(&ecrnx_hw->vifs); #ifdef CONFIG_ECRNX_ESWIN INIT_LIST_HEAD(&ecrnx_hw->agg_rx_list); INIT_LIST_HEAD(&ecrnx_hw->defrag_rx_list); #endif mutex_init(&ecrnx_hw->dbgdump_elem.mutex); spin_lock_init(&ecrnx_hw->tx_lock); spin_lock_init(&ecrnx_hw->cb_lock); spin_lock_init(&ecrnx_hw->rx_lock); spin_lock_init(&ecrnx_hw->scan_req_lock); spin_lock_init(&ecrnx_hw->connect_req_lock); if ((ret = ecrnx_platform_on(ecrnx_hw, NULL))) goto err_platon; if ((ret = ecrnx_get_cal_result(ecrnx_hw))) { wiphy_err(wiphy, "get cal result failed\n"); goto err_lmac_reqs; } else { if ((0 == (cal_result.mac_addr[0] & 0x1)) && (cal_result.mac_addr[0] || cal_result.mac_addr[1] || cal_result.mac_addr[2] || cal_result.mac_addr[3] || cal_result.mac_addr[4] || cal_result.mac_addr[5])) { memcpy(ecrnx_hw->conf_param.mac_addr, cal_result.mac_addr, ETH_ALEN); } } memcpy(wiphy->perm_addr, ecrnx_hw->conf_param.mac_addr, ETH_ALEN); /* Reset FW */ if ((ret = ecrnx_send_reset(ecrnx_hw))) goto err_lmac_reqs; if ((ret = ecrnx_send_version_req(ecrnx_hw, &ecrnx_hw->version_cfm))) goto err_lmac_reqs; ecrnx_set_vers(ecrnx_hw); if ((ret = ecrnx_handle_dynparams(ecrnx_hw, ecrnx_hw->wiphy))) goto err_lmac_reqs; ecrnx_enable_mesh(ecrnx_hw); ecrnx_radar_detection_init(&ecrnx_hw->radar); #ifdef CONFIG_ECRNX_P2P ecrnx_p2p_listen_init(&ecrnx_hw->p2p_listen); #endif /* Set parameters to firmware */ ecrnx_send_me_config_req(ecrnx_hw); /* Only monitor mode supported when custom channels are enabled */ if (ecrnx_mod_params.custchan) { ecrnx_limits[0].types = BIT(NL80211_IFTYPE_MONITOR); ecrnx_limits_dfs[0].types = BIT(NL80211_IFTYPE_MONITOR); } if ((ret = wiphy_register(wiphy))) { wiphy_err(wiphy, "Could not register wiphy device\n"); goto err_register_wiphy; } INIT_WORK(&ecrnx_hw->defer_rx.work, ecrnx_rx_deferred); skb_queue_head_init(&ecrnx_hw->defer_rx.sk_list); /* Update regulatory (if needed) and set channel parameters to firmware (must be done after WiPHY registration) */ ecrnx_fw_log_level_set((u32)ecrnx_hw->conf_param.fw_log_level, (u32)ecrnx_hw->conf_param.fw_log_type); ecrnx_custregd(ecrnx_hw, wiphy); ecrnx_send_me_chan_config_req(ecrnx_hw); /* config gain delta */ ecrnx_send_set_gain_delta_req(ecrnx_hw); #ifdef CONFIG_ECRNX_DEBUGFS if ((ret = ecrnx_dbgfs_register(ecrnx_hw, "ecrnx"))) { ECRNX_DBG(" ecrnx_dbgfs_register error \n"); wiphy_err(wiphy, "Failed to register debugfs entries"); goto err_debugfs; } #endif rtnl_lock(); /* Add an initial interface */ wdev = ecrnx_interface_add(ecrnx_hw, "wlan%d", NET_NAME_UNKNOWN, ecrnx_mod_params.custchan ? NL80211_IFTYPE_MONITOR : NL80211_IFTYPE_STATION, NULL); #if defined(CONFIG_ECRNX_P2P) wdev = ecrnx_interface_add(ecrnx_hw, "p2p%d", NET_NAME_UNKNOWN, NL80211_IFTYPE_STATION, NULL); #endif rtnl_unlock(); if (!wdev) { wiphy_err(wiphy, "Failed to instantiate a network device\n"); ret = -ENOMEM; goto err_add_interface; } wiphy_info(wiphy, "New interface create %s", wdev->netdev->name); #if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) ecrnx_debugfs_init(ecrnx_hw); #endif register_drv_done = true; return 0; err_add_interface: #ifdef CONFIG_ECRNX_DEBUGFS err_debugfs: #endif wiphy_unregister(ecrnx_hw->wiphy); err_register_wiphy: err_lmac_reqs: ecrnx_fw_trace_dump(ecrnx_hw); ecrnx_platform_off(ecrnx_hw, NULL); err_platon: err_config: kmem_cache_destroy(ecrnx_hw->sw_txhdr_cache); err_cache: wiphy_free(wiphy); err_out: ECRNX_DBG(" %s cfg80211 init failed %d!!", __func__, ret); return ret; } /** * */ void ecrnx_cfg80211_deinit(struct ecrnx_hw *ecrnx_hw) { ECRNX_DBG(ECRNX_FN_ENTRY_STR); if(!register_drv_done) { return; } #if 0 ecrnx_dbgfs_unregister(ecrnx_hw); #endif register_drv_done = false; del_timer_sync(&ecrnx_hw->txq_cleanup); ecrnx_wdev_unregister(ecrnx_hw); if(ecrnx_hw->wiphy) { ECRNX_DBG("%s wiphy_unregister \n", __func__); wiphy_unregister(ecrnx_hw->wiphy); wiphy_free(ecrnx_hw->wiphy); ecrnx_hw->wiphy = NULL; } ecrnx_radar_detection_deinit(&ecrnx_hw->radar); ecrnx_platform_off(ecrnx_hw, NULL); kmem_cache_destroy(ecrnx_hw->sw_txhdr_cache); } /** * */ static int __init ecrnx_mod_init(void) { ECRNX_DBG(ECRNX_FN_ENTRY_STR); ecrnx_print_version(); return ecrnx_platform_register_drv(); } /** * */ static void __exit ecrnx_mod_exit(void) { ECRNX_DBG(ECRNX_FN_ENTRY_STR); #if defined(CONFIG_ECRNX_DEBUGFS_CUSTOM) ecrnx_debugfs_exit(); #endif ecrnx_platform_unregister_drv(); } module_init(ecrnx_mod_init); module_exit(ecrnx_mod_exit); MODULE_FIRMWARE(ECRNX_CONFIG_FW_NAME); MODULE_DESCRIPTION(RW_DRV_DESCRIPTION); MODULE_VERSION(ECRNX_VERS_MOD); MODULE_AUTHOR(RW_DRV_COPYRIGHT " " RW_DRV_AUTHOR); MODULE_LICENSE("GPL");