From 61513162aa2d6c17c37d25de2d0e5020cd9b37ec Mon Sep 17 00:00:00 2001 From: Johannes Berg <johannes.berg@intel.com> Date: Tue, 12 Jul 2022 11:13:56 +0200 Subject: wifi: mac80211: mlme: shift some code around We'll need ieee80211_prep_channel() in other code for MLO later, so move the code up - unchanged for now - to avoid forward declarations in the future. Signed-off-by: Johannes Berg <johannes.berg@intel.com> --- net/mac80211/mlme.c | 3634 ++++++++++++++++++++++++++------------------------- 1 file changed, 1825 insertions(+), 1809 deletions(-) (limited to 'net/mac80211') diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 0d43b20d6e6f..43b64ec6e9df 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -3546,2195 +3546,2211 @@ static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link, return 0; } -static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, - struct cfg80211_bss *cbss, - struct ieee80211_mgmt *mgmt, size_t len, - struct ieee802_11_elems *elems) +static u8 ieee80211_max_rx_chains(struct ieee80211_link_data *link, + struct cfg80211_bss *cbss) { - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - struct ieee80211_local *local = sdata->local; - struct ieee80211_supported_band *sband; - struct link_sta_info *link_sta; - struct sta_info *sta; - u16 capab_info, aid; - struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; - const struct cfg80211_bss_ies *bss_ies = NULL; - struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; - bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; - bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ; - struct ieee80211_link_data *link = &sdata->deflink; - u32 changed = 0; - int err; - bool ret; + struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp; + const struct element *ht_cap_elem, *vht_cap_elem; + const struct cfg80211_bss_ies *ies; + const struct ieee80211_ht_cap *ht_cap; + const struct ieee80211_vht_cap *vht_cap; + const struct ieee80211_he_cap_elem *he_cap; + const struct element *he_cap_elem; + u16 mcs_80_map, mcs_160_map; + int i, mcs_nss_size; + bool support_160; + u8 chains = 1; - capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info); + if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) + return chains; - if (elems->aid_resp) - aid = le16_to_cpu(elems->aid_resp->aid); - else if (is_s1g) - aid = 0; /* TODO */ - else - aid = le16_to_cpu(mgmt->u.assoc_resp.aid); + ht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_HT_CAPABILITY); + if (ht_cap_elem && ht_cap_elem->datalen >= sizeof(*ht_cap)) { + ht_cap = (void *)ht_cap_elem->data; + chains = ieee80211_mcs_to_chains(&ht_cap->mcs); + /* + * TODO: use "Tx Maximum Number Spatial Streams Supported" and + * "Tx Unequal Modulation Supported" fields. + */ + } - /* - * The 5 MSB of the AID field are reserved - * (802.11-2016 9.4.1.8 AID field) - */ - aid &= 0x7ff; + if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) + return chains; - ifmgd->broken_ap = false; + vht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_VHT_CAPABILITY); + if (vht_cap_elem && vht_cap_elem->datalen >= sizeof(*vht_cap)) { + u8 nss; + u16 tx_mcs_map; - if (aid == 0 || aid > IEEE80211_MAX_AID) { - sdata_info(sdata, "invalid AID value %d (out of range), turn off PS\n", - aid); - aid = 0; - ifmgd->broken_ap = true; + vht_cap = (void *)vht_cap_elem->data; + tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); + for (nss = 8; nss > 0; nss--) { + if (((tx_mcs_map >> (2 * (nss - 1))) & 3) != + IEEE80211_VHT_MCS_NOT_SUPPORTED) + break; + } + /* TODO: use "Tx Highest Supported Long GI Data Rate" field? */ + chains = max(chains, nss); } - if (!is_s1g && !elems->supp_rates) { - sdata_info(sdata, "no SuppRates element in AssocResp\n"); - ret = false; - goto out; - } + if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) + return chains; - sdata->vif.cfg.aid = aid; - sdata->deflink.u.mgd.tdls_chan_switch_prohibited = - elems->ext_capab && elems->ext_capab_len >= 5 && - (elems->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED); + ies = rcu_dereference(cbss->ies); + he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, + ies->data, ies->len); - /* - * Some APs are erroneously not including some information in their - * (re)association response frames. Try to recover by using the data - * from the beacon or probe response. This seems to afflict mobile - * 2G/3G/4G wifi routers, reported models include the "Onda PN51T", - * "Vodafone PocketWiFi 2", "ZTE MF60" and a similar T-Mobile device. - */ - if (!is_6ghz && - ((assoc_data->wmm && !elems->wmm_param) || - (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && - (!elems->ht_cap_elem || !elems->ht_operation)) || - (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && - (!elems->vht_cap_elem || !elems->vht_operation)))) { - const struct cfg80211_bss_ies *ies; - struct ieee802_11_elems *bss_elems; + if (!he_cap_elem || he_cap_elem->datalen < sizeof(*he_cap)) + return chains; - rcu_read_lock(); - ies = rcu_dereference(cbss->ies); - if (ies) - bss_ies = kmemdup(ies, sizeof(*ies) + ies->len, - GFP_ATOMIC); - rcu_read_unlock(); - if (!bss_ies) { - ret = false; - goto out; - } + /* skip one byte ext_tag_id */ + he_cap = (void *)(he_cap_elem->data + 1); + mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap); - bss_elems = ieee802_11_parse_elems(bss_ies->data, bss_ies->len, - false, assoc_data->bss); - if (!bss_elems) { - ret = false; - goto out; - } + /* invalid HE IE */ + if (he_cap_elem->datalen < 1 + mcs_nss_size + sizeof(*he_cap)) + return chains; - if (assoc_data->wmm && - !elems->wmm_param && bss_elems->wmm_param) { - elems->wmm_param = bss_elems->wmm_param; - sdata_info(sdata, - "AP bug: WMM param missing from AssocResp\n"); - } + /* mcs_nss is right after he_cap info */ + he_mcs_nss_supp = (void *)(he_cap + 1); - /* - * Also check if we requested HT/VHT, otherwise the AP doesn't - * have to include the IEs in the (re)association response. - */ - if (!elems->ht_cap_elem && bss_elems->ht_cap_elem && - !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) { - elems->ht_cap_elem = bss_elems->ht_cap_elem; - sdata_info(sdata, - "AP bug: HT capability missing from AssocResp\n"); - } - if (!elems->ht_operation && bss_elems->ht_operation && - !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) { - elems->ht_operation = bss_elems->ht_operation; - sdata_info(sdata, - "AP bug: HT operation missing from AssocResp\n"); - } - if (!elems->vht_cap_elem && bss_elems->vht_cap_elem && - !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) { - elems->vht_cap_elem = bss_elems->vht_cap_elem; - sdata_info(sdata, - "AP bug: VHT capa missing from AssocResp\n"); - } - if (!elems->vht_operation && bss_elems->vht_operation && - !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) { - elems->vht_operation = bss_elems->vht_operation; - sdata_info(sdata, - "AP bug: VHT operation missing from AssocResp\n"); - } + mcs_80_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80); - kfree(bss_elems); + for (i = 7; i >= 0; i--) { + u8 mcs_80 = mcs_80_map >> (2 * i) & 3; + + if (mcs_80 != IEEE80211_VHT_MCS_NOT_SUPPORTED) { + chains = max_t(u8, chains, i + 1); + break; + } } - /* - * We previously checked these in the beacon/probe response, so - * they should be present here. This is just a safety net. - */ - if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && - (!elems->wmm_param || !elems->ht_cap_elem || !elems->ht_operation)) { - sdata_info(sdata, - "HT AP is missing WMM params or HT capability/operation\n"); - ret = false; - goto out; + support_160 = he_cap->phy_cap_info[0] & + IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + + if (!support_160) + return chains; + + mcs_160_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_160); + for (i = 7; i >= 0; i--) { + u8 mcs_160 = mcs_160_map >> (2 * i) & 3; + + if (mcs_160 != IEEE80211_VHT_MCS_NOT_SUPPORTED) { + chains = max_t(u8, chains, i + 1); + break; + } } - if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && - (!elems->vht_cap_elem || !elems->vht_operation)) { + return chains; +} + +static bool +ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_bss_ies *ies, + const struct ieee80211_he_operation *he_op) +{ + const struct element *he_cap_elem; + const struct ieee80211_he_cap_elem *he_cap; + struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp; + u16 mcs_80_map_tx, mcs_80_map_rx; + u16 ap_min_req_set; + int mcs_nss_size; + int nss; + + he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, + ies->data, ies->len); + + /* invalid HE IE */ + if (!he_cap_elem || he_cap_elem->datalen < 1 + sizeof(*he_cap)) { sdata_info(sdata, - "VHT AP is missing VHT capability/operation\n"); - ret = false; - goto out; + "Invalid HE elem, Disable HE\n"); + return false; } - if (is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && - !elems->he_6ghz_capa) { + /* skip one byte ext_tag_id */ + he_cap = (void *)(he_cap_elem->data + 1); + mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap); + + /* invalid HE IE */ + if (he_cap_elem->datalen < 1 + sizeof(*he_cap) + mcs_nss_size) { sdata_info(sdata, - "HE 6 GHz AP is missing HE 6 GHz band capability\n"); - ret = false; - goto out; + "Invalid HE elem with nss size, Disable HE\n"); + return false; } - mutex_lock(&sdata->local->sta_mtx); - /* - * station info was already allocated and inserted before - * the association and should be available to us - */ - sta = sta_info_get(sdata, cbss->bssid); - if (WARN_ON(!sta)) { - mutex_unlock(&sdata->local->sta_mtx); - ret = false; - goto out; - } + /* mcs_nss is right after he_cap info */ + he_mcs_nss_supp = (void *)(he_cap + 1); - link_sta = rcu_dereference_protected(sta->link[link->link_id], - lockdep_is_held(&local->sta_mtx)); - if (WARN_ON(!link_sta)) { - mutex_unlock(&sdata->local->sta_mtx); - ret = false; - goto out; - } - - sband = ieee80211_get_link_sband(link); - if (!sband) { - mutex_unlock(&sdata->local->sta_mtx); - ret = false; - goto out; - } + mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80); + mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80); - if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && - (!elems->he_cap || !elems->he_operation)) { - mutex_unlock(&sdata->local->sta_mtx); + /* P802.11-REVme/D0.3 + * 27.1.1 Introduction to the HE PHY + * ... + * An HE STA shall support the following features: + * ... + * Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all + * supported channel widths for HE SU PPDUs + */ + if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED || + (mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) { sdata_info(sdata, - "HE AP is missing HE capability/operation\n"); - ret = false; - goto out; + "Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n", + mcs_80_map_tx, mcs_80_map_rx); + return false; } - /* Set up internal HT/VHT capabilities */ - if (elems->ht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) - ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband, - elems->ht_cap_elem, - link_sta); + if (!he_op) + return true; - if (elems->vht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) - ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband, - elems->vht_cap_elem, - link_sta); + ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set); - if (elems->he_operation && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && - elems->he_cap) { - ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, - elems->he_cap, - elems->he_cap_len, - elems->he_6ghz_capa, - link_sta); + /* + * Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all + * zeroes, which is nonsense, and completely inconsistent with itself + * (it doesn't have 8 streams). Accept the settings in this case anyway. + */ + if (!ap_min_req_set) + return true; - bss_conf->he_support = link_sta->pub->he_cap.has_he; - if (elems->rsnx && elems->rsnx_len && - (elems->rsnx[0] & WLAN_RSNX_CAPA_PROTECTED_TWT) && - wiphy_ext_feature_isset(local->hw.wiphy, - NL80211_EXT_FEATURE_PROTECTED_TWT)) - bss_conf->twt_protected = true; - else - bss_conf->twt_protected = false; + /* make sure the AP is consistent with itself + * + * P802.11-REVme/D0.3 + * 26.17.1 Basic HE BSS operation + * + * A STA that is operating in an HE BSS shall be able to receive and + * transmit at each of the <HE-MCS, NSS> tuple values indicated by the + * Basic HE-MCS And NSS Set field of the HE Operation parameter of the + * MLME-START.request primitive and shall be able to receive at each of + * the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and + * NSS Set field in the HE Capabilities parameter of the MLMESTART.request + * primitive + */ + for (nss = 8; nss > 0; nss--) { + u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; + u8 ap_rx_val; + u8 ap_tx_val; - changed |= ieee80211_recalc_twt_req(link, link_sta, elems); + if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED) + continue; - if (elems->eht_operation && elems->eht_cap && - !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_EHT)) { - ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, - elems->he_cap, - elems->he_cap_len, - elems->eht_cap, - elems->eht_cap_len, - link_sta); + ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3; + ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3; - bss_conf->eht_support = link_sta->pub->eht_cap.has_eht; - } else { - bss_conf->eht_support = false; + if (ap_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || + ap_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || + ap_rx_val < ap_op_val || ap_tx_val < ap_op_val) { + sdata_info(sdata, + "Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n", + nss, ap_rx_val, ap_rx_val, ap_op_val); + return false; } - } else { - bss_conf->he_support = false; - bss_conf->twt_requester = false; - bss_conf->twt_protected = false; - bss_conf->eht_support = false; } - bss_conf->twt_broadcast = - ieee80211_twt_bcast_support(sdata, bss_conf, sband, link_sta); - - if (bss_conf->he_support) { - bss_conf->he_bss_color.color = - le32_get_bits(elems->he_operation->he_oper_params, - IEEE80211_HE_OPERATION_BSS_COLOR_MASK); - bss_conf->he_bss_color.partial = - le32_get_bits(elems->he_operation->he_oper_params, - IEEE80211_HE_OPERATION_PARTIAL_BSS_COLOR); - bss_conf->he_bss_color.enabled = - !le32_get_bits(elems->he_operation->he_oper_params, - IEEE80211_HE_OPERATION_BSS_COLOR_DISABLED); - - if (bss_conf->he_bss_color.enabled) - changed |= BSS_CHANGED_HE_BSS_COLOR; - - bss_conf->htc_trig_based_pkt_ext = - le32_get_bits(elems->he_operation->he_oper_params, - IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK); - bss_conf->frame_time_rts_th = - le32_get_bits(elems->he_operation->he_oper_params, - IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK); + return true; +} - bss_conf->uora_exists = !!elems->uora_element; - if (elems->uora_element) - bss_conf->uora_ocw_range = elems->uora_element[0]; +static bool +ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata, + struct ieee80211_supported_band *sband, + const struct ieee80211_he_operation *he_op) +{ + const struct ieee80211_sta_he_cap *sta_he_cap = + ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(&sdata->vif)); + u16 ap_min_req_set; + int i; - ieee80211_he_op_ie_to_bss_conf(&sdata->vif, elems->he_operation); - ieee80211_he_spr_ie_to_bss_conf(&sdata->vif, elems->he_spr); - /* TODO: OPEN: what happens if BSS color disable is set? */ - } + if (!sta_he_cap || !he_op) + return false; - if (cbss->transmitted_bss) { - bss_conf->nontransmitted = true; - ether_addr_copy(bss_conf->transmitter_bssid, - cbss->transmitted_bss->bssid); - bss_conf->bssid_indicator = cbss->max_bssid_indicator; - bss_conf->bssid_index = cbss->bssid_index; - } else { - bss_conf->nontransmitted = false; - memset(bss_conf->transmitter_bssid, 0, - sizeof(bss_conf->transmitter_bssid)); - bss_conf->bssid_indicator = 0; - bss_conf->bssid_index = 0; - } + ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set); /* - * Some APs, e.g. Netgear WNDR3700, report invalid HT operation data - * in their association response, so ignore that data for our own - * configuration. If it changed since the last beacon, we'll get the - * next beacon and update then. + * Apparently iPhone 13 (at least iOS version 15.3.1) sets this to all + * zeroes, which is nonsense, and completely inconsistent with itself + * (it doesn't have 8 streams). Accept the settings in this case anyway. */ + if (!ap_min_req_set) + return true; - /* - * If an operating mode notification IE is present, override the - * NSS calculation (that would be done in rate_control_rate_init()) - * and use the # of streams from that element. - */ - if (elems->opmode_notif && - !(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) { + /* Need to go over for 80MHz, 160MHz and for 80+80 */ + for (i = 0; i < 3; i++) { + const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp = + &sta_he_cap->he_mcs_nss_supp; + u16 sta_mcs_map_rx = + le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]); + u16 sta_mcs_map_tx = + le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]); u8 nss; + bool verified = true; - nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK; - nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT; - nss += 1; - link_sta->pub->rx_nss = nss; - } - - rate_control_rate_init(sta); + /* + * For each band there is a maximum of 8 spatial streams + * possible. Each of the sta_mcs_map_* is a 16-bit struct built + * of 2 bits per NSS (1-8), with the values defined in enum + * ieee80211_he_mcs_support. Need to make sure STA TX and RX + * capabilities aren't less than the AP's minimum requirements + * for this HE BSS per SS. + * It is enough to find one such band that meets the reqs. + */ + for (nss = 8; nss > 0; nss--) { + u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3; + u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3; + u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; - if (ifmgd->flags & IEEE80211_STA_MFP_ENABLED) { - set_sta_flag(sta, WLAN_STA_MFP); - sta->sta.mfp = true; - } else { - sta->sta.mfp = false; - } + if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED) + continue; - sta->sta.wme = (elems->wmm_param || elems->s1g_capab) && - local->hw.queues >= IEEE80211_NUM_ACS; + /* + * Make sure the HE AP doesn't require MCSs that aren't + * supported by the client as required by spec + * + * P802.11-REVme/D0.3 + * 26.17.1 Basic HE BSS operation + * + * An HE STA shall not attempt to join * (MLME-JOIN.request primitive) + * a BSS, unless it supports (i.e., is able to both transmit and + * receive using) all of the <HE-MCS, NSS> tuples in the basic + * HE-MCS and NSS set. + */ + if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || + sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || + (ap_val > sta_rx_val) || (ap_val > sta_tx_val)) { + verified = false; + break; + } + } - err = sta_info_move_state(sta, IEEE80211_STA_ASSOC); - if (!err && !(ifmgd->flags & IEEE80211_STA_CONTROL_PORT)) - err = sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED); - if (err) { - sdata_info(sdata, - "failed to move station %pM to desired state\n", - sta->sta.addr); - WARN_ON(__sta_info_destroy(sta)); - mutex_unlock(&sdata->local->sta_mtx); - ret = false; - goto out; + if (verified) + return true; } - if (sdata->wdev.use_4addr) - drv_sta_set_4addr(local, sdata, &sta->sta, true); - - mutex_unlock(&sdata->local->sta_mtx); + /* If here, STA doesn't meet AP's HE min requirements */ + return false; +} - /* - * Always handle WMM once after association regardless - * of the first value the AP uses. Setting -1 here has - * that effect because the AP values is an unsigned - * 4-bit value. - */ - link->u.mgd.wmm_last_param_set = -1; - link->u.mgd.mu_edca_last_param_set = -1; +static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_link_data *link, + struct cfg80211_bss *cbss) +{ + struct ieee80211_local *local = sdata->local; + const struct ieee80211_ht_cap *ht_cap = NULL; + const struct ieee80211_ht_operation *ht_oper = NULL; + const struct ieee80211_vht_operation *vht_oper = NULL; + const struct ieee80211_he_operation *he_oper = NULL; + const struct ieee80211_eht_operation *eht_oper = NULL; + const struct ieee80211_s1g_oper_ie *s1g_oper = NULL; + struct ieee80211_supported_band *sband; + struct cfg80211_chan_def chandef; + bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; + bool is_5ghz = cbss->channel->band == NL80211_BAND_5GHZ; + struct ieee80211_bss *bss = (void *)cbss->priv; + struct ieee802_11_elems *elems; + const struct cfg80211_bss_ies *ies; + int ret; + u32 i; + bool have_80mhz; - if (link->u.mgd.disable_wmm_tracking) { - ieee80211_set_wmm_default(link, false, false); - } else if (!ieee80211_sta_wmm_params(local, link, elems->wmm_param, - elems->wmm_param_len, - elems->mu_edca_param_set)) { - /* still enable QoS since we might have HT/VHT */ - ieee80211_set_wmm_default(link, false, true); - /* disable WMM tracking in this case to disable - * tracking WMM parameter changes in the beacon if - * the parameters weren't actually valid. Doing so - * avoids changing parameters very strangely when - * the AP is going back and forth between valid and - * invalid parameters. - */ - link->u.mgd.disable_wmm_tracking = true; + rcu_read_lock(); + + ies = rcu_dereference(cbss->ies); + elems = ieee802_11_parse_elems(ies->data, ies->len, false, cbss); + if (!elems) { + rcu_read_unlock(); + return -ENOMEM; } - changed |= BSS_CHANGED_QOS; - if (elems->max_idle_period_ie) { - bss_conf->max_idle_period = - le16_to_cpu(elems->max_idle_period_ie->max_idle_period); - bss_conf->protected_keep_alive = - !!(elems->max_idle_period_ie->idle_options & - WLAN_IDLE_OPTIONS_PROTECTED_KEEP_ALIVE); - changed |= BSS_CHANGED_KEEP_ALIVE; - } else { - bss_conf->max_idle_period = 0; - bss_conf->protected_keep_alive = false; + sband = local->hw.wiphy->bands[cbss->channel->band]; + + link->u.mgd.conn_flags &= ~(IEEE80211_CONN_DISABLE_40MHZ | + IEEE80211_CONN_DISABLE_80P80MHZ | + IEEE80211_CONN_DISABLE_160MHZ); + + /* disable HT/VHT/HE if we don't support them */ + if (!sband->ht_cap.ht_supported && !is_6ghz) { + mlme_dbg(sdata, "HT not supported, disabling HT/VHT/HE/EHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; } - /* set assoc capability (AID was already set earlier), - * ieee80211_set_associated() will tell the driver */ - bss_conf->assoc_capability = capab_info; - ieee80211_set_associated(sdata, cbss, changed); + if (!sband->vht_cap.vht_supported && is_5ghz) { + mlme_dbg(sdata, "VHT not supported, disabling VHT/HE/EHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; + } - /* - * If we're using 4-addr mode, let the AP know that we're - * doing so, so that it can create the STA VLAN on its side - */ - if (ifmgd->use_4addr) - ieee80211_send_4addr_nullfunc(local, sdata); + if (!ieee80211_get_he_iftype_cap(sband, + ieee80211_vif_type_p2p(&sdata->vif))) { + mlme_dbg(sdata, "HE not supported, disabling HE and EHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; + } - /* - * Start timer to probe the connection to the AP now. - * Also start the timer that will detect beacon loss. - */ - ieee80211_sta_reset_beacon_monitor(sdata); - ieee80211_sta_reset_conn_monitor(sdata); + if (!ieee80211_get_eht_iftype_cap(sband, + ieee80211_vif_type_p2p(&sdata->vif))) { + mlme_dbg(sdata, "EHT not supported, disabling EHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; + } - ret = true; - out: - kfree(bss_ies); - return ret; -} + if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && !is_6ghz) { + ht_oper = elems->ht_operation; + ht_cap = elems->ht_cap_elem; -static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, - struct ieee80211_mgmt *mgmt, - size_t len) -{ - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; - u16 capab_info, status_code, aid; - struct ieee802_11_elems *elems; - int ac; - u8 *pos; - bool reassoc; - struct cfg80211_bss *cbss; - struct ieee80211_event event = { - .type = MLME_EVENT, - .u.mlme.data = ASSOC_EVENT, - }; - struct ieee80211_prep_tx_info info = {}; - struct cfg80211_rx_assoc_resp resp = { - .uapsd_queues = -1, - }; + if (!ht_cap) { + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; + ht_oper = NULL; + } + } - sdata_assert_lock(sdata); + if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && !is_6ghz) { + vht_oper = elems->vht_operation; + if (vht_oper && !ht_oper) { + vht_oper = NULL; + sdata_info(sdata, + "AP advertised VHT without HT, disabling HT/VHT/HE\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; + } - if (!assoc_data) - return; + if (!elems->vht_cap_elem) { + sdata_info(sdata, + "bad VHT capabilities, disabling VHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + vht_oper = NULL; + } + } - if (!ether_addr_equal(assoc_data->bss->bssid, mgmt->bssid)) - return; + if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE)) { + he_oper = elems->he_operation; - cbss = assoc_data->bss; + if (is_6ghz) { + struct ieee80211_bss_conf *bss_conf; + u8 j = 0; - /* - * AssocResp and ReassocResp have identical structure, so process both - * of them in this function. - */ + bss_conf = link->conf; - if (len < 24 + 6) - return; + if (elems->pwr_constr_elem) + bss_conf->pwr_reduction = *elems->pwr_constr_elem; - reassoc = ieee80211_is_reassoc_resp(mgmt->frame_control); - capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info); - status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code); - pos = mgmt->u.assoc_resp.variable; - aid = le16_to_cpu(mgmt->u.assoc_resp.aid); - if (cbss->channel->band == NL80211_BAND_S1GHZ) { - pos = (u8 *) mgmt->u.s1g_assoc_resp.variable; - aid = 0; /* TODO */ + BUILD_BUG_ON(ARRAY_SIZE(bss_conf->tx_pwr_env) != + ARRAY_SIZE(elems->tx_pwr_env)); + + for (i = 0; i < elems->tx_pwr_env_num; i++) { + if (elems->tx_pwr_env_len[i] > + sizeof(bss_conf->tx_pwr_env[j])) + continue; + + bss_conf->tx_pwr_env_num++; + memcpy(&bss_conf->tx_pwr_env[j], elems->tx_pwr_env[i], + elems->tx_pwr_env_len[i]); + j++; + } + } + + if (!ieee80211_verify_peer_he_mcs_support(sdata, ies, he_oper) || + !ieee80211_verify_sta_he_mcs_support(sdata, sband, he_oper)) + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE | + IEEE80211_CONN_DISABLE_EHT; } /* - * Note: this may not be perfect, AP might misbehave - if - * anyone needs to rely on perfect complete notification - * with the exact right subtype, then we need to track what - * we actually transmitted. + * EHT requires HE to be supported as well. Specifically for 6 GHz + * channels, the operation channel information can only be deduced from + * both the 6 GHz operation information (from the HE operation IE) and + * EHT operation. */ - info.subtype = reassoc ? IEEE80211_STYPE_REASSOC_REQ : - IEEE80211_STYPE_ASSOC_REQ; - - sdata_info(sdata, - "RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n", - reassoc ? "Rea" : "A", mgmt->sa, - capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14)))); + if (!(link->u.mgd.conn_flags & + (IEEE80211_CONN_DISABLE_HE | + IEEE80211_CONN_DISABLE_EHT)) && + he_oper) { + const struct cfg80211_bss_ies *cbss_ies; + const u8 *eht_oper_ie; - if (assoc_data->fils_kek_len && - fils_decrypt_assoc_resp(sdata, (u8 *)mgmt, &len, assoc_data) < 0) - return; + cbss_ies = rcu_dereference(cbss->ies); + eht_oper_ie = cfg80211_find_ext_ie(WLAN_EID_EXT_EHT_OPERATION, + cbss_ies->data, cbss_ies->len); + if (eht_oper_ie && eht_oper_ie[1] >= + 1 + sizeof(struct ieee80211_eht_operation)) + eht_oper = (void *)(eht_oper_ie + 3); + else + eht_oper = NULL; + } - elems = ieee802_11_parse_elems(pos, len - (pos - (u8 *)mgmt), false, - assoc_data->bss); - if (!elems) - goto notify_driver; + /* Allow VHT if at least one channel on the sband supports 80 MHz */ + have_80mhz = false; + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_NO_80MHZ)) + continue; - if (status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY && - elems->timeout_int && - elems->timeout_int->type == WLAN_TIMEOUT_ASSOC_COMEBACK) { - u32 tu, ms; + have_80mhz = true; + break; + } - cfg80211_assoc_comeback(sdata->dev, assoc_data->bss->bssid, - le32_to_cpu(elems->timeout_int->value)); + if (!have_80mhz) { + sdata_info(sdata, "80 MHz not supported, disabling VHT\n"); + link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + } - tu = le32_to_cpu(elems->timeout_int->value); - ms = tu * 1024 / 1000; - sdata_info(sdata, - "%pM rejected association temporarily; comeback duration %u TU (%u ms)\n", - mgmt->sa, tu, ms); - assoc_data->timeout = jiffies + msecs_to_jiffies(ms); - assoc_data->timeout_started = true; - if (ms > IEEE80211_ASSOC_TIMEOUT) - run_again(sdata, assoc_data->timeout); - goto notify_driver; + if (sband->band == NL80211_BAND_S1GHZ) { + s1g_oper = elems->s1g_oper; + if (!s1g_oper) + sdata_info(sdata, + "AP missing S1G operation element?\n"); } - if (status_code != WLAN_STATUS_SUCCESS) { - sdata_info(sdata, "%pM denied association (code=%d)\n", - mgmt->sa, status_code); - ieee80211_destroy_assoc_data(sdata, ASSOC_REJECTED); - event.u.mlme.status = MLME_DENIED; - event.u.mlme.reason = status_code; - drv_event_callback(sdata->local, sdata, &event); - } else { - if (!ieee80211_assoc_success(sdata, cbss, mgmt, len, elems)) { - /* oops -- internal error -- send timeout for now */ - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); - goto notify_driver; - } - event.u.mlme.status = MLME_SUCCESS; - drv_event_callback(sdata->local, sdata, &event); - sdata_info(sdata, "associated\n"); + link->u.mgd.conn_flags |= + ieee80211_determine_chantype(link, sband, + cbss->channel, + bss->vht_cap_info, + ht_oper, vht_oper, + he_oper, eht_oper, + s1g_oper, + &chandef, false); - /* - * destroy assoc_data afterwards, as otherwise an idle - * recalc after assoc_data is NULL but before associated - * is set can cause the interface to go idle - */ - ieee80211_destroy_assoc_data(sdata, ASSOC_SUCCESS); + link->needed_rx_chains = + min(ieee80211_max_rx_chains(link, cbss), local->rx_chains); - /* get uapsd queues configuration */ - resp.uapsd_queues = 0; - for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) - if (sdata->deflink.tx_conf[ac].uapsd) - resp.uapsd_queues |= ieee80211_ac_to_qos_mask[ac]; + rcu_read_unlock(); + /* the element data was RCU protected so no longer valid anyway */ + kfree(elems); + elems = NULL; - info.success = 1; + if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) { + sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection"); + return -EINVAL; } - resp.links[0].bss = cbss; - resp.buf = (u8 *)mgmt; - resp.len = len; - resp.req_ies = ifmgd->assoc_req_ies; - resp.req_ies_len = ifmgd->assoc_req_ies_len; - cfg80211_rx_assoc_resp(sdata->dev, &resp); -notify_driver: - drv_mgd_complete_tx(sdata->local, sdata, &info); - kfree(elems); -} - -static void ieee80211_rx_bss_info(struct ieee80211_link_data *link, - struct ieee80211_mgmt *mgmt, size_t len, - struct ieee80211_rx_status *rx_status) -{ - struct ieee80211_sub_if_data *sdata = link->sdata; - struct ieee80211_local *local = sdata->local; - struct ieee80211_bss *bss; - struct ieee80211_channel *channel; + /* will change later if needed */ + link->smps_mode = IEEE80211_SMPS_OFF; - sdata_assert_lock(sdata); + mutex_lock(&local->mtx); + /* + * If this fails (possibly due to channel context sharing + * on incompatible channels, e.g. 80+80 and 160 sharing the + * same control channel) try to use a smaller bandwidth. + */ + ret = ieee80211_link_use_channel(link, &chandef, + IEEE80211_CHANCTX_SHARED); - channel = ieee80211_get_channel_khz(local->hw.wiphy, - ieee80211_rx_status_to_khz(rx_status)); - if (!channel) - return; + /* don't downgrade for 5 and 10 MHz channels, though. */ + if (chandef.width == NL80211_CHAN_WIDTH_5 || + chandef.width == NL80211_CHAN_WIDTH_10) + goto out; - bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, channel); - if (bss) { - link->conf->beacon_rate = bss->beacon_rate; - ieee80211_rx_bss_put(local, bss); + while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) { + link->u.mgd.conn_flags |= + ieee80211_chandef_downgrade(&chandef); + ret = ieee80211_link_use_channel(link, &chandef, + IEEE80211_CHANCTX_SHARED); } + out: + mutex_unlock(&local->mtx); + return ret; } - -static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_link_data *link, - struct sk_buff *skb) +static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, + struct cfg80211_bss *cbss, + struct ieee80211_mgmt *mgmt, size_t len, + struct ieee802_11_elems *elems) { - struct ieee80211_sub_if_data *sdata = link->sdata; - struct ieee80211_mgmt *mgmt = (void *)skb->data; - struct ieee80211_if_managed *ifmgd; - struct ieee80211_rx_status *rx_status = (void *) skb->cb; - struct ieee80211_channel *channel; - size_t baselen, len = skb->len; + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_local *local = sdata->local; + struct ieee80211_supported_band *sband; + struct link_sta_info *link_sta; + struct sta_info *sta; + u16 capab_info, aid; + struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; + const struct cfg80211_bss_ies *bss_ies = NULL; + struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; + bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; + bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ; + struct ieee80211_link_data *link = &sdata->deflink; + u32 changed = 0; + int err; + bool ret; - ifmgd = &sdata->u.mgd; + capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info); - sdata_assert_lock(sdata); + if (elems->aid_resp) + aid = le16_to_cpu(elems->aid_resp->aid); + else if (is_s1g) + aid = 0; /* TODO */ + else + aid = le16_to_cpu(mgmt->u.assoc_resp.aid); /* - * According to Draft P802.11ax D6.0 clause 26.17.2.3.2: - * "If a 6 GHz AP receives a Probe Request frame and responds with - * a Probe Response frame [..], the Address 1 field of the Probe - * Response frame shall be set to the broadcast address [..]" - * So, on 6GHz band we should also accept broadcast responses. + * The 5 MSB of the AID field are reserved + * (802.11-2016 9.4.1.8 AID field) */ - channel = ieee80211_get_channel(sdata->local->hw.wiphy, - rx_status->freq); - if (!channel) - return; + aid &= 0x7ff; - if (!ether_addr_equal(mgmt->da, sdata->vif.addr) && - (channel->band != NL80211_BAND_6GHZ || - !is_broadcast_ether_addr(mgmt->da))) - return; /* ignore ProbeResp to foreign address */ + ifmgd->broken_ap = false; - baselen = (u8 *) mgmt->u.probe_resp.variable - (u8 *) mgmt; - if (baselen > len) - return; + if (aid == 0 || aid > IEEE80211_MAX_AID) { + sdata_info(sdata, "invalid AID value %d (out of range), turn off PS\n", + aid); + aid = 0; + ifmgd->broken_ap = true; + } - ieee80211_rx_bss_info(link, mgmt, len, rx_status); + if (!is_s1g && !elems->supp_rates) { + sdata_info(sdata, "no SuppRates element in AssocResp\n"); + ret = false; + goto out; + } - if (ifmgd->associated && - ether_addr_equal(mgmt->bssid, link->u.mgd.bssid)) - ieee80211_reset_ap_probe(sdata); -} + sdata->vif.cfg.aid = aid; + sdata->deflink.u.mgd.tdls_chan_switch_prohibited = + elems->ext_capab && elems->ext_capab_len >= 5 && + (elems->ext_capab[4] & WLAN_EXT_CAPA5_TDLS_CH_SW_PROHIBITED); -/* - * This is the canonical list of information elements we care about, - * the filter code also gives us all changes to the Microsoft OUI - * (00:50:F2) vendor IE which is used for WMM which we need to track, - * as well as the DTPC IE (part of the Cisco OUI) used for signaling - * changes to requested client power. - * - * We implement beacon filtering in software since that means we can - * avoid processing the frame here and in cfg80211, and userspace - * will not be able to tell whether the hardware supports it or not. - * - * XXX: This list needs to be dynamic -- userspace needs to be able to - * add items it requires. It also needs to be able to tell us to - * look out for other vendor IEs. - */ -static const u64 care_about_ies = - (1ULL << WLAN_EID_COUNTRY) | - (1ULL << WLAN_EID_ERP_INFO) | - (1ULL << WLAN_EID_CHANNEL_SWITCH) | - (1ULL << WLAN_EID_PWR_CONSTRAINT) | - (1ULL << WLAN_EID_HT_CAPABILITY) | - (1ULL << WLAN_EID_HT_OPERATION) | - (1ULL << WLAN_EID_EXT_CHANSWITCH_ANN); + /* + * Some APs are erroneously not including some information in their + * (re)association response frames. Try to recover by using the data + * from the beacon or probe response. This seems to afflict mobile + * 2G/3G/4G wifi routers, reported models include the "Onda PN51T", + * "Vodafone PocketWiFi 2", "ZTE MF60" and a similar T-Mobile device. + */ + if (!is_6ghz && + ((assoc_data->wmm && !elems->wmm_param) || + (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && + (!elems->ht_cap_elem || !elems->ht_operation)) || + (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && + (!elems->vht_cap_elem || !elems->vht_operation)))) { + const struct cfg80211_bss_ies *ies; + struct ieee802_11_elems *bss_elems; -static void ieee80211_handle_beacon_sig(struct ieee80211_link_data *link, - struct ieee80211_if_managed *ifmgd, - struct ieee80211_bss_conf *bss_conf, - struct ieee80211_local *local, - struct ieee80211_rx_status *rx_status) -{ - struct ieee80211_sub_if_data *sdata = link->sdata; + rcu_read_lock(); + ies = rcu_dereference(cbss->ies); + if (ies) + bss_ies = kmemdup(ies, sizeof(*ies) + ies->len, + GFP_ATOMIC); + rcu_read_unlock(); + if (!bss_ies) { + ret = false; + goto out; + } - /* Track average RSSI from the Beacon frames of the current AP */ + bss_elems = ieee802_11_parse_elems(bss_ies->data, bss_ies->len, + false, assoc_data->bss); + if (!bss_elems) { + ret = false; + goto out; + } - if (!link->u.mgd.tracking_signal_avg) { - link->u.mgd.tracking_signal_avg = true; - ewma_beacon_signal_init(&link->u.mgd.ave_beacon_signal); - link->u.mgd.last_cqm_event_signal = 0; - link->u.mgd.count_beacon_signal = 1; - link->u.mgd.last_ave_beacon_signal = 0; - } else { - link->u.mgd.count_beacon_signal++; - } - - ewma_beacon_signal_add(&link->u.mgd.ave_beacon_signal, - -rx_status->signal); - - if (ifmgd->rssi_min_thold != ifmgd->rssi_max_thold && - link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT) { - int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); - int last_sig = link->u.mgd.last_ave_beacon_signal; - struct ieee80211_event event = { - .type = RSSI_EVENT, - }; + if (assoc_data->wmm && + !elems->wmm_param && bss_elems->wmm_param) { + elems->wmm_param = bss_elems->wmm_param; + sdata_info(sdata, + "AP bug: WMM param missing from AssocResp\n"); + } /* - * if signal crosses either of the boundaries, invoke callback - * with appropriate parameters + * Also check if we requested HT/VHT, otherwise the AP doesn't + * have to include the IEs in the (re)association response. */ - if (sig > ifmgd->rssi_max_thold && - (last_sig <= ifmgd->rssi_min_thold || last_sig == 0)) { - link->u.mgd.last_ave_beacon_signal = sig; - event.u.rssi.data = RSSI_EVENT_HIGH; - drv_event_callback(local, sdata, &event); - } else if (sig < ifmgd->rssi_min_thold && - (last_sig >= ifmgd->rssi_max_thold || - last_sig == 0)) { - link->u.mgd.last_ave_beacon_signal = sig; - event.u.rssi.data = RSSI_EVENT_LOW; - drv_event_callback(local, sdata, &event); + if (!elems->ht_cap_elem && bss_elems->ht_cap_elem && + !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) { + elems->ht_cap_elem = bss_elems->ht_cap_elem; + sdata_info(sdata, + "AP bug: HT capability missing from AssocResp\n"); } - } - - if (bss_conf->cqm_rssi_thold && - link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT && - !(sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) { - int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); - int last_event = link->u.mgd.last_cqm_event_signal; - int thold = bss_conf->cqm_rssi_thold; - int hyst = bss_conf->cqm_rssi_hyst; - - if (sig < thold && - (last_event == 0 || sig < last_event - hyst)) { - link->u.mgd.last_cqm_event_signal = sig; - ieee80211_cqm_rssi_notify( - &sdata->vif, - NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, - sig, GFP_KERNEL); - } else if (sig > thold && - (last_event == 0 || sig > last_event + hyst)) { - link->u.mgd.last_cqm_event_signal = sig; - ieee80211_cqm_rssi_notify( - &sdata->vif, - NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, - sig, GFP_KERNEL); + if (!elems->ht_operation && bss_elems->ht_operation && + !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) { + elems->ht_operation = bss_elems->ht_operation; + sdata_info(sdata, + "AP bug: HT operation missing from AssocResp\n"); } - } - - if (bss_conf->cqm_rssi_low && - link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT) { - int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); - int last_event = link->u.mgd.last_cqm_event_signal; - int low = bss_conf->cqm_rssi_low; - int high = bss_conf->cqm_rssi_high; - - if (sig < low && - (last_event == 0 || last_event >= low)) { - link->u.mgd.last_cqm_event_signal = sig; - ieee80211_cqm_rssi_notify( - &sdata->vif, - NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, - sig, GFP_KERNEL); - } else if (sig > high && - (last_event == 0 || last_event <= high)) { - link->u.mgd.last_cqm_event_signal = sig; - ieee80211_cqm_rssi_notify( - &sdata->vif, - NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, - sig, GFP_KERNEL); + if (!elems->vht_cap_elem && bss_elems->vht_cap_elem && + !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) { + elems->vht_cap_elem = bss_elems->vht_cap_elem; + sdata_info(sdata, + "AP bug: VHT capa missing from AssocResp\n"); + } + if (!elems->vht_operation && bss_elems->vht_operation && + !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) { + elems->vht_operation = bss_elems->vht_operation; + sdata_info(sdata, + "AP bug: VHT operation missing from AssocResp\n"); } - } -} - -static bool ieee80211_rx_our_beacon(const u8 *tx_bssid, - struct cfg80211_bss *bss) -{ - if (ether_addr_equal(tx_bssid, bss->bssid)) - return true; - if (!bss->transmitted_bss) - return false; - return ether_addr_equal(tx_bssid, bss->transmitted_bss->bssid); -} - -static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link, - struct ieee80211_hdr *hdr, size_t len, - struct ieee80211_rx_status *rx_status) -{ - struct ieee80211_sub_if_data *sdata = link->sdata; - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; - struct ieee80211_vif_cfg *vif_cfg = &sdata->vif.cfg; - struct ieee80211_mgmt *mgmt = (void *) hdr; - size_t baselen; - struct ieee802_11_elems *elems; - struct ieee80211_local *local = sdata->local; - struct ieee80211_chanctx_conf *chanctx_conf; - struct ieee80211_channel *chan; - struct link_sta_info *link_sta; - struct sta_info *sta; - u32 changed = 0; - bool erp_valid; - u8 erp_value = 0; - u32 ncrc = 0; - u8 *bssid, *variable = mgmt->u.beacon.variable; - u8 deauth_buf[IEEE80211_DEAUTH_FRAME_LEN]; - - sdata_assert_lock(sdata); - - /* Process beacon from the current BSS */ - bssid = ieee80211_get_bssid(hdr, len, sdata->vif.type); - if (ieee80211_is_s1g_beacon(mgmt->frame_control)) { - struct ieee80211_ext *ext = (void *) mgmt; - if (ieee80211_is_s1g_short_beacon(ext->frame_control)) - variable = ext->u.s1g_short_beacon.variable; - else - variable = ext->u.s1g_beacon.variable; + kfree(bss_elems); } - baselen = (u8 *) variable - (u8 *) mgmt; - if (baselen > len) - return; - - rcu_read_lock(); - chanctx_conf = rcu_dereference(link->conf->chanctx_conf); - if (!chanctx_conf) { - rcu_read_unlock(); - return; + /* + * We previously checked these in the beacon/probe response, so + * they should be present here. This is just a safety net. + */ + if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && + (!elems->wmm_param || !elems->ht_cap_elem || !elems->ht_operation)) { + sdata_info(sdata, + "HT AP is missing WMM params or HT capability/operation\n"); + ret = false; + goto out; } - if (ieee80211_rx_status_to_khz(rx_status) != - ieee80211_channel_to_khz(chanctx_conf->def.chan)) { - rcu_read_unlock(); - return; + if (!is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && + (!elems->vht_cap_elem || !elems->vht_operation)) { + sdata_info(sdata, + "VHT AP is missing VHT capability/operation\n"); + ret = false; + goto out; } - chan = chanctx_conf->def.chan; - rcu_read_unlock(); - - if (ifmgd->assoc_data && ifmgd->assoc_data->need_beacon && - ieee80211_rx_our_beacon(bssid, ifmgd->assoc_data->bss)) { - elems = ieee802_11_parse_elems(variable, len - baselen, false, - ifmgd->assoc_data->bss); - if (!elems) - return; - ieee80211_rx_bss_info(link, mgmt, len, rx_status); + if (is_6ghz && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && + !elems->he_6ghz_capa) { + sdata_info(sdata, + "HE 6 GHz AP is missing HE 6 GHz band capability\n"); + ret = false; + goto out; + } - if (elems->dtim_period) - link->u.mgd.dtim_period = elems->dtim_period; - link->u.mgd.have_beacon = true; - ifmgd->assoc_data->need_beacon = false; - if (ieee80211_hw_check(&local->hw, TIMING_BEACON_ONLY)) { - link->conf->sync_tsf = - le64_to_cpu(mgmt->u.beacon.timestamp); - link->conf->sync_device_ts = - rx_status->device_timestamp; - link->conf->sync_dtim_count = elems->dtim_count; - } + mutex_lock(&sdata->local->sta_mtx); + /* + * station info was already allocated and inserted before + * the association and should be available to us + */ + sta = sta_info_get(sdata, cbss->bssid); + if (WARN_ON(!sta)) { + mutex_unlock(&sdata->local->sta_mtx); + ret = false; + goto out; + } - if (elems->mbssid_config_ie) - bss_conf->profile_periodicity = - elems->mbssid_config_ie->profile_periodicity; - else - bss_conf->profile_periodicity = 0; + link_sta = rcu_dereference_protected(sta->link[link->link_id], + lockdep_is_held(&local->sta_mtx)); + if (WARN_ON(!link_sta)) { + mutex_unlock(&sdata->local->sta_mtx); + ret = false; + goto out; + } - if (elems->ext_capab_len >= 11 && - (elems->ext_capab[10] & WLAN_EXT_CAPA11_EMA_SUPPORT)) - bss_conf->ema_ap = true; - else - bss_conf->ema_ap = false; + sband = ieee80211_get_link_sband(link); + if (!sband) { + mutex_unlock(&sdata->local->sta_mtx); + ret = false; + goto out; + } - /* continue assoc process */ - ifmgd->assoc_data->timeout = jiffies; - ifmgd->assoc_data->timeout_started = true; - run_again(sdata, ifmgd->assoc_data->timeout); - kfree(elems); - return; + if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && + (!elems->he_cap || !elems->he_operation)) { + mutex_unlock(&sdata->local->sta_mtx); + sdata_info(sdata, + "HE AP is missing HE capability/operation\n"); + ret = false; + goto out; } - if (!ifmgd->associated || - !ieee80211_rx_our_beacon(bssid, link->u.mgd.bss)) - return; - bssid = link->u.mgd.bssid; + /* Set up internal HT/VHT capabilities */ + if (elems->ht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT)) + ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband, + elems->ht_cap_elem, + link_sta); - if (!(rx_status->flag & RX_FLAG_NO_SIGNAL_VAL)) - ieee80211_handle_beacon_sig(link, ifmgd, bss_conf, - local, rx_status); + if (elems->vht_cap_elem && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT)) + ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband, + elems->vht_cap_elem, + link_sta); - if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL) { - mlme_dbg_ratelimited(sdata, - "cancelling AP probe due to a received beacon\n"); - ieee80211_reset_ap_probe(sdata); - } + if (elems->he_operation && !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) && + elems->he_cap) { + ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband, + elems->he_cap, + elems->he_cap_len, + elems->he_6ghz_capa, + link_sta); - /* - * Push the beacon loss detection into the future since - * we are processing a beacon from the AP just now. - */ - ieee80211_sta_reset_beacon_monitor(sdata); + bss_conf->he_support = link_sta->pub->he_cap.has_he; + if (elems->rsnx && elems->rsnx_len && + (elems->rsnx[0] & WLAN_RSNX_CAPA_PROTECTED_TWT) && + wiphy_ext_feature_isset(local->hw.wiphy, + NL80211_EXT_FEATURE_PROTECTED_TWT)) + bss_conf->twt_protected = true; + else + bss_conf->twt_protected = false; - /* TODO: CRC urrently not calculated on S1G Beacon Compatibility - * element (which carries the beacon interval). Don't forget to add a - * bit to care_about_ies[] above if mac80211 is interested in a - * changing S1G element. - */ - if (!ieee80211_is_s1g_beacon(hdr->frame_control)) - ncrc = crc32_be(0, (void *)&mgmt->u.beacon.beacon_int, 4); - elems = ieee802_11_parse_elems_crc(variable, len - baselen, - false, care_about_ies, ncrc, - link->u.mgd.bss); - if (!elems) - return; - ncrc = elems->crc; + changed |= ieee80211_recalc_twt_req(link, link_sta, elems); - if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) && - ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid)) { - if (local->hw.conf.dynamic_ps_timeout > 0) { - if (local->hw.conf.flags & IEEE80211_CONF_PS) { - local->hw.conf.flags &= ~IEEE80211_CONF_PS; - ieee80211_hw_config(local, - IEEE80211_CONF_CHANGE_PS); - } - ieee80211_send_nullfunc(local, sdata, false); - } else if (!local->pspolling && sdata->u.mgd.powersave) { - local->pspolling = true; + if (elems->eht_operation && elems->eht_cap && + !(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_EHT)) { + ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband, + elems->he_cap, + elems->he_cap_len, + elems->eht_cap, + elems->eht_cap_len, + link_sta); - /* - * Here is assumed that the driver will be - * able to send ps-poll frame and receive a - * response even though power save mode is - * enabled, but some drivers might require - * to disable power save here. This needs - * to be investigated. - */ - ieee80211_send_pspoll(local, sdata); + bss_conf->eht_support = link_sta->pub->eht_cap.has_eht; + } else { + bss_conf->eht_support = false; } + } else { + bss_conf->he_support = false; + bss_conf->twt_requester = false; + bss_conf->twt_protected = false; + bss_conf->eht_support = false; } - if (sdata->vif.p2p || - sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) { - struct ieee80211_p2p_noa_attr noa = {}; - int ret; + bss_conf->twt_broadcast = + ieee80211_twt_bcast_support(sdata, bss_conf, sband, link_sta); - ret = cfg80211_get_p2p_attr(variable, - len - baselen, - IEEE80211_P2P_ATTR_ABSENCE_NOTICE, - (u8 *) &noa, sizeof(noa)); - if (ret >= 2) { - if (link->u.mgd.p2p_noa_index != noa.index) { - /* valid noa_attr and index changed */ - link->u.mgd.p2p_noa_index = noa.index; - memcpy(&bss_conf->p2p_noa_attr, &noa, sizeof(noa)); - changed |= BSS_CHANGED_P2P_PS; - /* - * make sure we update all information, the CRC - * mechanism doesn't look at P2P attributes. - */ - link->u.mgd.beacon_crc_valid = false; - } - } else if (link->u.mgd.p2p_noa_index != -1) { - /* noa_attr not found and we had valid noa_attr before */ - link->u.mgd.p2p_noa_index = -1; - memset(&bss_conf->p2p_noa_attr, 0, sizeof(bss_conf->p2p_noa_attr)); - changed |= BSS_CHANGED_P2P_PS; - link->u.mgd.beacon_crc_valid = false; - } - } + if (bss_conf->he_support) { + bss_conf->he_bss_color.color = + le32_get_bits(elems->he_operation->he_oper_params, + IEEE80211_HE_OPERATION_BSS_COLOR_MASK); + bss_conf->he_bss_color.partial = + le32_get_bits(elems->he_operation->he_oper_params, + IEEE80211_HE_OPERATION_PARTIAL_BSS_COLOR); + bss_conf->he_bss_color.enabled = + !le32_get_bits(elems->he_operation->he_oper_params, + IEEE80211_HE_OPERATION_BSS_COLOR_DISABLED); - if (link->u.mgd.csa_waiting_bcn) - ieee80211_chswitch_post_beacon(link); + if (bss_conf->he_bss_color.enabled) + changed |= BSS_CHANGED_HE_BSS_COLOR; - /* - * Update beacon timing and dtim count on every beacon appearance. This - * will allow the driver to use the most updated values. Do it before - * comparing this one with last received beacon. - * IMPORTANT: These parameters would possibly be out of sync by the time - * the driver will use them. The synchronized view is currently - * guaranteed only in certain callbacks. - */ - if (ieee80211_hw_check(&local->hw, TIMING_BEACON_ONLY) && - !ieee80211_is_s1g_beacon(hdr->frame_control)) { - link->conf->sync_tsf = - le64_to_cpu(mgmt->u.beacon.timestamp); - link->conf->sync_device_ts = - rx_status->device_timestamp; - link->conf->sync_dtim_count = elems->dtim_count; - } + bss_conf->htc_trig_based_pkt_ext = + le32_get_bits(elems->he_operation->he_oper_params, + IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK); + bss_conf->frame_time_rts_th = + le32_get_bits(elems->he_operation->he_oper_params, + IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK); - if ((ncrc == link->u.mgd.beacon_crc && link->u.mgd.beacon_crc_valid) || - ieee80211_is_s1g_short_beacon(mgmt->frame_control)) - goto free; - link->u.mgd.beacon_crc = ncrc; - link->u.mgd.beacon_crc_valid = true; + bss_conf->uora_exists = !!elems->uora_element; + if (elems->uora_element) + bss_conf->uora_ocw_range = elems->uora_element[0]; - ieee80211_rx_bss_info(link, mgmt, len, rx_status); + ieee80211_he_op_ie_to_bss_conf(&sdata->vif, elems->he_operation); + ieee80211_he_spr_ie_to_bss_conf(&sdata->vif, elems->he_spr); + /* TODO: OPEN: what happens if BSS color disable is set? */ + } - ieee80211_sta_process_chanswitch(link, rx_status->mactime, - rx_status->device_timestamp, - elems, true); + if (cbss->transmitted_bss) { + bss_conf->nontransmitted = true; + ether_addr_copy(bss_conf->transmitter_bssid, + cbss->transmitted_bss->bssid); + bss_conf->bssid_indicator = cbss->max_bssid_indicator; + bss_conf->bssid_index = cbss->bssid_index; + } else { + bss_conf->nontransmitted = false; + memset(bss_conf->transmitter_bssid, 0, + sizeof(bss_conf->transmitter_bssid)); + bss_conf->bssid_indicator = 0; + bss_conf->bssid_index = 0; + } - if (!link->u.mgd.disable_wmm_tracking && - ieee80211_sta_wmm_params(local, link, elems->wmm_param, - elems->wmm_param_len, - elems->mu_edca_param_set)) - changed |= BSS_CHANGED_QOS; + /* + * Some APs, e.g. Netgear WNDR3700, report invalid HT operation data + * in their association response, so ignore that data for our own + * configuration. If it changed since the last beacon, we'll get the + * next beacon and update then. + */ /* - * If we haven't had a beacon before, tell the driver about the - * DTIM period (and beacon timing if desired) now. + * If an operating mode notification IE is present, override the + * NSS calculation (that would be done in rate_control_rate_init()) + * and use the # of streams from that element. */ - if (!link->u.mgd.have_beacon) { - /* a few bogus AP send dtim_period = 0 or no TIM IE */ - bss_conf->dtim_period = elems->dtim_period ?: 1; + if (elems->opmode_notif && + !(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) { + u8 nss; - changed |= BSS_CHANGED_BEACON_INFO; - link->u.mgd.have_beacon = true; + nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK; + nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT; + nss += 1; + link_sta->pub->rx_nss = nss; + } - mutex_lock(&local->iflist_mtx); - ieee80211_recalc_ps(local); - mutex_unlock(&local->iflist_mtx); + rate_control_rate_init(sta); - ieee80211_recalc_ps_vif(sdata); + if (ifmgd->flags & IEEE80211_STA_MFP_ENABLED) { + set_sta_flag(sta, WLAN_STA_MFP); + sta->sta.mfp = true; + } else { + sta->sta.mfp = false; } - if (elems->erp_info) { - erp_valid = true; - erp_value = elems->erp_info[0]; - } else { - erp_valid = false; + sta->sta.wme = (elems->wmm_param || elems->s1g_capab) && + local->hw.queues >= IEEE80211_NUM_ACS; + + err = sta_info_move_state(sta, IEEE80211_STA_ASSOC); + if (!err && !(ifmgd->flags & IEEE80211_STA_CONTROL_PORT)) + err = sta_info_move_state(sta, IEEE80211_STA_AUTHORIZED); + if (err) { + sdata_info(sdata, + "failed to move station %pM to desired state\n", + sta->sta.addr); + WARN_ON(__sta_info_destroy(sta)); + mutex_unlock(&sdata->local->sta_mtx); + ret = false; + goto out; } - if (!ieee80211_is_s1g_beacon(hdr->frame_control)) - changed |= ieee80211_handle_bss_capability(link, - le16_to_cpu(mgmt->u.beacon.capab_info), - erp_valid, erp_value); + if (sdata->wdev.use_4addr) + drv_sta_set_4addr(local, sdata, &sta->sta, true); - mutex_lock(&local->sta_mtx); - sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); - if (WARN_ON(!sta)) - goto free; - link_sta = rcu_dereference_protected(sta->link[link->link_id], - lockdep_is_held(&local->sta_mtx)); - if (WARN_ON(!link_sta)) - goto free; + mutex_unlock(&sdata->local->sta_mtx); - changed |= ieee80211_recalc_twt_req(link, link_sta, elems); + /* + * Always handle WMM once after association regardless + * of the first value the AP uses. Setting -1 here has + * that effect because the AP values is an unsigned + * 4-bit value. + */ + link->u.mgd.wmm_last_param_set = -1; + link->u.mgd.mu_edca_last_param_set = -1; - if (ieee80211_config_bw(link, elems->ht_cap_elem, - elems->vht_cap_elem, elems->ht_operation, - elems->vht_operation, elems->he_operation, - elems->eht_operation, - elems->s1g_oper, bssid, &changed)) { - mutex_unlock(&local->sta_mtx); - sdata_info(sdata, - "failed to follow AP %pM bandwidth change, disconnect\n", - bssid); - ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH, - WLAN_REASON_DEAUTH_LEAVING, - true, deauth_buf); - ieee80211_report_disconnect(sdata, deauth_buf, - sizeof(deauth_buf), true, - WLAN_REASON_DEAUTH_LEAVING, - false); - goto free; + if (link->u.mgd.disable_wmm_tracking) { + ieee80211_set_wmm_default(link, false, false); + } else if (!ieee80211_sta_wmm_params(local, link, elems->wmm_param, + elems->wmm_param_len, + elems->mu_edca_param_set)) { + /* still enable QoS since we might have HT/VHT */ + ieee80211_set_wmm_default(link, false, true); + /* disable WMM tracking in this case to disable + * tracking WMM parameter changes in the beacon if + * the parameters weren't actually valid. Doing so + * avoids changing parameters very strangely when + * the AP is going back and forth between valid and + * invalid parameters. + */ + link->u.mgd.disable_wmm_tracking = true; } + changed |= BSS_CHANGED_QOS; - if (sta && elems->opmode_notif) - ieee80211_vht_handle_opmode(sdata, link_sta, - *elems->opmode_notif, - rx_status->band); - mutex_unlock(&local->sta_mtx); - - changed |= ieee80211_handle_pwr_constr(link, chan, mgmt, - elems->country_elem, - elems->country_elem_len, - elems->pwr_constr_elem, - elems->cisco_dtpc_elem); + if (elems->max_idle_period_ie) { + bss_conf->max_idle_period = + le16_to_cpu(elems->max_idle_period_ie->max_idle_period); + bss_conf->protected_keep_alive = + !!(elems->max_idle_period_ie->idle_options & + WLAN_IDLE_OPTIONS_PROTECTED_KEEP_ALIVE); + changed |= BSS_CHANGED_KEEP_ALIVE; + } else { + bss_conf->max_idle_period = 0; + bss_conf->protected_keep_alive = false; + } - ieee80211_link_info_change_notify(sdata, link, changed); -free: - kfree(elems); -} + /* set assoc capability (AID was already set earlier), + * ieee80211_set_associated() will tell the driver */ + bss_conf->assoc_capability = capab_info; + ieee80211_set_associated(sdata, cbss, changed); -void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb) -{ - struct ieee80211_link_data *link = &sdata->deflink; - struct ieee80211_rx_status *rx_status; - struct ieee80211_hdr *hdr; - u16 fc; + /* + * If we're using 4-addr mode, let the AP know that we're + * doing so, so that it can create the STA VLAN on its side + */ + if (ifmgd->use_4addr) + ieee80211_send_4addr_nullfunc(local, sdata); - rx_status = (struct ieee80211_rx_status *) skb->cb; - hdr = (struct ieee80211_hdr *) skb->data; - fc = le16_to_cpu(hdr->frame_control); + /* + * Start timer to probe the connection to the AP now. + * Also start the timer that will detect beacon loss. + */ + ieee80211_sta_reset_beacon_monitor(sdata); + ieee80211_sta_reset_conn_monitor(sdata); - sdata_lock(sdata); - switch (fc & IEEE80211_FCTL_STYPE) { - case IEEE80211_STYPE_S1G_BEACON: - ieee80211_rx_mgmt_beacon(link, hdr, skb->len, rx_status); - break; - } - sdata_unlock(sdata); + ret = true; + out: + kfree(bss_ies); + return ret; } -void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb) +static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, + size_t len) { - struct ieee80211_link_data *link = &sdata->deflink; - struct ieee80211_rx_status *rx_status; - struct ieee80211_mgmt *mgmt; - u16 fc; - int ies_len; + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_mgd_assoc_data *assoc_data = ifmgd->assoc_data; + u16 capab_info, status_code, aid; + struct ieee802_11_elems *elems; + int ac; + u8 *pos; + bool reassoc; + struct cfg80211_bss *cbss; + struct ieee80211_event event = { + .type = MLME_EVENT, + .u.mlme.data = ASSOC_EVENT, + }; + struct ieee80211_prep_tx_info info = {}; + struct cfg80211_rx_assoc_resp resp = { + .uapsd_queues = -1, + }; - rx_status = (struct ieee80211_rx_status *) skb->cb; - mgmt = (struct ieee80211_mgmt *) skb->data; - fc = le16_to_cpu(mgmt->frame_control); + sdata_assert_lock(sdata); - sdata_lock(sdata); + if (!assoc_data) + return; - switch (fc & IEEE80211_FCTL_STYPE) { - case IEEE80211_STYPE_BEACON: - ieee80211_rx_mgmt_beacon(link, (void *)mgmt, - skb->len, rx_status); - break; - case IEEE80211_STYPE_PROBE_RESP: - ieee80211_rx_mgmt_probe_resp(link, skb); - break; - case IEEE80211_STYPE_AUTH: - ieee80211_rx_mgmt_auth(sdata, mgmt, skb->len); - break; - case IEEE80211_STYPE_DEAUTH: - ieee80211_rx_mgmt_deauth(sdata, mgmt, skb->len); - break; - case IEEE80211_STYPE_DISASSOC: - ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len); - break; - case IEEE80211_STYPE_ASSOC_RESP: - case IEEE80211_STYPE_REASSOC_RESP: - ieee80211_rx_mgmt_assoc_resp(sdata, mgmt, skb->len); - break; - case IEEE80211_STYPE_ACTION: - if (mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) { - struct ieee802_11_elems *elems; + if (!ether_addr_equal(assoc_data->bss->bssid, mgmt->bssid)) + return; - ies_len = skb->len - - offsetof(struct ieee80211_mgmt, - u.action.u.chan_switch.variable); + cbss = assoc_data->bss; - if (ies_len < 0) - break; + /* + * AssocResp and ReassocResp have identical structure, so process both + * of them in this function. + */ - /* CSA IE cannot be overridden, no need for BSSID */ - elems = ieee802_11_parse_elems( - mgmt->u.action.u.chan_switch.variable, - ies_len, true, NULL); + if (len < 24 + 6) + return; - if (elems && !elems->parse_error) - ieee80211_sta_process_chanswitch(link, - rx_status->mactime, - rx_status->device_timestamp, - elems, false); - kfree(elems); - } else if (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) { - struct ieee802_11_elems *elems; + reassoc = ieee80211_is_reassoc_resp(mgmt->frame_control); + capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info); + status_code = le16_to_cpu(mgmt->u.assoc_resp.status_code); + pos = mgmt->u.assoc_resp.variable; + aid = le16_to_cpu(mgmt->u.assoc_resp.aid); + if (cbss->channel->band == NL80211_BAND_S1GHZ) { + pos = (u8 *) mgmt->u.s1g_assoc_resp.variable; + aid = 0; /* TODO */ + } - ies_len = skb->len - - offsetof(struct ieee80211_mgmt, - u.action.u.ext_chan_switch.variable); + /* + * Note: this may not be perfect, AP might misbehave - if + * anyone needs to rely on perfect complete notification + * with the exact right subtype, then we need to track what + * we actually transmitted. + */ + info.subtype = reassoc ? IEEE80211_STYPE_REASSOC_REQ : + IEEE80211_STYPE_ASSOC_REQ; - if (ies_len < 0) - break; + sdata_info(sdata, + "RX %sssocResp from %pM (capab=0x%x status=%d aid=%d)\n", + reassoc ? "Rea" : "A", mgmt->sa, + capab_info, status_code, (u16)(aid & ~(BIT(15) | BIT(14)))); - /* - * extended CSA IE can't be overridden, no need for - * BSSID - */ - elems = ieee802_11_parse_elems( - mgmt->u.action.u.ext_chan_switch.variable, - ies_len, true, NULL); + if (assoc_data->fils_kek_len && + fils_decrypt_assoc_resp(sdata, (u8 *)mgmt, &len, assoc_data) < 0) + return; - if (elems && !elems->parse_error) { - /* for the handling code pretend it was an IE */ - elems->ext_chansw_ie = - &mgmt->u.action.u.ext_chan_switch.data; + elems = ieee802_11_parse_elems(pos, len - (pos - (u8 *)mgmt), false, + assoc_data->bss); + if (!elems) + goto notify_driver; - ieee80211_sta_process_chanswitch(link, - rx_status->mactime, - rx_status->device_timestamp, - elems, false); - } + if (status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY && + elems->timeout_int && + elems->timeout_int->type == WLAN_TIMEOUT_ASSOC_COMEBACK) { + u32 tu, ms; - kfree(elems); - } - break; - } - sdata_unlock(sdata); -} + cfg80211_assoc_comeback(sdata->dev, assoc_data->bss->bssid, + le32_to_cpu(elems->timeout_int->value)); -static void ieee80211_sta_timer(struct timer_list *t) -{ - struct ieee80211_sub_if_data *sdata = - from_timer(sdata, t, u.mgd.timer); + tu = le32_to_cpu(elems->timeout_int->value); + ms = tu * 1024 / 1000; + sdata_info(sdata, + "%pM rejected association temporarily; comeback duration %u TU (%u ms)\n", + mgmt->sa, tu, ms); + assoc_data->timeout = jiffies + msecs_to_jiffies(ms); + assoc_data->timeout_started = true; + if (ms > IEEE80211_ASSOC_TIMEOUT) + run_again(sdata, assoc_data->timeout); + goto notify_driver; + } - ieee80211_queue_work(&sdata->local->hw, &sdata->work); -} + if (status_code != WLAN_STATUS_SUCCESS) { + sdata_info(sdata, "%pM denied association (code=%d)\n", + mgmt->sa, status_code); + ieee80211_destroy_assoc_data(sdata, ASSOC_REJECTED); + event.u.mlme.status = MLME_DENIED; + event.u.mlme.reason = status_code; + drv_event_callback(sdata->local, sdata, &event); + } else { + if (!ieee80211_assoc_success(sdata, cbss, mgmt, len, elems)) { + /* oops -- internal error -- send timeout for now */ + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + goto notify_driver; + } + event.u.mlme.status = MLME_SUCCESS; + drv_event_callback(sdata->local, sdata, &event); + sdata_info(sdata, "associated\n"); -void ieee80211_sta_connection_lost(struct ieee80211_sub_if_data *sdata, - u8 reason, bool tx) -{ - u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; + /* + * destroy assoc_data afterwards, as otherwise an idle + * recalc after assoc_data is NULL but before associated + * is set can cause the interface to go idle + */ + ieee80211_destroy_assoc_data(sdata, ASSOC_SUCCESS); - ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH, reason, - tx, frame_buf); + /* get uapsd queues configuration */ + resp.uapsd_queues = 0; + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) + if (sdata->deflink.tx_conf[ac].uapsd) + resp.uapsd_queues |= ieee80211_ac_to_qos_mask[ac]; - ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, - reason, false); + info.success = 1; + } + + resp.links[0].bss = cbss; + resp.buf = (u8 *)mgmt; + resp.len = len; + resp.req_ies = ifmgd->assoc_req_ies; + resp.req_ies_len = ifmgd->assoc_req_ies_len; + cfg80211_rx_assoc_resp(sdata->dev, &resp); +notify_driver: + drv_mgd_complete_tx(sdata->local, sdata, &info); + kfree(elems); } -static int ieee80211_auth(struct ieee80211_sub_if_data *sdata) +static void ieee80211_rx_bss_info(struct ieee80211_link_data *link, + struct ieee80211_mgmt *mgmt, size_t len, + struct ieee80211_rx_status *rx_status) { + struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_local *local = sdata->local; - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - struct ieee80211_mgd_auth_data *auth_data = ifmgd->auth_data; - u32 tx_flags = 0; - u16 trans = 1; - u16 status = 0; - struct ieee80211_prep_tx_info info = { - .subtype = IEEE80211_STYPE_AUTH, - }; + struct ieee80211_bss *bss; + struct ieee80211_channel *channel; sdata_assert_lock(sdata); - if (WARN_ON_ONCE(!auth_data)) - return -EINVAL; - - auth_data->tries++; - - if (auth_data->tries > IEEE80211_AUTH_MAX_TRIES) { - sdata_info(sdata, "authentication with %pM timed out\n", - auth_data->bss->bssid); - - /* - * Most likely AP is not in the range so remove the - * bss struct for that AP. - */ - cfg80211_unlink_bss(local->hw.wiphy, auth_data->bss); + channel = ieee80211_get_channel_khz(local->hw.wiphy, + ieee80211_rx_status_to_khz(rx_status)); + if (!channel) + return; - return -ETIMEDOUT; + bss = ieee80211_bss_info_update(local, rx_status, mgmt, len, channel); + if (bss) { + link->conf->beacon_rate = bss->beacon_rate; + ieee80211_rx_bss_put(local, bss); } +} - if (auth_data->algorithm == WLAN_AUTH_SAE) - info.duration = jiffies_to_msecs(IEEE80211_AUTH_TIMEOUT_SAE); - - drv_mgd_prepare_tx(local, sdata, &info); - sdata_info(sdata, "send auth to %pM (try %d/%d)\n", - auth_data->bss->bssid, auth_data->tries, - IEEE80211_AUTH_MAX_TRIES); +static void ieee80211_rx_mgmt_probe_resp(struct ieee80211_link_data *link, + struct sk_buff *skb) +{ + struct ieee80211_sub_if_data *sdata = link->sdata; + struct ieee80211_mgmt *mgmt = (void *)skb->data; + struct ieee80211_if_managed *ifmgd; + struct ieee80211_rx_status *rx_status = (void *) skb->cb; + struct ieee80211_channel *channel; + size_t baselen, len = skb->len; - auth_data->expected_transaction = 2; + ifmgd = &sdata->u.mgd; - if (auth_data->algorithm == WLAN_AUTH_SAE) { - trans = auth_data->sae_trans; - status = auth_data->sae_status; - auth_data->expected_transaction = trans; - } + sdata_assert_lock(sdata); - if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) - tx_flags = IEEE80211_TX_CTL_REQ_TX_STATUS | - IEEE80211_TX_INTFL_MLME_CONN_TX; + /* + * According to Draft P802.11ax D6.0 clause 26.17.2.3.2: + * "If a 6 GHz AP receives a Probe Request frame and responds with + * a Probe Response frame [..], the Address 1 field of the Probe + * Response frame shall be set to the broadcast address [..]" + * So, on 6GHz band we should also accept broadcast responses. + */ + channel = ieee80211_get_channel(sdata->local->hw.wiphy, + rx_status->freq); + if (!channel) + return; - ieee80211_send_auth(sdata, trans, auth_data->algorithm, status, - auth_data->data, auth_data->data_len, - auth_data->bss->bssid, - auth_data->bss->bssid, NULL, 0, 0, - tx_flags); + if (!ether_addr_equal(mgmt->da, sdata->vif.addr) && + (channel->band != NL80211_BAND_6GHZ || + !is_broadcast_ether_addr(mgmt->da))) + return; /* ignore ProbeResp to foreign address */ - if (tx_flags == 0) { - if (auth_data->algorithm == WLAN_AUTH_SAE) - auth_data->timeout = jiffies + - IEEE80211_AUTH_TIMEOUT_SAE; - else - auth_data->timeout = jiffies + IEEE80211_AUTH_TIMEOUT; - } else { - auth_data->timeout = - round_jiffies_up(jiffies + IEEE80211_AUTH_TIMEOUT_LONG); - } + baselen = (u8 *) mgmt->u.probe_resp.variable - (u8 *) mgmt; + if (baselen > len) + return; - auth_data->timeout_started = true; - run_again(sdata, auth_data->timeout); + ieee80211_rx_bss_info(link, mgmt, len, rx_status); - return 0; + if (ifmgd->associated && + ether_addr_equal(mgmt->bssid, link->u.mgd.bssid)) + ieee80211_reset_ap_probe(sdata); } -static int ieee80211_do_assoc(struct ieee80211_sub_if_data *sdata) +/* + * This is the canonical list of information elements we care about, + * the filter code also gives us all changes to the Microsoft OUI + * (00:50:F2) vendor IE which is used for WMM which we need to track, + * as well as the DTPC IE (part of the Cisco OUI) used for signaling + * changes to requested client power. + * + * We implement beacon filtering in software since that means we can + * avoid processing the frame here and in cfg80211, and userspace + * will not be able to tell whether the hardware supports it or not. + * + * XXX: This list needs to be dynamic -- userspace needs to be able to + * add items it requires. It also needs to be able to tell us to + * look out for other vendor IEs. + */ +static const u64 care_about_ies = + (1ULL << WLAN_EID_COUNTRY) | + (1ULL << WLAN_EID_ERP_INFO) | + (1ULL << WLAN_EID_CHANNEL_SWITCH) | + (1ULL << WLAN_EID_PWR_CONSTRAINT) | + (1ULL << WLAN_EID_HT_CAPABILITY) | + (1ULL << WLAN_EID_HT_OPERATION) | + (1ULL << WLAN_EID_EXT_CHANSWITCH_ANN); + +static void ieee80211_handle_beacon_sig(struct ieee80211_link_data *link, + struct ieee80211_if_managed *ifmgd, + struct ieee80211_bss_conf *bss_conf, + struct ieee80211_local *local, + struct ieee80211_rx_status *rx_status) { - struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data; - struct ieee80211_local *local = sdata->local; - int ret; + struct ieee80211_sub_if_data *sdata = link->sdata; - sdata_assert_lock(sdata); + /* Track average RSSI from the Beacon frames of the current AP */ - assoc_data->tries++; - if (assoc_data->tries > IEEE80211_ASSOC_MAX_TRIES) { - sdata_info(sdata, "association with %pM timed out\n", - assoc_data->bss->bssid); + if (!link->u.mgd.tracking_signal_avg) { + link->u.mgd.tracking_signal_avg = true; + ewma_beacon_signal_init(&link->u.mgd.ave_beacon_signal); + link->u.mgd.last_cqm_event_signal = 0; + link->u.mgd.count_beacon_signal = 1; + link->u.mgd.last_ave_beacon_signal = 0; + } else { + link->u.mgd.count_beacon_signal++; + } + + ewma_beacon_signal_add(&link->u.mgd.ave_beacon_signal, + -rx_status->signal); + + if (ifmgd->rssi_min_thold != ifmgd->rssi_max_thold && + link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT) { + int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); + int last_sig = link->u.mgd.last_ave_beacon_signal; + struct ieee80211_event event = { + .type = RSSI_EVENT, + }; /* - * Most likely AP is not in the range so remove the - * bss struct for that AP. + * if signal crosses either of the boundaries, invoke callback + * with appropriate parameters */ - cfg80211_unlink_bss(local->hw.wiphy, assoc_data->bss); + if (sig > ifmgd->rssi_max_thold && + (last_sig <= ifmgd->rssi_min_thold || last_sig == 0)) { + link->u.mgd.last_ave_beacon_signal = sig; + event.u.rssi.data = RSSI_EVENT_HIGH; + drv_event_callback(local, sdata, &event); + } else if (sig < ifmgd->rssi_min_thold && + (last_sig >= ifmgd->rssi_max_thold || + last_sig == 0)) { + link->u.mgd.last_ave_beacon_signal = sig; + event.u.rssi.data = RSSI_EVENT_LOW; + drv_event_callback(local, sdata, &event); + } + } - return -ETIMEDOUT; + if (bss_conf->cqm_rssi_thold && + link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT && + !(sdata->vif.driver_flags & IEEE80211_VIF_SUPPORTS_CQM_RSSI)) { + int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); + int last_event = link->u.mgd.last_cqm_event_signal; + int thold = bss_conf->cqm_rssi_thold; + int hyst = bss_conf->cqm_rssi_hyst; + + if (sig < thold && + (last_event == 0 || sig < last_event - hyst)) { + link->u.mgd.last_cqm_event_signal = sig; + ieee80211_cqm_rssi_notify( + &sdata->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + sig, GFP_KERNEL); + } else if (sig > thold && + (last_event == 0 || sig > last_event + hyst)) { + link->u.mgd.last_cqm_event_signal = sig; + ieee80211_cqm_rssi_notify( + &sdata->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + sig, GFP_KERNEL); + } } - sdata_info(sdata, "associate with %pM (try %d/%d)\n", - assoc_data->bss->bssid, assoc_data->tries, - IEEE80211_ASSOC_MAX_TRIES); - ret = ieee80211_send_assoc(sdata); - if (ret) - return ret; + if (bss_conf->cqm_rssi_low && + link->u.mgd.count_beacon_signal >= IEEE80211_SIGNAL_AVE_MIN_COUNT) { + int sig = -ewma_beacon_signal_read(&link->u.mgd.ave_beacon_signal); + int last_event = link->u.mgd.last_cqm_event_signal; + int low = bss_conf->cqm_rssi_low; + int high = bss_conf->cqm_rssi_high; - if (!ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) { - assoc_data->timeout = jiffies + IEEE80211_ASSOC_TIMEOUT; - assoc_data->timeout_started = true; - run_again(sdata, assoc_data->timeout); - } else { - assoc_data->timeout = - round_jiffies_up(jiffies + - IEEE80211_ASSOC_TIMEOUT_LONG); - assoc_data->timeout_started = true; - run_again(sdata, assoc_data->timeout); + if (sig < low && + (last_event == 0 || last_event >= low)) { + link->u.mgd.last_cqm_event_signal = sig; + ieee80211_cqm_rssi_notify( + &sdata->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW, + sig, GFP_KERNEL); + } else if (sig > high && + (last_event == 0 || last_event <= high)) { + link->u.mgd.last_cqm_event_signal = sig; + ieee80211_cqm_rssi_notify( + &sdata->vif, + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH, + sig, GFP_KERNEL); + } } - - return 0; } -void ieee80211_mgd_conn_tx_status(struct ieee80211_sub_if_data *sdata, - __le16 fc, bool acked) +static bool ieee80211_rx_our_beacon(const u8 *tx_bssid, + struct cfg80211_bss *bss) { - struct ieee80211_local *local = sdata->local; - - sdata->u.mgd.status_fc = fc; - sdata->u.mgd.status_acked = acked; - sdata->u.mgd.status_received = true; - - ieee80211_queue_work(&local->hw, &sdata->work); + if (ether_addr_equal(tx_bssid, bss->bssid)) + return true; + if (!bss->transmitted_bss) + return false; + return ether_addr_equal(tx_bssid, bss->transmitted_bss->bssid); } -void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) +static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link, + struct ieee80211_hdr *hdr, size_t len, + struct ieee80211_rx_status *rx_status) { - struct ieee80211_local *local = sdata->local; + struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf; + struct ieee80211_vif_cfg *vif_cfg = &sdata->vif.cfg; + struct ieee80211_mgmt *mgmt = (void *) hdr; + size_t baselen; + struct ieee802_11_elems *elems; + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_channel *chan; + struct link_sta_info *link_sta; + struct sta_info *sta; + u32 changed = 0; + bool erp_valid; + u8 erp_value = 0; + u32 ncrc = 0; + u8 *bssid, *variable = mgmt->u.beacon.variable; + u8 deauth_buf[IEEE80211_DEAUTH_FRAME_LEN]; - sdata_lock(sdata); + sdata_assert_lock(sdata); - if (ifmgd->status_received) { - __le16 fc = ifmgd->status_fc; - bool status_acked = ifmgd->status_acked; + /* Process beacon from the current BSS */ + bssid = ieee80211_get_bssid(hdr, len, sdata->vif.type); + if (ieee80211_is_s1g_beacon(mgmt->frame_control)) { + struct ieee80211_ext *ext = (void *) mgmt; - ifmgd->status_received = false; - if (ifmgd->auth_data && ieee80211_is_auth(fc)) { - if (status_acked) { - if (ifmgd->auth_data->algorithm == - WLAN_AUTH_SAE) - ifmgd->auth_data->timeout = - jiffies + - IEEE80211_AUTH_TIMEOUT_SAE; - else - ifmgd->auth_data->timeout = - jiffies + - IEEE80211_AUTH_TIMEOUT_SHORT; - run_again(sdata, ifmgd->auth_data->timeout); - } else { - ifmgd->auth_data->timeout = jiffies - 1; - } - ifmgd->auth_data->timeout_started = true; - } else if (ifmgd->assoc_data && - (ieee80211_is_assoc_req(fc) || - ieee80211_is_reassoc_req(fc))) { - if (status_acked) { - ifmgd->assoc_data->timeout = - jiffies + IEEE80211_ASSOC_TIMEOUT_SHORT; - run_again(sdata, ifmgd->assoc_data->timeout); - } else { - ifmgd->assoc_data->timeout = jiffies - 1; - } - ifmgd->assoc_data->timeout_started = true; - } + if (ieee80211_is_s1g_short_beacon(ext->frame_control)) + variable = ext->u.s1g_short_beacon.variable; + else + variable = ext->u.s1g_beacon.variable; } - if (ifmgd->auth_data && ifmgd->auth_data->timeout_started && - time_after(jiffies, ifmgd->auth_data->timeout)) { - if (ifmgd->auth_data->done || ifmgd->auth_data->waiting) { - /* - * ok ... we waited for assoc or continuation but - * userspace didn't do it, so kill the auth data - */ - ieee80211_destroy_auth_data(sdata, false); - } else if (ieee80211_auth(sdata)) { - u8 bssid[ETH_ALEN]; - struct ieee80211_event event = { - .type = MLME_EVENT, - .u.mlme.data = AUTH_EVENT, - .u.mlme.status = MLME_TIMEOUT, - }; + baselen = (u8 *) variable - (u8 *) mgmt; + if (baselen > len) + return; - memcpy(bssid, ifmgd->auth_data->bss->bssid, ETH_ALEN); + rcu_read_lock(); + chanctx_conf = rcu_dereference(link->conf->chanctx_conf); + if (!chanctx_conf) { + rcu_read_unlock(); + return; + } - ieee80211_destroy_auth_data(sdata, false); + if (ieee80211_rx_status_to_khz(rx_status) != + ieee80211_channel_to_khz(chanctx_conf->def.chan)) { + rcu_read_unlock(); + return; + } + chan = chanctx_conf->def.chan; + rcu_read_unlock(); - cfg80211_auth_timeout(sdata->dev, bssid); - drv_event_callback(sdata->local, sdata, &event); - } - } else if (ifmgd->auth_data && ifmgd->auth_data->timeout_started) - run_again(sdata, ifmgd->auth_data->timeout); + if (ifmgd->assoc_data && ifmgd->assoc_data->need_beacon && + ieee80211_rx_our_beacon(bssid, ifmgd->assoc_data->bss)) { + elems = ieee802_11_parse_elems(variable, len - baselen, false, + ifmgd->assoc_data->bss); + if (!elems) + return; - if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started && - time_after(jiffies, ifmgd->assoc_data->timeout)) { - if ((ifmgd->assoc_data->need_beacon && - !sdata->deflink.u.mgd.have_beacon) || - ieee80211_do_assoc(sdata)) { - struct ieee80211_event event = { - .type = MLME_EVENT, - .u.mlme.data = ASSOC_EVENT, - .u.mlme.status = MLME_TIMEOUT, - }; + ieee80211_rx_bss_info(link, mgmt, len, rx_status); - ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); - drv_event_callback(sdata->local, sdata, &event); + if (elems->dtim_period) + link->u.mgd.dtim_period = elems->dtim_period; + link->u.mgd.have_beacon = true; + ifmgd->assoc_data->need_beacon = false; + if (ieee80211_hw_check(&local->hw, TIMING_BEACON_ONLY)) { + link->conf->sync_tsf = + le64_to_cpu(mgmt->u.beacon.timestamp); + link->conf->sync_device_ts = + rx_status->device_timestamp; + link->conf->sync_dtim_count = elems->dtim_count; } - } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started) - run_again(sdata, ifmgd->assoc_data->timeout); - if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL && - ifmgd->associated) { - u8 *bssid = sdata->deflink.u.mgd.bssid; - int max_tries; + if (elems->mbssid_config_ie) + bss_conf->profile_periodicity = + elems->mbssid_config_ie->profile_periodicity; + else + bss_conf->profile_periodicity = 0; - if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) - max_tries = max_nullfunc_tries; + if (elems->ext_capab_len >= 11 && + (elems->ext_capab[10] & WLAN_EXT_CAPA11_EMA_SUPPORT)) + bss_conf->ema_ap = true; else - max_tries = max_probe_tries; + bss_conf->ema_ap = false; + + /* continue assoc process */ + ifmgd->assoc_data->timeout = jiffies; + ifmgd->assoc_data->timeout_started = true; + run_again(sdata, ifmgd->assoc_data->timeout); + kfree(elems); + return; + } + + if (!ifmgd->associated || + !ieee80211_rx_our_beacon(bssid, link->u.mgd.bss)) + return; + bssid = link->u.mgd.bssid; + + if (!(rx_status->flag & RX_FLAG_NO_SIGNAL_VAL)) + ieee80211_handle_beacon_sig(link, ifmgd, bss_conf, + local, rx_status); + + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL) { + mlme_dbg_ratelimited(sdata, + "cancelling AP probe due to a received beacon\n"); + ieee80211_reset_ap_probe(sdata); + } + + /* + * Push the beacon loss detection into the future since + * we are processing a beacon from the AP just now. + */ + ieee80211_sta_reset_beacon_monitor(sdata); + + /* TODO: CRC urrently not calculated on S1G Beacon Compatibility + * element (which carries the beacon interval). Don't forget to add a + * bit to care_about_ies[] above if mac80211 is interested in a + * changing S1G element. + */ + if (!ieee80211_is_s1g_beacon(hdr->frame_control)) + ncrc = crc32_be(0, (void *)&mgmt->u.beacon.beacon_int, 4); + elems = ieee802_11_parse_elems_crc(variable, len - baselen, + false, care_about_ies, ncrc, + link->u.mgd.bss); + if (!elems) + return; + ncrc = elems->crc; + + if (ieee80211_hw_check(&local->hw, PS_NULLFUNC_STACK) && + ieee80211_check_tim(elems->tim, elems->tim_len, vif_cfg->aid)) { + if (local->hw.conf.dynamic_ps_timeout > 0) { + if (local->hw.conf.flags & IEEE80211_CONF_PS) { + local->hw.conf.flags &= ~IEEE80211_CONF_PS; + ieee80211_hw_config(local, + IEEE80211_CONF_CHANGE_PS); + } + ieee80211_send_nullfunc(local, sdata, false); + } else if (!local->pspolling && sdata->u.mgd.powersave) { + local->pspolling = true; - /* ACK received for nullfunc probing frame */ - if (!ifmgd->probe_send_count) - ieee80211_reset_ap_probe(sdata); - else if (ifmgd->nullfunc_failed) { - if (ifmgd->probe_send_count < max_tries) { - mlme_dbg(sdata, - "No ack for nullfunc frame to AP %pM, try %d/%i\n", - bssid, ifmgd->probe_send_count, - max_tries); - ieee80211_mgd_probe_ap_send(sdata); - } else { - mlme_dbg(sdata, - "No ack for nullfunc frame to AP %pM, disconnecting.\n", - bssid); - ieee80211_sta_connection_lost(sdata, - WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, - false); - } - } else if (time_is_after_jiffies(ifmgd->probe_timeout)) - run_again(sdata, ifmgd->probe_timeout); - else if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) { - mlme_dbg(sdata, - "Failed to send nullfunc to AP %pM after %dms, disconnecting\n", - bssid, probe_wait_ms); - ieee80211_sta_connection_lost(sdata, - WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, false); - } else if (ifmgd->probe_send_count < max_tries) { - mlme_dbg(sdata, - "No probe response from AP %pM after %dms, try %d/%i\n", - bssid, probe_wait_ms, - ifmgd->probe_send_count, max_tries); - ieee80211_mgd_probe_ap_send(sdata); - } else { /* - * We actually lost the connection ... or did we? - * Let's make sure! + * Here is assumed that the driver will be + * able to send ps-poll frame and receive a + * response even though power save mode is + * enabled, but some drivers might require + * to disable power save here. This needs + * to be investigated. */ - mlme_dbg(sdata, - "No probe response from AP %pM after %dms, disconnecting.\n", - bssid, probe_wait_ms); - - ieee80211_sta_connection_lost(sdata, - WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, false); + ieee80211_send_pspoll(local, sdata); } } - sdata_unlock(sdata); -} - -static void ieee80211_sta_bcn_mon_timer(struct timer_list *t) -{ - struct ieee80211_sub_if_data *sdata = - from_timer(sdata, t, u.mgd.bcn_mon_timer); - - if (WARN_ON(sdata->vif.valid_links)) - return; - - if (sdata->vif.bss_conf.csa_active && - !sdata->deflink.u.mgd.csa_waiting_bcn) - return; + if (sdata->vif.p2p || + sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) { + struct ieee80211_p2p_noa_attr noa = {}; + int ret; - if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER) - return; + ret = cfg80211_get_p2p_attr(variable, + len - baselen, + IEEE80211_P2P_ATTR_ABSENCE_NOTICE, + (u8 *) &noa, sizeof(noa)); + if (ret >= 2) { + if (link->u.mgd.p2p_noa_index != noa.index) { + /* valid noa_attr and index changed */ + link->u.mgd.p2p_noa_index = noa.index; + memcpy(&bss_conf->p2p_noa_attr, &noa, sizeof(noa)); + changed |= BSS_CHANGED_P2P_PS; + /* + * make sure we update all information, the CRC + * mechanism doesn't look at P2P attributes. + */ + link->u.mgd.beacon_crc_valid = false; + } + } else if (link->u.mgd.p2p_noa_index != -1) { + /* noa_attr not found and we had valid noa_attr before */ + link->u.mgd.p2p_noa_index = -1; + memset(&bss_conf->p2p_noa_attr, 0, sizeof(bss_conf->p2p_noa_attr)); + changed |= BSS_CHANGED_P2P_PS; + link->u.mgd.beacon_crc_valid = false; + } + } - sdata->u.mgd.connection_loss = false; - ieee80211_queue_work(&sdata->local->hw, - &sdata->u.mgd.beacon_connection_loss_work); -} + if (link->u.mgd.csa_waiting_bcn) + ieee80211_chswitch_post_beacon(link); -static void ieee80211_sta_conn_mon_timer(struct timer_list *t) -{ - struct ieee80211_sub_if_data *sdata = - from_timer(sdata, t, u.mgd.conn_mon_timer); - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - struct ieee80211_local *local = sdata->local; - struct sta_info *sta; - unsigned long timeout; + /* + * Update beacon timing and dtim count on every beacon appearance. This + * will allow the driver to use the most updated values. Do it before + * comparing this one with last received beacon. + * IMPORTANT: These parameters would possibly be out of sync by the time + * the driver will use them. The synchronized view is currently + * guaranteed only in certain callbacks. + */ + if (ieee80211_hw_check(&local->hw, TIMING_BEACON_ONLY) && + !ieee80211_is_s1g_beacon(hdr->frame_control)) { + link->conf->sync_tsf = + le64_to_cpu(mgmt->u.beacon.timestamp); + link->conf->sync_device_ts = + rx_status->device_timestamp; + link->conf->sync_dtim_count = elems->dtim_count; + } - if (WARN_ON(sdata->vif.valid_links)) - return; + if ((ncrc == link->u.mgd.beacon_crc && link->u.mgd.beacon_crc_valid) || + ieee80211_is_s1g_short_beacon(mgmt->frame_control)) + goto free; + link->u.mgd.beacon_crc = ncrc; + link->u.mgd.beacon_crc_valid = true; - if (sdata->vif.bss_conf.csa_active && - !sdata->deflink.u.mgd.csa_waiting_bcn) - return; + ieee80211_rx_bss_info(link, mgmt, len, rx_status); - sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); - if (!sta) - return; + ieee80211_sta_process_chanswitch(link, rx_status->mactime, + rx_status->device_timestamp, + elems, true); - timeout = sta->deflink.status_stats.last_ack; - if (time_before(sta->deflink.status_stats.last_ack, sta->deflink.rx_stats.last_rx)) - timeout = sta->deflink.rx_stats.last_rx; - timeout += IEEE80211_CONNECTION_IDLE_TIME; + if (!link->u.mgd.disable_wmm_tracking && + ieee80211_sta_wmm_params(local, link, elems->wmm_param, + elems->wmm_param_len, + elems->mu_edca_param_set)) + changed |= BSS_CHANGED_QOS; - /* If timeout is after now, then update timer to fire at - * the later date, but do not actually probe at this time. + /* + * If we haven't had a beacon before, tell the driver about the + * DTIM period (and beacon timing if desired) now. */ - if (time_is_after_jiffies(timeout)) { - mod_timer(&ifmgd->conn_mon_timer, round_jiffies_up(timeout)); - return; - } - - ieee80211_queue_work(&local->hw, &ifmgd->monitor_work); -} + if (!link->u.mgd.have_beacon) { + /* a few bogus AP send dtim_period = 0 or no TIM IE */ + bss_conf->dtim_period = elems->dtim_period ?: 1; -static void ieee80211_sta_monitor_work(struct work_struct *work) -{ - struct ieee80211_sub_if_data *sdata = - container_of(work, struct ieee80211_sub_if_data, - u.mgd.monitor_work); + changed |= BSS_CHANGED_BEACON_INFO; + link->u.mgd.have_beacon = true; - ieee80211_mgd_probe_ap(sdata, false); -} + mutex_lock(&local->iflist_mtx); + ieee80211_recalc_ps(local); + mutex_unlock(&local->iflist_mtx); -static void ieee80211_restart_sta_timer(struct ieee80211_sub_if_data *sdata) -{ - if (sdata->vif.type == NL80211_IFTYPE_STATION) { - __ieee80211_stop_poll(sdata); + ieee80211_recalc_ps_vif(sdata); + } - /* let's probe the connection once */ - if (!ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR)) - ieee80211_queue_work(&sdata->local->hw, - &sdata->u.mgd.monitor_work); + if (elems->erp_info) { + erp_valid = true; + erp_value = elems->erp_info[0]; + } else { + erp_valid = false; } -} -#ifdef CONFIG_PM -void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata) -{ - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; + if (!ieee80211_is_s1g_beacon(hdr->frame_control)) + changed |= ieee80211_handle_bss_capability(link, + le16_to_cpu(mgmt->u.beacon.capab_info), + erp_valid, erp_value); - sdata_lock(sdata); + mutex_lock(&local->sta_mtx); + sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); + if (WARN_ON(!sta)) + goto free; + link_sta = rcu_dereference_protected(sta->link[link->link_id], + lockdep_is_held(&local->sta_mtx)); + if (WARN_ON(!link_sta)) + goto free; - if (ifmgd->auth_data || ifmgd->assoc_data) { - const u8 *bssid = ifmgd->auth_data ? - ifmgd->auth_data->bss->bssid : - ifmgd->assoc_data->bss->bssid; + changed |= ieee80211_recalc_twt_req(link, link_sta, elems); - /* - * If we are trying to authenticate / associate while suspending, - * cfg80211 won't know and won't actually abort those attempts, - * thus we need to do that ourselves. - */ - ieee80211_send_deauth_disassoc(sdata, bssid, bssid, - IEEE80211_STYPE_DEAUTH, - WLAN_REASON_DEAUTH_LEAVING, - false, frame_buf); - if (ifmgd->assoc_data) - ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); - if (ifmgd->auth_data) - ieee80211_destroy_auth_data(sdata, false); - cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, - IEEE80211_DEAUTH_FRAME_LEN, - false); + if (ieee80211_config_bw(link, elems->ht_cap_elem, + elems->vht_cap_elem, elems->ht_operation, + elems->vht_operation, elems->he_operation, + elems->eht_operation, + elems->s1g_oper, bssid, &changed)) { + mutex_unlock(&local->sta_mtx); + sdata_info(sdata, + "failed to follow AP %pM bandwidth change, disconnect\n", + bssid); + ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH, + WLAN_REASON_DEAUTH_LEAVING, + true, deauth_buf); + ieee80211_report_disconnect(sdata, deauth_buf, + sizeof(deauth_buf), true, + WLAN_REASON_DEAUTH_LEAVING, + false); + goto free; } - /* This is a bit of a hack - we should find a better and more generic - * solution to this. Normally when suspending, cfg80211 will in fact - * deauthenticate. However, it doesn't (and cannot) stop an ongoing - * auth (not so important) or assoc (this is the problem) process. - * - * As a consequence, it can happen that we are in the process of both - * associating and suspending, and receive an association response - * after cfg80211 has checked if it needs to disconnect, but before - * we actually set the flag to drop incoming frames. This will then - * cause the workqueue flush to process the association response in - * the suspend, resulting in a successful association just before it - * tries to remove the interface from the driver, which now though - * has a channel context assigned ... this results in issues. - * - * To work around this (for now) simply deauth here again if we're - * now connected. - */ - if (ifmgd->associated && !sdata->local->wowlan) { - u8 bssid[ETH_ALEN]; - struct cfg80211_deauth_request req = { - .reason_code = WLAN_REASON_DEAUTH_LEAVING, - .bssid = bssid, - }; + if (sta && elems->opmode_notif) + ieee80211_vht_handle_opmode(sdata, link_sta, + *elems->opmode_notif, + rx_status->band); + mutex_unlock(&local->sta_mtx); - memcpy(bssid, sdata->vif.cfg.ap_addr, ETH_ALEN); - ieee80211_mgd_deauth(sdata, &req); - } + changed |= ieee80211_handle_pwr_constr(link, chan, mgmt, + elems->country_elem, + elems->country_elem_len, + elems->pwr_constr_elem, + elems->cisco_dtpc_elem); - sdata_unlock(sdata); + ieee80211_link_info_change_notify(sdata, link, changed); +free: + kfree(elems); } -#endif -void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata) +void ieee80211_sta_rx_queued_ext(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) { - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - - sdata_lock(sdata); - if (!ifmgd->associated) { - sdata_unlock(sdata); - return; - } + struct ieee80211_link_data *link = &sdata->deflink; + struct ieee80211_rx_status *rx_status; + struct ieee80211_hdr *hdr; + u16 fc; - if (sdata->flags & IEEE80211_SDATA_DISCONNECT_RESUME) { - sdata->flags &= ~IEEE80211_SDATA_DISCONNECT_RESUME; - mlme_dbg(sdata, "driver requested disconnect after resume\n"); - ieee80211_sta_connection_lost(sdata, - WLAN_REASON_UNSPECIFIED, - true); - sdata_unlock(sdata); - return; - } + rx_status = (struct ieee80211_rx_status *) skb->cb; + hdr = (struct ieee80211_hdr *) skb->data; + fc = le16_to_cpu(hdr->frame_control); - if (sdata->flags & IEEE80211_SDATA_DISCONNECT_HW_RESTART) { - sdata->flags &= ~IEEE80211_SDATA_DISCONNECT_HW_RESTART; - mlme_dbg(sdata, "driver requested disconnect after hardware restart\n"); - ieee80211_sta_connection_lost(sdata, - WLAN_REASON_UNSPECIFIED, - true); - sdata_unlock(sdata); - return; + sdata_lock(sdata); + switch (fc & IEEE80211_FCTL_STYPE) { + case IEEE80211_STYPE_S1G_BEACON: + ieee80211_rx_mgmt_beacon(link, hdr, skb->len, rx_status); + break; } - sdata_unlock(sdata); } -static void ieee80211_request_smps_mgd_work(struct work_struct *work) +void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) { - struct ieee80211_link_data *link = - container_of(work, struct ieee80211_link_data, - u.mgd.request_smps_work); + struct ieee80211_link_data *link = &sdata->deflink; + struct ieee80211_rx_status *rx_status; + struct ieee80211_mgmt *mgmt; + u16 fc; + int ies_len; - sdata_lock(link->sdata); - __ieee80211_request_smps_mgd(link->sdata, link, - link->u.mgd.driver_smps_mode); - sdata_unlock(link->sdata); -} + rx_status = (struct ieee80211_rx_status *) skb->cb; + mgmt = (struct ieee80211_mgmt *) skb->data; + fc = le16_to_cpu(mgmt->frame_control); -/* interface setup */ -void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata) -{ - struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + sdata_lock(sdata); - INIT_WORK(&ifmgd->monitor_work, ieee80211_sta_monitor_work); - INIT_WORK(&ifmgd->beacon_connection_loss_work, - ieee80211_beacon_connection_loss_work); - INIT_WORK(&ifmgd->csa_connection_drop_work, - ieee80211_csa_connection_drop_work); - INIT_DELAYED_WORK(&ifmgd->tdls_peer_del_work, - ieee80211_tdls_peer_del_work); - timer_setup(&ifmgd->timer, ieee80211_sta_timer, 0); - timer_setup(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer, 0); - timer_setup(&ifmgd->conn_mon_timer, ieee80211_sta_conn_mon_timer, 0); - INIT_DELAYED_WORK(&ifmgd->tx_tspec_wk, - ieee80211_sta_handle_tspec_ac_params_wk); + switch (fc & IEEE80211_FCTL_STYPE) { + case IEEE80211_STYPE_BEACON: + ieee80211_rx_mgmt_beacon(link, (void *)mgmt, + skb->len, rx_status); + break; + case IEEE80211_STYPE_PROBE_RESP: + ieee80211_rx_mgmt_probe_resp(link, skb); + break; + case IEEE80211_STYPE_AUTH: + ieee80211_rx_mgmt_auth(sdata, mgmt, skb->len); + break; + case IEEE80211_STYPE_DEAUTH: + ieee80211_rx_mgmt_deauth(sdata, mgmt, skb->len); + break; + case IEEE80211_STYPE_DISASSOC: + ieee80211_rx_mgmt_disassoc(sdata, mgmt, skb->len); + break; + case IEEE80211_STYPE_ASSOC_RESP: + case IEEE80211_STYPE_REASSOC_RESP: + ieee80211_rx_mgmt_assoc_resp(sdata, mgmt, skb->len); + break; + case IEEE80211_STYPE_ACTION: + if (mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT) { + struct ieee802_11_elems *elems; - ifmgd->flags = 0; - ifmgd->powersave = sdata->wdev.ps; - ifmgd->uapsd_queues = sdata->local->hw.uapsd_queues; - ifmgd->uapsd_max_sp_len = sdata->local->hw.uapsd_max_sp_len; - /* Setup TDLS data */ - spin_lock_init(&ifmgd->teardown_lock); - ifmgd->teardown_skb = NULL; - ifmgd->orig_teardown_skb = NULL; -} + ies_len = skb->len - + offsetof(struct ieee80211_mgmt, + u.action.u.chan_switch.variable); -void ieee80211_mgd_setup_link(struct ieee80211_link_data *link) -{ - struct ieee80211_local *local = link->sdata->local; + if (ies_len < 0) + break; - link->u.mgd.p2p_noa_index = -1; - link->u.mgd.conn_flags = 0; - link->conf->bssid = link->u.mgd.bssid; + /* CSA IE cannot be overridden, no need for BSSID */ + elems = ieee802_11_parse_elems( + mgmt->u.action.u.chan_switch.variable, + ies_len, true, NULL); - INIT_WORK(&link->u.mgd.request_smps_work, - ieee80211_request_smps_mgd_work); - if (local->hw.wiphy->features & NL80211_FEATURE_DYNAMIC_SMPS) - link->u.mgd.req_smps = IEEE80211_SMPS_AUTOMATIC; - else - link->u.mgd.req_smps = IEEE80211_SMPS_OFF; + if (elems && !elems->parse_error) + ieee80211_sta_process_chanswitch(link, + rx_status->mactime, + rx_status->device_timestamp, + elems, false); + kfree(elems); + } else if (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) { + struct ieee802_11_elems *elems; - INIT_WORK(&link->u.mgd.chswitch_work, ieee80211_chswitch_work); - timer_setup(&link->u.mgd.chswitch_timer, ieee80211_chswitch_timer, 0); -} + ies_len = skb->len - + offsetof(struct ieee80211_mgmt, + u.action.u.ext_chan_switch.variable); -/* scan finished notification */ -void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local) -{ - struct ieee80211_sub_if_data *sdata; + if (ies_len < 0) + break; - /* Restart STA timers */ - rcu_read_lock(); - list_for_each_entry_rcu(sdata, &local->interfaces, list) { - if (ieee80211_sdata_running(sdata)) - ieee80211_restart_sta_timer(sdata); + /* + * extended CSA IE can't be overridden, no need for + * BSSID + */ + elems = ieee802_11_parse_elems( + mgmt->u.action.u.ext_chan_switch.variable, + ies_len, true, NULL); + + if (elems && !elems->parse_error) { + /* for the handling code pretend it was an IE */ + elems->ext_chansw_ie = + &mgmt->u.action.u.ext_chan_switch.data; + + ieee80211_sta_process_chanswitch(link, + rx_status->mactime, + rx_status->device_timestamp, + elems, false); + } + + kfree(elems); + } + break; } - rcu_read_unlock(); + sdata_unlock(sdata); } -static u8 ieee80211_max_rx_chains(struct ieee80211_link_data *link, - struct cfg80211_bss *cbss) +static void ieee80211_sta_timer(struct timer_list *t) { - struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp; - const struct element *ht_cap_elem, *vht_cap_elem; - const struct cfg80211_bss_ies *ies; - const struct ieee80211_ht_cap *ht_cap; - const struct ieee80211_vht_cap *vht_cap; - const struct ieee80211_he_cap_elem *he_cap; - const struct element *he_cap_elem; - u16 mcs_80_map, mcs_160_map; - int i, mcs_nss_size; - bool support_160; - u8 chains = 1; + struct ieee80211_sub_if_data *sdata = + from_timer(sdata, t, u.mgd.timer); - if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) - return chains; + ieee80211_queue_work(&sdata->local->hw, &sdata->work); +} - ht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_HT_CAPABILITY); - if (ht_cap_elem && ht_cap_elem->datalen >= sizeof(*ht_cap)) { - ht_cap = (void *)ht_cap_elem->data; - chains = ieee80211_mcs_to_chains(&ht_cap->mcs); - /* - * TODO: use "Tx Maximum Number Spatial Streams Supported" and - * "Tx Unequal Modulation Supported" fields. - */ - } +void ieee80211_sta_connection_lost(struct ieee80211_sub_if_data *sdata, + u8 reason, bool tx) +{ + u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; - if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) - return chains; + ieee80211_set_disassoc(sdata, IEEE80211_STYPE_DEAUTH, reason, + tx, frame_buf); - vht_cap_elem = ieee80211_bss_get_elem(cbss, WLAN_EID_VHT_CAPABILITY); - if (vht_cap_elem && vht_cap_elem->datalen >= sizeof(*vht_cap)) { - u8 nss; - u16 tx_mcs_map; + ieee80211_report_disconnect(sdata, frame_buf, sizeof(frame_buf), true, + reason, false); +} - vht_cap = (void *)vht_cap_elem->data; - tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map); - for (nss = 8; nss > 0; nss--) { - if (((tx_mcs_map >> (2 * (nss - 1))) & 3) != - IEEE80211_VHT_MCS_NOT_SUPPORTED) - break; - } - /* TODO: use "Tx Highest Supported Long GI Data Rate" field? */ - chains = max(chains, nss); - } +static int ieee80211_auth(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_mgd_auth_data *auth_data = ifmgd->auth_data; + u32 tx_flags = 0; + u16 trans = 1; + u16 status = 0; + struct ieee80211_prep_tx_info info = { + .subtype = IEEE80211_STYPE_AUTH, + }; + + sdata_assert_lock(sdata); - if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE) - return chains; + if (WARN_ON_ONCE(!auth_data)) + return -EINVAL; - ies = rcu_dereference(cbss->ies); - he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, - ies->data, ies->len); + auth_data->tries++; - if (!he_cap_elem || he_cap_elem->datalen < sizeof(*he_cap)) - return chains; + if (auth_data->tries > IEEE80211_AUTH_MAX_TRIES) { + sdata_info(sdata, "authentication with %pM timed out\n", + auth_data->bss->bssid); - /* skip one byte ext_tag_id */ - he_cap = (void *)(he_cap_elem->data + 1); - mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap); + /* + * Most likely AP is not in the range so remove the + * bss struct for that AP. + */ + cfg80211_unlink_bss(local->hw.wiphy, auth_data->bss); - /* invalid HE IE */ - if (he_cap_elem->datalen < 1 + mcs_nss_size + sizeof(*he_cap)) - return chains; + return -ETIMEDOUT; + } - /* mcs_nss is right after he_cap info */ - he_mcs_nss_supp = (void *)(he_cap + 1); + if (auth_data->algorithm == WLAN_AUTH_SAE) + info.duration = jiffies_to_msecs(IEEE80211_AUTH_TIMEOUT_SAE); - mcs_80_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80); + drv_mgd_prepare_tx(local, sdata, &info); - for (i = 7; i >= 0; i--) { - u8 mcs_80 = mcs_80_map >> (2 * i) & 3; + sdata_info(sdata, "send auth to %pM (try %d/%d)\n", + auth_data->bss->bssid, auth_data->tries, + IEEE80211_AUTH_MAX_TRIES); - if (mcs_80 != IEEE80211_VHT_MCS_NOT_SUPPORTED) { - chains = max_t(u8, chains, i + 1); - break; - } - } + auth_data->expected_transaction = 2; - support_160 = he_cap->phy_cap_info[0] & - IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + if (auth_data->algorithm == WLAN_AUTH_SAE) { + trans = auth_data->sae_trans; + status = auth_data->sae_status; + auth_data->expected_transaction = trans; + } - if (!support_160) - return chains; + if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) + tx_flags = IEEE80211_TX_CTL_REQ_TX_STATUS | + IEEE80211_TX_INTFL_MLME_CONN_TX; - mcs_160_map = le16_to_cpu(he_mcs_nss_supp->tx_mcs_160); - for (i = 7; i >= 0; i--) { - u8 mcs_160 = mcs_160_map >> (2 * i) & 3; + ieee80211_send_auth(sdata, trans, auth_data->algorithm, status, + auth_data->data, auth_data->data_len, + auth_data->bss->bssid, + auth_data->bss->bssid, NULL, 0, 0, + tx_flags); - if (mcs_160 != IEEE80211_VHT_MCS_NOT_SUPPORTED) { - chains = max_t(u8, chains, i + 1); - break; - } + if (tx_flags == 0) { + if (auth_data->algorithm == WLAN_AUTH_SAE) + auth_data->timeout = jiffies + + IEEE80211_AUTH_TIMEOUT_SAE; + else + auth_data->timeout = jiffies + IEEE80211_AUTH_TIMEOUT; + } else { + auth_data->timeout = + round_jiffies_up(jiffies + IEEE80211_AUTH_TIMEOUT_LONG); } - return chains; + auth_data->timeout_started = true; + run_again(sdata, auth_data->timeout); + + return 0; } -static bool -ieee80211_verify_peer_he_mcs_support(struct ieee80211_sub_if_data *sdata, - const struct cfg80211_bss_ies *ies, - const struct ieee80211_he_operation *he_op) +static int ieee80211_do_assoc(struct ieee80211_sub_if_data *sdata) { - const struct element *he_cap_elem; - const struct ieee80211_he_cap_elem *he_cap; - struct ieee80211_he_mcs_nss_supp *he_mcs_nss_supp; - u16 mcs_80_map_tx, mcs_80_map_rx; - u16 ap_min_req_set; - int mcs_nss_size; - int nss; + struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data; + struct ieee80211_local *local = sdata->local; + int ret; - he_cap_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, - ies->data, ies->len); + sdata_assert_lock(sdata); - /* invalid HE IE */ - if (!he_cap_elem || he_cap_elem->datalen < 1 + sizeof(*he_cap)) { - sdata_info(sdata, - "Invalid HE elem, Disable HE\n"); - return false; - } + assoc_data->tries++; + if (assoc_data->tries > IEEE80211_ASSOC_MAX_TRIES) { + sdata_info(sdata, "association with %pM timed out\n", + assoc_data->bss->bssid); - /* skip one byte ext_tag_id */ - he_cap = (void *)(he_cap_elem->data + 1); - mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap); + /* + * Most likely AP is not in the range so remove the + * bss struct for that AP. + */ + cfg80211_unlink_bss(local->hw.wiphy, assoc_data->bss); - /* invalid HE IE */ - if (he_cap_elem->datalen < 1 + sizeof(*he_cap) + mcs_nss_size) { - sdata_info(sdata, - "Invalid HE elem with nss size, Disable HE\n"); - return false; + return -ETIMEDOUT; } - /* mcs_nss is right after he_cap info */ - he_mcs_nss_supp = (void *)(he_cap + 1); - - mcs_80_map_tx = le16_to_cpu(he_mcs_nss_supp->tx_mcs_80); - mcs_80_map_rx = le16_to_cpu(he_mcs_nss_supp->rx_mcs_80); + sdata_info(sdata, "associate with %pM (try %d/%d)\n", + assoc_data->bss->bssid, assoc_data->tries, + IEEE80211_ASSOC_MAX_TRIES); + ret = ieee80211_send_assoc(sdata); + if (ret) + return ret; - /* P802.11-REVme/D0.3 - * 27.1.1 Introduction to the HE PHY - * ... - * An HE STA shall support the following features: - * ... - * Single spatial stream HE-MCSs 0 to 7 (transmit and receive) in all - * supported channel widths for HE SU PPDUs - */ - if ((mcs_80_map_tx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED || - (mcs_80_map_rx & 0x3) == IEEE80211_HE_MCS_NOT_SUPPORTED) { - sdata_info(sdata, - "Missing mandatory rates for 1 Nss, rx 0x%x, tx 0x%x, disable HE\n", - mcs_80_map_tx, mcs_80_map_rx); - return false; + if (!ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) { + assoc_data->timeout = jiffies + IEEE80211_ASSOC_TIMEOUT; + assoc_data->timeout_started = true; + run_again(sdata, assoc_data->timeout); + } else { + assoc_data->timeout = + round_jiffies_up(jiffies + + IEEE80211_ASSOC_TIMEOUT_LONG); + assoc_data->timeout_started = true; + run_again(sdata, assoc_data->timeout); } - if (!he_op) - return true; + return 0; +} - ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set); +void ieee80211_mgd_conn_tx_status(struct ieee80211_sub_if_data *sdata, + __le16 fc, bool acked) +{ + struct ieee80211_local *local = sdata->local; - /* make sure the AP is consistent with itself - * - * P802.11-REVme/D0.3 - * 26.17.1 Basic HE BSS operation - * - * A STA that is operating in an HE BSS shall be able to receive and - * transmit at each of the <HE-MCS, NSS> tuple values indicated by the - * Basic HE-MCS And NSS Set field of the HE Operation parameter of the - * MLME-START.request primitive and shall be able to receive at each of - * the <HE-MCS, NSS> tuple values indicated by the Supported HE-MCS and - * NSS Set field in the HE Capabilities parameter of the MLMESTART.request - * primitive - */ - for (nss = 8; nss > 0; nss--) { - u8 ap_op_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; - u8 ap_rx_val; - u8 ap_tx_val; + sdata->u.mgd.status_fc = fc; + sdata->u.mgd.status_acked = acked; + sdata->u.mgd.status_received = true; - if (ap_op_val == IEEE80211_HE_MCS_NOT_SUPPORTED) - continue; + ieee80211_queue_work(&local->hw, &sdata->work); +} - ap_rx_val = (mcs_80_map_rx >> (2 * (nss - 1))) & 3; - ap_tx_val = (mcs_80_map_tx >> (2 * (nss - 1))) & 3; +void ieee80211_sta_work(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_local *local = sdata->local; + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - if (ap_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || - ap_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || - ap_rx_val < ap_op_val || ap_tx_val < ap_op_val) { - sdata_info(sdata, - "Invalid rates for %d Nss, rx %d, tx %d oper %d, disable HE\n", - nss, ap_rx_val, ap_rx_val, ap_op_val); - return false; + sdata_lock(sdata); + + if (ifmgd->status_received) { + __le16 fc = ifmgd->status_fc; + bool status_acked = ifmgd->status_acked; + + ifmgd->status_received = false; + if (ifmgd->auth_data && ieee80211_is_auth(fc)) { + if (status_acked) { + if (ifmgd->auth_data->algorithm == + WLAN_AUTH_SAE) + ifmgd->auth_data->timeout = + jiffies + + IEEE80211_AUTH_TIMEOUT_SAE; + else + ifmgd->auth_data->timeout = + jiffies + + IEEE80211_AUTH_TIMEOUT_SHORT; + run_again(sdata, ifmgd->auth_data->timeout); + } else { + ifmgd->auth_data->timeout = jiffies - 1; + } + ifmgd->auth_data->timeout_started = true; + } else if (ifmgd->assoc_data && + (ieee80211_is_assoc_req(fc) || + ieee80211_is_reassoc_req(fc))) { + if (status_acked) { + ifmgd->assoc_data->timeout = + jiffies + IEEE80211_ASSOC_TIMEOUT_SHORT; + run_again(sdata, ifmgd->assoc_data->timeout); + } else { + ifmgd->assoc_data->timeout = jiffies - 1; + } + ifmgd->assoc_data->timeout_started = true; } } - return true; -} + if (ifmgd->auth_data && ifmgd->auth_data->timeout_started && + time_after(jiffies, ifmgd->auth_data->timeout)) { + if (ifmgd->auth_data->done || ifmgd->auth_data->waiting) { + /* + * ok ... we waited for assoc or continuation but + * userspace didn't do it, so kill the auth data + */ + ieee80211_destroy_auth_data(sdata, false); + } else if (ieee80211_auth(sdata)) { + u8 bssid[ETH_ALEN]; + struct ieee80211_event event = { + .type = MLME_EVENT, + .u.mlme.data = AUTH_EVENT, + .u.mlme.status = MLME_TIMEOUT, + }; + + memcpy(bssid, ifmgd->auth_data->bss->bssid, ETH_ALEN); -static bool -ieee80211_verify_sta_he_mcs_support(struct ieee80211_sub_if_data *sdata, - struct ieee80211_supported_band *sband, - const struct ieee80211_he_operation *he_op) -{ - const struct ieee80211_sta_he_cap *sta_he_cap = - ieee80211_get_he_iftype_cap(sband, - ieee80211_vif_type_p2p(&sdata->vif)); - u16 ap_min_req_set; - int i; + ieee80211_destroy_auth_data(sdata, false); - if (!sta_he_cap || !he_op) - return false; + cfg80211_auth_timeout(sdata->dev, bssid); + drv_event_callback(sdata->local, sdata, &event); + } + } else if (ifmgd->auth_data && ifmgd->auth_data->timeout_started) + run_again(sdata, ifmgd->auth_data->timeout); - ap_min_req_set = le16_to_cpu(he_op->he_mcs_nss_set); + if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started && + time_after(jiffies, ifmgd->assoc_data->timeout)) { + if ((ifmgd->assoc_data->need_beacon && + !sdata->deflink.u.mgd.have_beacon) || + ieee80211_do_assoc(sdata)) { + struct ieee80211_event event = { + .type = MLME_EVENT, + .u.mlme.data = ASSOC_EVENT, + .u.mlme.status = MLME_TIMEOUT, + }; - /* Need to go over for 80MHz, 160MHz and for 80+80 */ - for (i = 0; i < 3; i++) { - const struct ieee80211_he_mcs_nss_supp *sta_mcs_nss_supp = - &sta_he_cap->he_mcs_nss_supp; - u16 sta_mcs_map_rx = - le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i]); - u16 sta_mcs_map_tx = - le16_to_cpu(((__le16 *)sta_mcs_nss_supp)[2 * i + 1]); - u8 nss; - bool verified = true; + ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); + drv_event_callback(sdata->local, sdata, &event); + } + } else if (ifmgd->assoc_data && ifmgd->assoc_data->timeout_started) + run_again(sdata, ifmgd->assoc_data->timeout); - /* - * For each band there is a maximum of 8 spatial streams - * possible. Each of the sta_mcs_map_* is a 16-bit struct built - * of 2 bits per NSS (1-8), with the values defined in enum - * ieee80211_he_mcs_support. Need to make sure STA TX and RX - * capabilities aren't less than the AP's minimum requirements - * for this HE BSS per SS. - * It is enough to find one such band that meets the reqs. - */ - for (nss = 8; nss > 0; nss--) { - u8 sta_rx_val = (sta_mcs_map_rx >> (2 * (nss - 1))) & 3; - u8 sta_tx_val = (sta_mcs_map_tx >> (2 * (nss - 1))) & 3; - u8 ap_val = (ap_min_req_set >> (2 * (nss - 1))) & 3; + if (ifmgd->flags & IEEE80211_STA_CONNECTION_POLL && + ifmgd->associated) { + u8 *bssid = sdata->deflink.u.mgd.bssid; + int max_tries; - if (ap_val == IEEE80211_HE_MCS_NOT_SUPPORTED) - continue; + if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) + max_tries = max_nullfunc_tries; + else + max_tries = max_probe_tries; + /* ACK received for nullfunc probing frame */ + if (!ifmgd->probe_send_count) + ieee80211_reset_ap_probe(sdata); + else if (ifmgd->nullfunc_failed) { + if (ifmgd->probe_send_count < max_tries) { + mlme_dbg(sdata, + "No ack for nullfunc frame to AP %pM, try %d/%i\n", + bssid, ifmgd->probe_send_count, + max_tries); + ieee80211_mgd_probe_ap_send(sdata); + } else { + mlme_dbg(sdata, + "No ack for nullfunc frame to AP %pM, disconnecting.\n", + bssid); + ieee80211_sta_connection_lost(sdata, + WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, + false); + } + } else if (time_is_after_jiffies(ifmgd->probe_timeout)) + run_again(sdata, ifmgd->probe_timeout); + else if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS)) { + mlme_dbg(sdata, + "Failed to send nullfunc to AP %pM after %dms, disconnecting\n", + bssid, probe_wait_ms); + ieee80211_sta_connection_lost(sdata, + WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, false); + } else if (ifmgd->probe_send_count < max_tries) { + mlme_dbg(sdata, + "No probe response from AP %pM after %dms, try %d/%i\n", + bssid, probe_wait_ms, + ifmgd->probe_send_count, max_tries); + ieee80211_mgd_probe_ap_send(sdata); + } else { /* - * Make sure the HE AP doesn't require MCSs that aren't - * supported by the client as required by spec - * - * P802.11-REVme/D0.3 - * 26.17.1 Basic HE BSS operation - * - * An HE STA shall not attempt to join * (MLME-JOIN.request primitive) - * a BSS, unless it supports (i.e., is able to both transmit and - * receive using) all of the <HE-MCS, NSS> tuples in the basic - * HE-MCS and NSS set. + * We actually lost the connection ... or did we? + * Let's make sure! */ - if (sta_rx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || - sta_tx_val == IEEE80211_HE_MCS_NOT_SUPPORTED || - (ap_val > sta_rx_val) || (ap_val > sta_tx_val)) { - verified = false; - break; - } - } + mlme_dbg(sdata, + "No probe response from AP %pM after %dms, disconnecting.\n", + bssid, probe_wait_ms); - if (verified) - return true; + ieee80211_sta_connection_lost(sdata, + WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY, false); + } } - /* If here, STA doesn't meet AP's HE min requirements */ - return false; + sdata_unlock(sdata); } -static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, - struct ieee80211_link_data *link, - struct cfg80211_bss *cbss) +static void ieee80211_sta_bcn_mon_timer(struct timer_list *t) { - struct ieee80211_local *local = sdata->local; - const struct ieee80211_ht_cap *ht_cap = NULL; - const struct ieee80211_ht_operation *ht_oper = NULL; - const struct ieee80211_vht_operation *vht_oper = NULL; - const struct ieee80211_he_operation *he_oper = NULL; - const struct ieee80211_eht_operation *eht_oper = NULL; - const struct ieee80211_s1g_oper_ie *s1g_oper = NULL; - struct ieee80211_supported_band *sband; - struct cfg80211_chan_def chandef; - bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ; - bool is_5ghz = cbss->channel->band == NL80211_BAND_5GHZ; - struct ieee80211_bss *bss = (void *)cbss->priv; - struct ieee802_11_elems *elems; - const struct cfg80211_bss_ies *ies; - int ret; - u32 i; - bool have_80mhz; - - rcu_read_lock(); - - ies = rcu_dereference(cbss->ies); - elems = ieee802_11_parse_elems(ies->data, ies->len, false, cbss); - if (!elems) { - rcu_read_unlock(); - return -ENOMEM; - } + struct ieee80211_sub_if_data *sdata = + from_timer(sdata, t, u.mgd.bcn_mon_timer); - sband = local->hw.wiphy->bands[cbss->channel->band]; + if (WARN_ON(sdata->vif.valid_links)) + return; - link->u.mgd.conn_flags &= ~(IEEE80211_CONN_DISABLE_40MHZ | - IEEE80211_CONN_DISABLE_80P80MHZ | - IEEE80211_CONN_DISABLE_160MHZ); + if (sdata->vif.bss_conf.csa_active && + !sdata->deflink.u.mgd.csa_waiting_bcn) + return; - /* disable HT/VHT/HE if we don't support them */ - if (!sband->ht_cap.ht_supported && !is_6ghz) { - mlme_dbg(sdata, "HT not supported, disabling HT/VHT/HE/EHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; - } + if (sdata->vif.driver_flags & IEEE80211_VIF_BEACON_FILTER) + return; - if (!sband->vht_cap.vht_supported && is_5ghz) { - mlme_dbg(sdata, "VHT not supported, disabling VHT/HE/EHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; - } + sdata->u.mgd.connection_loss = false; + ieee80211_queue_work(&sdata->local->hw, + &sdata->u.mgd.beacon_connection_loss_work); +} - if (!ieee80211_get_he_iftype_cap(sband, - ieee80211_vif_type_p2p(&sdata->vif))) { - mlme_dbg(sdata, "HE not supported, disabling HE and EHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; - } +static void ieee80211_sta_conn_mon_timer(struct timer_list *t) +{ + struct ieee80211_sub_if_data *sdata = + from_timer(sdata, t, u.mgd.conn_mon_timer); + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct ieee80211_local *local = sdata->local; + struct sta_info *sta; + unsigned long timeout; - if (!ieee80211_get_eht_iftype_cap(sband, - ieee80211_vif_type_p2p(&sdata->vif))) { - mlme_dbg(sdata, "EHT not supported, disabling EHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; - } + if (WARN_ON(sdata->vif.valid_links)) + return; - if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HT) && !is_6ghz) { - ht_oper = elems->ht_operation; - ht_cap = elems->ht_cap_elem; + if (sdata->vif.bss_conf.csa_active && + !sdata->deflink.u.mgd.csa_waiting_bcn) + return; - if (!ht_cap) { - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; - ht_oper = NULL; - } - } + sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); + if (!sta) + return; - if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_VHT) && !is_6ghz) { - vht_oper = elems->vht_operation; - if (vht_oper && !ht_oper) { - vht_oper = NULL; - sdata_info(sdata, - "AP advertised VHT without HT, disabling HT/VHT/HE\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HT; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE; - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_EHT; - } + timeout = sta->deflink.status_stats.last_ack; + if (time_before(sta->deflink.status_stats.last_ack, sta->deflink.rx_stats.last_rx)) + timeout = sta->deflink.rx_stats.last_rx; + timeout += IEEE80211_CONNECTION_IDLE_TIME; - if (!elems->vht_cap_elem) { - sdata_info(sdata, - "bad VHT capabilities, disabling VHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; - vht_oper = NULL; - } + /* If timeout is after now, then update timer to fire at + * the later date, but do not actually probe at this time. + */ + if (time_is_after_jiffies(timeout)) { + mod_timer(&ifmgd->conn_mon_timer, round_jiffies_up(timeout)); + return; } - if (!(link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE)) { - he_oper = elems->he_operation; + ieee80211_queue_work(&local->hw, &ifmgd->monitor_work); +} - if (is_6ghz) { - struct ieee80211_bss_conf *bss_conf; - u8 i, j = 0; +static void ieee80211_sta_monitor_work(struct work_struct *work) +{ + struct ieee80211_sub_if_data *sdata = + container_of(work, struct ieee80211_sub_if_data, + u.mgd.monitor_work); - bss_conf = link->conf; + ieee80211_mgd_probe_ap(sdata, false); +} - if (elems->pwr_constr_elem) - bss_conf->pwr_reduction = *elems->pwr_constr_elem; +static void ieee80211_restart_sta_timer(struct ieee80211_sub_if_data *sdata) +{ + if (sdata->vif.type == NL80211_IFTYPE_STATION) { + __ieee80211_stop_poll(sdata); - BUILD_BUG_ON(ARRAY_SIZE(bss_conf->tx_pwr_env) != - ARRAY_SIZE(elems->tx_pwr_env)); + /* let's probe the connection once */ + if (!ieee80211_hw_check(&sdata->local->hw, CONNECTION_MONITOR)) + ieee80211_queue_work(&sdata->local->hw, + &sdata->u.mgd.monitor_work); + } +} - for (i = 0; i < elems->tx_pwr_env_num; i++) { - if (elems->tx_pwr_env_len[i] > - sizeof(bss_conf->tx_pwr_env[j])) - continue; +#ifdef CONFIG_PM +void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + u8 frame_buf[IEEE80211_DEAUTH_FRAME_LEN]; - bss_conf->tx_pwr_env_num++; - memcpy(&bss_conf->tx_pwr_env[j], elems->tx_pwr_env[i], - elems->tx_pwr_env_len[i]); - j++; - } - } + sdata_lock(sdata); - if (!ieee80211_verify_peer_he_mcs_support(sdata, ies, he_oper) || - !ieee80211_verify_sta_he_mcs_support(sdata, sband, he_oper)) - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_HE | - IEEE80211_CONN_DISABLE_EHT; + if (ifmgd->auth_data || ifmgd->assoc_data) { + const u8 *bssid = ifmgd->auth_data ? + ifmgd->auth_data->bss->bssid : + ifmgd->assoc_data->bss->bssid; + + /* + * If we are trying to authenticate / associate while suspending, + * cfg80211 won't know and won't actually abort those attempts, + * thus we need to do that ourselves. + */ + ieee80211_send_deauth_disassoc(sdata, bssid, bssid, + IEEE80211_STYPE_DEAUTH, + WLAN_REASON_DEAUTH_LEAVING, + false, frame_buf); + if (ifmgd->assoc_data) + ieee80211_destroy_assoc_data(sdata, ASSOC_ABANDON); + if (ifmgd->auth_data) + ieee80211_destroy_auth_data(sdata, false); + cfg80211_tx_mlme_mgmt(sdata->dev, frame_buf, + IEEE80211_DEAUTH_FRAME_LEN, + false); } - /* - * EHT requires HE to be supported as well. Specifically for 6 GHz - * channels, the operation channel information can only be deduced from - * both the 6 GHz operation information (from the HE operation IE) and - * EHT operation. + /* This is a bit of a hack - we should find a better and more generic + * solution to this. Normally when suspending, cfg80211 will in fact + * deauthenticate. However, it doesn't (and cannot) stop an ongoing + * auth (not so important) or assoc (this is the problem) process. + * + * As a consequence, it can happen that we are in the process of both + * associating and suspending, and receive an association response + * after cfg80211 has checked if it needs to disconnect, but before + * we actually set the flag to drop incoming frames. This will then + * cause the workqueue flush to process the association response in + * the suspend, resulting in a successful association just before it + * tries to remove the interface from the driver, which now though + * has a channel context assigned ... this results in issues. + * + * To work around this (for now) simply deauth here again if we're + * now connected. */ - if (!(link->u.mgd.conn_flags & - (IEEE80211_CONN_DISABLE_HE | - IEEE80211_CONN_DISABLE_EHT)) && - he_oper) { - const struct cfg80211_bss_ies *ies; - const u8 *eht_oper_ie; + if (ifmgd->associated && !sdata->local->wowlan) { + u8 bssid[ETH_ALEN]; + struct cfg80211_deauth_request req = { + .reason_code = WLAN_REASON_DEAUTH_LEAVING, + .bssid = bssid, + }; - ies = rcu_dereference(cbss->ies); - eht_oper_ie = cfg80211_find_ext_ie(WLAN_EID_EXT_EHT_OPERATION, - ies->data, ies->len); - if (eht_oper_ie && eht_oper_ie[1] >= - 1 + sizeof(struct ieee80211_eht_operation)) - eht_oper = (void *)(eht_oper_ie + 3); - else - eht_oper = NULL; + memcpy(bssid, sdata->vif.cfg.ap_addr, ETH_ALEN); + ieee80211_mgd_deauth(sdata, &req); } - /* Allow VHT if at least one channel on the sband supports 80 MHz */ - have_80mhz = false; - for (i = 0; i < sband->n_channels; i++) { - if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED | - IEEE80211_CHAN_NO_80MHZ)) - continue; + sdata_unlock(sdata); +} +#endif - have_80mhz = true; - break; +void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + + sdata_lock(sdata); + if (!ifmgd->associated) { + sdata_unlock(sdata); + return; } - if (!have_80mhz) { - sdata_info(sdata, "80 MHz not supported, disabling VHT\n"); - link->u.mgd.conn_flags |= IEEE80211_CONN_DISABLE_VHT; + if (sdata->flags & IEEE80211_SDATA_DISCONNECT_RESUME) { + sdata->flags &= ~IEEE80211_SDATA_DISCONNECT_RESUME; + mlme_dbg(sdata, "driver requested disconnect after resume\n"); + ieee80211_sta_connection_lost(sdata, + WLAN_REASON_UNSPECIFIED, + true); + sdata_unlock(sdata); + return; } - if (sband->band == NL80211_BAND_S1GHZ) { - s1g_oper = elems->s1g_oper; - if (!s1g_oper) - sdata_info(sdata, - "AP missing S1G operation element?\n"); + if (sdata->flags & IEEE80211_SDATA_DISCONNECT_HW_RESTART) { + sdata->flags &= ~IEEE80211_SDATA_DISCONNECT_HW_RESTART; + mlme_dbg(sdata, "driver requested disconnect after hardware restart\n"); + ieee80211_sta_connection_lost(sdata, + WLAN_REASON_UNSPECIFIED, + true); + sdata_unlock(sdata); + return; } - link->u.mgd.conn_flags |= - ieee80211_determine_chantype(link, sband, - cbss->channel, - bss->vht_cap_info, - ht_oper, vht_oper, - he_oper, eht_oper, - s1g_oper, - &chandef, false); + sdata_unlock(sdata); +} - link->needed_rx_chains = - min(ieee80211_max_rx_chains(link, cbss), local->rx_chains); +static void ieee80211_request_smps_mgd_work(struct work_struct *work) +{ + struct ieee80211_link_data *link = + container_of(work, struct ieee80211_link_data, + u.mgd.request_smps_work); - rcu_read_unlock(); - /* the element data was RCU protected so no longer valid anyway */ - kfree(elems); - elems = NULL; + sdata_lock(link->sdata); + __ieee80211_request_smps_mgd(link->sdata, link, + link->u.mgd.driver_smps_mode); + sdata_unlock(link->sdata); +} - if (link->u.mgd.conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) { - sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection"); - return -EINVAL; - } +/* interface setup */ +void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata) +{ + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; - /* will change later if needed */ - link->smps_mode = IEEE80211_SMPS_OFF; + INIT_WORK(&ifmgd->monitor_work, ieee80211_sta_monitor_work); + INIT_WORK(&ifmgd->beacon_connection_loss_work, + ieee80211_beacon_connection_loss_work); + INIT_WORK(&ifmgd->csa_connection_drop_work, + ieee80211_csa_connection_drop_work); + INIT_DELAYED_WORK(&ifmgd->tdls_peer_del_work, + ieee80211_tdls_peer_del_work); + timer_setup(&ifmgd->timer, ieee80211_sta_timer, 0); + timer_setup(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer, 0); + timer_setup(&ifmgd->conn_mon_timer, ieee80211_sta_conn_mon_timer, 0); + INIT_DELAYED_WORK(&ifmgd->tx_tspec_wk, + ieee80211_sta_handle_tspec_ac_params_wk); - mutex_lock(&local->mtx); - /* - * If this fails (possibly due to channel context sharing - * on incompatible channels, e.g. 80+80 and 160 sharing the - * same control channel) try to use a smaller bandwidth. - */ - ret = ieee80211_link_use_channel(link, &chandef, - IEEE80211_CHANCTX_SHARED); + ifmgd->flags = 0; + ifmgd->powersave = sdata->wdev.ps; + ifmgd->uapsd_queues = sdata->local->hw.uapsd_queues; + ifmgd->uapsd_max_sp_len = sdata->local->hw.uapsd_max_sp_len; + /* Setup TDLS data */ + spin_lock_init(&ifmgd->teardown_lock); + ifmgd->teardown_skb = NULL; + ifmgd->orig_teardown_skb = NULL; +} - /* don't downgrade for 5 and 10 MHz channels, though. */ - if (chandef.width == NL80211_CHAN_WIDTH_5 || - chandef.width == NL80211_CHAN_WIDTH_10) - goto out; +void ieee80211_mgd_setup_link(struct ieee80211_link_data *link) +{ + struct ieee80211_local *local = link->sdata->local; - while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) { - link->u.mgd.conn_flags |= - ieee80211_chandef_downgrade(&chandef); - ret = ieee80211_link_use_channel(link, &chandef, - IEEE80211_CHANCTX_SHARED); + link->u.mgd.p2p_noa_index = -1; + link->u.mgd.conn_flags = 0; + link->conf->bssid = link->u.mgd.bssid; + + INIT_WORK(&link->u.mgd.request_smps_work, + ieee80211_request_smps_mgd_work); + if (local->hw.wiphy->features & NL80211_FEATURE_DYNAMIC_SMPS) + link->u.mgd.req_smps = IEEE80211_SMPS_AUTOMATIC; + else + link->u.mgd.req_smps = IEEE80211_SMPS_OFF; + + INIT_WORK(&link->u.mgd.chswitch_work, ieee80211_chswitch_work); + timer_setup(&link->u.mgd.chswitch_timer, ieee80211_chswitch_timer, 0); +} + +/* scan finished notification */ +void ieee80211_mlme_notify_scan_completed(struct ieee80211_local *local) +{ + struct ieee80211_sub_if_data *sdata; + + /* Restart STA timers */ + rcu_read_lock(); + list_for_each_entry_rcu(sdata, &local->interfaces, list) { + if (ieee80211_sdata_running(sdata)) + ieee80211_restart_sta_timer(sdata); } - out: - mutex_unlock(&local->mtx); - return ret; + rcu_read_unlock(); } static bool ieee80211_get_dtim(const struct cfg80211_bss_ies *ies, -- cgit v1.2.3