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,