wifi: mac80211: mlme: refactor assoc success handling
authorJohannes Berg <johannes.berg@intel.com>
Tue, 12 Jul 2022 12:59:22 +0000 (14:59 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 15 Jul 2022 09:43:22 +0000 (11:43 +0200)
Refactor the per-link setup out of ieee80211_assoc_success()
into a new function ieee80211_assoc_config_link().

It looks useless for now to parse the elements again inside
ieee80211_assoc_config_link(), but that will be done with
the link ID in the future.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/mlme.c

index f3d4507a477c18a8ca6d1d38ee187f5cfdf464f4..1829a0b6c9d4ba6b77584c6b68244f50882e36b5 100644 (file)
@@ -3541,6 +3541,326 @@ static bool ieee80211_twt_bcast_support(struct ieee80211_sub_if_data *sdata,
                        IEEE80211_HE_MAC_CAP2_BCAST_TWT);
 }
 
+static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
+                                       struct link_sta_info *link_sta,
+                                       struct cfg80211_bss *cbss,
+                                       struct ieee80211_mgmt *mgmt,
+                                       const u8 *elem_start,
+                                       unsigned int elem_len,
+                                       u64 *changed)
+{
+       struct ieee80211_sub_if_data *sdata = link->sdata;
+       struct ieee80211_mgd_assoc_data *assoc_data = sdata->u.mgd.assoc_data;
+       struct ieee80211_bss_conf *bss_conf = link->conf;
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_elems_parse_params parse_params = {
+               .start = elem_start,
+               .len = elem_len,
+               .bss = cbss,
+       };
+       bool is_6ghz = cbss->channel->band == NL80211_BAND_6GHZ;
+       bool is_s1g = cbss->channel->band == NL80211_BAND_S1GHZ;
+       const struct cfg80211_bss_ies *bss_ies = NULL;
+       struct ieee80211_supported_band *sband;
+       struct ieee802_11_elems *elems;
+       u16 capab_info;
+       bool ret;
+
+       elems = ieee802_11_parse_elems_full(&parse_params);
+       if (!elems)
+               return false;
+
+       capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
+
+       if (!is_s1g && !elems->supp_rates) {
+               sdata_info(sdata, "no SuppRates element in AssocResp\n");
+               ret = false;
+               goto out;
+       }
+
+       link->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);
+
+       /*
+        * 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;
+
+               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;
+               }
+
+               parse_params.start = bss_ies->data;
+               parse_params.len = bss_ies->len;
+               bss_elems = ieee802_11_parse_elems_full(&parse_params);
+               if (!bss_elems) {
+                       ret = false;
+                       goto out;
+               }
+
+               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");
+               }
+
+               /*
+                * 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");
+               }
+
+               kfree(bss_elems);
+       }
+
+       /*
+        * 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 (!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;
+       }
+
+       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;
+       }
+
+       sband = ieee80211_get_link_sband(link);
+       if (!sband) {
+               ret = false;
+               goto out;
+       }
+
+       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;
+       }
+
+       /* 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 (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 (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);
+
+               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;
+
+               *changed |= ieee80211_recalc_twt_req(link, link_sta, elems);
+
+               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);
+
+                       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;
+       }
+
+       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);
+
+               bss_conf->uora_exists = !!elems->uora_element;
+               if (elems->uora_element)
+                       bss_conf->uora_ocw_range = elems->uora_element[0];
+
+               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 (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;
+       }
+
+       /*
+        * 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 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)) {
+               u8 nss;
+
+               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;
+       }
+
+       /*
+        * 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 (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;
+       }
+
+       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;
+       }
+
+       /* set assoc capability (AID was already set earlier),
+        * ieee80211_set_associated() will tell the driver */
+       bss_conf->assoc_capability = capab_info;
+
+       ret = true;
+out:
+       kfree(elems);
+       kfree(bss_ies);
+       return ret;
+}
+
 static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link,
                                        struct sta_info *sta,
                                        struct ieee80211_link_sta *link_sta,
@@ -4047,388 +4367,102 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
                        continue;
 
                have_80mhz = true;
