From 61513162aa2d6c17c37d25de2d0e5020cd9b37ec Mon Sep 17 00:00:00 2001
From: Johannes Berg <johannes.berg@intel.com>
Date: Tue, 12 Jul 2022 11:13:56 +0200
Subject: wifi: mac80211: mlme: shift some code around

We'll need ieee80211_prep_channel() in other code for MLO
later, so move the code up - unchanged for now - to avoid
forward declarations in the future.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 net/mac80211/mlme.c | 3634 ++++++++++++++++++++++++++-------------------------
 1 file changed, 1825 insertions(+), 1809 deletions(-)

(limited to 'net/mac80211')

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