-               break;
-       }
-
-       if (!have_80mhz) {
-               sdata_info(sdata, "80 MHz not supported, disabling VHT\n");
-               *conn_flags |= IEEE80211_CONN_DISABLE_VHT;
-       }
-
-       if (sband->band == NL80211_BAND_S1GHZ) {
-               s1g_oper = elems->s1g_oper;
-               if (!s1g_oper)
-                       sdata_info(sdata,
-                                  "AP missing S1G operation element?\n");
-       }
-
-       *conn_flags |=
-               ieee80211_determine_chantype(sdata, link, *conn_flags,
-                                            sband,
-                                            cbss->channel,
-                                            bss->vht_cap_info,
-                                            ht_oper, vht_oper,
-                                            he_oper, eht_oper,
-                                            s1g_oper,
-                                            &chandef, false);
-
-       if (link)
-               link->needed_rx_chains =
-                       min(ieee80211_max_rx_chains(link, cbss),
-                           local->rx_chains);
-
-       rcu_read_unlock();
-       /* the element data was RCU protected so no longer valid anyway */
-       kfree(elems);
-       elems = NULL;
-
-       if (*conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) {
-               sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection");
-               return -EINVAL;
-       }
-
-       if (!link)
-               return 0;
-
-       /* will change later if needed */
-       link->smps_mode = IEEE80211_SMPS_OFF;
-
-       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);
-
-       /* 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;
-
-       while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) {
-               *conn_flags |=
-                       ieee80211_chandef_downgrade(&chandef);
-               ret = ieee80211_link_use_channel(link, &chandef,
-                                                IEEE80211_CHANCTX_SHARED);
-       }
- out:
-       mutex_unlock(&local->mtx);
-       return ret;
-}
-
-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_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;
-
-       capab_info = le16_to_cpu(mgmt->u.assoc_resp.capab_info);
-
-       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);
-
-       /*
-        * The 5 MSB of the AID field are reserved
-        * (802.11-2016 9.4.1.8 AID field)
-        */
-       aid &= 0x7ff;
-
-       ifmgd->broken_ap = false;
-
-       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;
-       }
-
-       if (!is_s1g && !elems->supp_rates) {
-               sdata_info(sdata, "no SuppRates element in AssocResp\n");
-               ret = false;
-               goto out;
-       }
-
-       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);
-
-       /*
-        * 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;
-
-               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;
-               }
-
-               bss_elems = ieee802_11_parse_elems(bss_ies->data, bss_ies->len,
-                                                  false, assoc_data->bss);
-               if (!bss_elems) {
-                       ret = false;
-                       goto out;
-               }
-
-               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");
-               }
-
-               /*
-                * 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");
-               }
-
-               kfree(bss_elems);
-       }
-
-       /*
-        * 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 (!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;
-       }
-
-       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;
-       }
-
-       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;
-       }
-
-       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;
+               break;
        }
 
-       sband = ieee80211_get_link_sband(link);
-       if (!sband) {
-               mutex_unlock(&sdata->local->sta_mtx);
-               ret = false;
-               goto out;
+       if (!have_80mhz) {
+               sdata_info(sdata, "80 MHz not supported, disabling VHT\n");
+               *conn_flags |= IEEE80211_CONN_DISABLE_VHT;
        }
 
-       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 (sband->band == NL80211_BAND_S1GHZ) {
+               s1g_oper = elems->s1g_oper;
+               if (!s1g_oper)
+                       sdata_info(sdata,
+                                  "AP missing S1G operation element?\n");
        }
 
-       /* 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 (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 (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);
-
-               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;
+       *conn_flags |=
+               ieee80211_determine_chantype(sdata, link, *conn_flags,
+                                            sband,
+                                            cbss->channel,
+                                            bss->vht_cap_info,
+                                            ht_oper, vht_oper,
+                                            he_oper, eht_oper,
+                                            s1g_oper,
+                                            &chandef, false);
 
-               changed |= ieee80211_recalc_twt_req(link, link_sta, elems);
+       if (link)
+               link->needed_rx_chains =
+                       min(ieee80211_max_rx_chains(link, cbss),
+                           local->rx_chains);
 
-               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);
+       rcu_read_unlock();
+       /* the element data was RCU protected so no longer valid anyway */
+       kfree(elems);
+       elems = NULL;
 
-                       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 (*conn_flags & IEEE80211_CONN_DISABLE_HE && is_6ghz) {
+               sdata_info(sdata, "Rejecting non-HE 6/7 GHz connection");
+               return -EINVAL;
        }
 
-       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;
+       if (!link)
+               return 0;
 
-               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);
+       /* will change later if needed */
+       link->smps_mode = IEEE80211_SMPS_OFF;
 
-               bss_conf->uora_exists = !!elems->uora_element;
-               if (elems->uora_element)
-                       bss_conf->uora_ocw_range = elems->uora_element[0];
+       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);
 
-               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? */
-       }
+       /* 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;
 
-       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;
+       while (ret && chandef.width != NL80211_CHAN_WIDTH_20_NOHT) {
+               *conn_flags |=
+                       ieee80211_chandef_downgrade(&chandef);
+               ret = ieee80211_link_use_channel(link, &chandef,
+                                                IEEE80211_CHANCTX_SHARED);
        }
+ out:
+       mutex_unlock(&local->mtx);
+       return ret;
+}
 
-       /*
-        * 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.
-        */
+static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
+                                   struct cfg80211_bss *cbss,
+                                   struct ieee80211_mgmt *mgmt,
+                                   struct ieee802_11_elems *elems,
+                                   const u8 *elem_start, unsigned int elem_len)
+{
+       struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+       struct ieee80211_local *local = sdata->local;
+       struct sta_info *sta;
+       u64 changed = 0;
+       int err;
 
+       mutex_lock(&sdata->local->sta_mtx);
        /*
-        * 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.
+        * station info was already allocated and inserted before
+        * the association and should be available to us
         */
-       if (elems->opmode_notif &&
-           !(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
-               u8 nss;
+       sta = sta_info_get(sdata, cbss->bssid);
+       if (WARN_ON(!sta))
+               goto out_err;
 
-               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;
-       }
+       if (!ieee80211_assoc_config_link(&sdata->deflink, &sta->deflink,
+                                        cbss, mgmt, elem_start, elem_len,
+                                        &changed))
+               goto out_err;
 
        rate_control_rate_init(sta);
 
@@ -4450,9 +4484,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *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;
+               goto out_err;
        }
 
        if (sdata->wdev.use_4addr)
@@ -4460,48 +4492,6 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
 
        mutex_unlock(&sdata->local->sta_mtx);
 
-       /*
-        * 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 (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 (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;
-       }
-
-       /* 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);
 
        /*
@@ -4518,10 +4508,10 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
        ieee80211_sta_reset_beacon_monitor(sdata);
        ieee80211_sta_reset_conn_monitor(sdata);
 
-       ret = true;
- out:
-       kfree(bss_ies);
-       return ret;
+       return true;
+out_err:
+       mutex_unlock(&sdata->local->sta_mtx);
+       return false;
 }
 
 static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
@@ -4533,7 +4523,8 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
        u16 capab_info, status_code, aid;
        struct ieee802_11_elems *elems;
        int ac;
-       u8 *pos;
+       const u8 *elem_start;
+       unsigned int elem_len;
        bool reassoc;
        struct cfg80211_bss *cbss;
        struct ieee80211_event event = {
@@ -4566,12 +4557,10 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
        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 */
-       }
+       if (cbss->channel->band == NL80211_BAND_S1GHZ)
+               elem_start = mgmt->u.s1g_assoc_resp.variable;
+       else
+               elem_start = mgmt->u.assoc_resp.variable;
 
        /*
         * Note: this may not be perfect, AP might misbehave - if
@@ -4582,20 +4571,35 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
        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 (assoc_data->fils_kek_len &&
            fils_decrypt_assoc_resp(sdata, (u8 *)mgmt, &len, assoc_data) < 0)
                return;
 
-       elems = ieee802_11_parse_elems(pos, len - (pos - (u8 *)mgmt), false,
-                                      assoc_data->bss);
+       elem_len = len - (elem_start - (u8 *)mgmt);
+       elems = ieee802_11_parse_elems(elem_start, elem_len, false, NULL);
        if (!elems)
                goto notify_driver;
 
+       if (elems->aid_resp)
+               aid = le16_to_cpu(elems->aid_resp->aid);
+       else if (cbss->channel->band == NL80211_BAND_S1GHZ)
+               aid = 0; /* TODO */
+       else
+               aid = le16_to_cpu(mgmt->u.assoc_resp.aid);
+
+       /*
+        * The 5 MSB of the AID field are reserved
+        * (802.11-2016 9.4.1.8 AID field)
+        */
+       aid &= 0x7ff;
+
+       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))));
+
+       ifmgd->broken_ap = false;
+
        if (status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY &&
            elems->timeout_int &&
            elems->timeout_int->type == WLAN_TIMEOUT_ASSOC_COMEBACK) {
@@ -4624,7 +4628,18 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
                event.u.mlme.reason = status_code;
                drv_event_callback(sdata->local, sdata, &event);
        } else {
-               if (!ieee80211_assoc_success(sdata, cbss, mgmt, len, elems)) {
+               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;
+               }
+
+               sdata->vif.cfg.aid = aid;
+
+               if (!ieee80211_assoc_success(sdata, cbss, mgmt, elems,
+                                            elem_start, elem_len)) {
                        /* oops -- internal error -- send timeout for now */
                        ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT);
                        goto notify_driver;