[PATCH 09/20] wifi: mac80211: clean up STA NSS handling

Johannes Berg
2026-04-15T12:45:25Z

Move ieee80211_sta_init_nss() from VHT code to station code,
and disentangle it from rate control. This way, it becomes
clearer when 'rx_nss' is set up.

While doing this, fix the client side code to set up
link_sta->op_mode_nss instead of link_sta->pub->rx_nss for
the opmode element in association response, and remove the
(now wrong) comment about handling that in the function.

type=cleanup
ticket=none

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Reviewed-on: https://gerritwcs.ir.intel.com/c/iwlwifi-stack-dev/+/273695
Tested-by: iil_jenkins iil_jenkins <EC.GER.UNIX.IIL.JENKINS@INTEL.COM>
tested: iil_jenkins iil_jenkins <EC.GER.UNIX.IIL.JENKINS@INTEL.COM>
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
---
net/mac80211/ibss.c | 2 +
net/mac80211/ieee80211_i.h | 2 +-
net/mac80211/mesh_plink.c | 2 +
net/mac80211/mlme.c | 11 +++--
net/mac80211/ocb.c | 4 +-
net/mac80211/rate.c | 4 +-
net/mac80211/sta_info.c | 92 +++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.h | 1 +
net/mac80211/vht.c | 93 --------------------------------------
9 files changed, 108 insertions(+), 103 deletions(-)

diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 97292ff51475..96f040b4672e 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -553,6 +553,8 @@ static struct sta_info *ieee80211_ibss_finish_sta(struct sta_info *sta)

memcpy(addr, sta->sta.addr, ETH_ALEN);

+ ieee80211_sta_init_nss(&sta->deflink);
+
ibss_dbg(sdata, "Adding new IBSS station %pM\n", addr);

sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 3ab8368acaf9..76213b50fe57 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2315,7 +2315,7 @@ ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta)
{
return _ieee80211_sta_cur_vht_bw(link_sta, NULL);
}
-void ieee80211_sta_init_nss(struct link_sta_info *link_sta);
+
void ieee80211_process_mu_groups(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct ieee80211_mgmt *mgmt);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 7cbab90c8784..94201840677d 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -470,6 +470,8 @@ static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
elems->eht_cap, elems->eht_cap_len,
&sta->deflink);

+ ieee80211_sta_init_nss(&sta->deflink);
+
if (bw != sta->sta.deflink.bandwidth)
changed |= IEEE80211_RC_BW_CHANGED;

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 160ae65a5c64..438cc2d4731d 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5749,9 +5749,9 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
*/

/*
- * 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 an operating mode notification element is present, set the opmode
+ * NSS override to correct for the current number of spatial streams,
+ * overriding the capabilities. ieee80211_sta_init_nss() uses this.
*/
if (elems->opmode_notif &&
!(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
@@ -5760,9 +5760,11 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
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;
+ link_sta->op_mode_nss = nss;
}

+ ieee80211_sta_init_nss(link_sta);
+
/*
* Always handle WMM once after association regardless
* of the first value the AP uses. Setting -1 here has
@@ -10617,7 +10619,6 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
if (add_links_data->link[link_id].status != WLAN_STATUS_SUCCESS)
goto disconnect;

- ieee80211_sta_init_nss(link_sta);
if (ieee80211_sta_activate_link(sta, link_id))
goto disconnect;

diff --git a/net/mac80211/ocb.c b/net/mac80211/ocb.c
index ebb4f4d88c23..447c84235c1c 100644
--- a/net/mac80211/ocb.c
+++ b/net/mac80211/ocb.c
@@ -4,7 +4,7 @@
*
* Copyright: (c) 2014 Czech Technical University in Prague
* (c) 2014 Volkswagen Group Research
- * Copyright (C) 2022 - 2024 Intel Corporation
+ * Copyright (C) 2022 - 2024, 2026 Intel Corporation
* Author: Rostislav Lisovy <rostislav.lisovy@fel.cvut.cz>
* Funded by: Volkswagen Group Research
*/
@@ -92,6 +92,8 @@ static struct sta_info *ieee80211_ocb_finish_sta(struct sta_info *sta)

memcpy(addr, sta->sta.addr, ETH_ALEN);

+ ieee80211_sta_init_nss(&sta->deflink);
+
ocb_dbg(sdata, "Adding new IBSS station %pM (dev=%s)\n",
addr, sdata->name);

diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index 31af7dd6aedc..ba1a3aa3f5d4 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -4,7 +4,7 @@
* Copyright 2005-2006, Devicescape Software, Inc.
* Copyright (c) 2006 Jiri Benc <jbenc@suse.cz>
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2019, 2022-2025 Intel Corporation
+ * Copyright (C) 2019, 2022-2026 Intel Corporation
*/

#include <linux/kernel.h>
@@ -38,8 +38,6 @@ void rate_control_rate_init(struct link_sta_info *link_sta)
struct ieee80211_supported_band *sband;
struct ieee80211_chanctx_conf *chanctx_conf;

- ieee80211_sta_init_nss(link_sta);
-
if (!ref)
return;

diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index e08db1b0cb30..0f174a6a04a8 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3426,6 +3426,98 @@ void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
sta_remove_link(sta, link_id, true);
}

+void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
+{
+ u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
+ bool support_160;
+
+ if (link_sta->pub->eht_cap.has_eht) {
+ int i;
+ const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
+
+ /* get the max nss for EHT over all possible bandwidths and mcs */
+ for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
+ eht_rx_nss = max_t(u8, eht_rx_nss,
+ u8_get_bits(rx_nss_mcs[i],
+ IEEE80211_EHT_MCS_NSS_RX));
+ }
+
+ if (link_sta->pub->he_cap.has_he) {
+ int i;
+ u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
+ const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
+ u16 mcs_160_map =
+ le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
+ u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
+
+ for (i = 7; i >= 0; i--) {
+ u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
+
+ if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
+ rx_mcs_160 = i + 1;
+ break;
+ }
+ }
+ for (i = 7; i >= 0; i--) {
+ u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
+
+ if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
+ rx_mcs_80 = i + 1;
+ break;
+ }
+ }
+
+ support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+
+ if (support_160)
+ he_rx_nss = min(rx_mcs_80, rx_mcs_160);
+ else
+ he_rx_nss = rx_mcs_80;
+ }
+
+ if (link_sta->pub->ht_cap.ht_supported) {
+ if (link_sta->pub->ht_cap.mcs.rx_mask[0])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[1])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[2])
+ ht_rx_nss++;
+ if (link_sta->pub->ht_cap.mcs.rx_mask[3])
+ ht_rx_nss++;
+ /* FIXME: consider rx_highest? */
+ }
+
+ if (link_sta->pub->vht_cap.vht_supported) {
+ int i;
+ u16 rx_mcs_map;
+
+ rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
+
+ for (i = 7; i >= 0; i--) {
+ u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
+
+ if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
+ vht_rx_nss = i + 1;
+ break;
+ }
+ }
+ /* FIXME: consider rx_highest? */
+ }
+
+ rx_nss = max(vht_rx_nss, ht_rx_nss);
+ rx_nss = max(he_rx_nss, rx_nss);
+ rx_nss = max(eht_rx_nss, rx_nss);
+ rx_nss = max_t(u8, 1, rx_nss);
+ link_sta->capa_nss = rx_nss;
+
+ if (link_sta->op_mode_nss)
+ link_sta->pub->rx_nss =
+ min_t(u8, rx_nss, link_sta->op_mode_nss);
+ else
+ link_sta->pub->rx_nss = rx_nss;
+}
+
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len)
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 05a776dba3e9..d0987b546bb7 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -997,6 +997,7 @@ void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);

unsigned long ieee80211_sta_last_active(struct sta_info *sta, int link_id);

+void ieee80211_sta_init_nss(struct link_sta_info *link_sta);
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len);
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 17f3f281abe1..b93d6a3200c8 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -488,99 +488,6 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
return bw;
}

-void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
-{
- u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
- bool support_160;
-
- if (link_sta->pub->eht_cap.has_eht) {
- int i;
- const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
-
- /* get the max nss for EHT over all possible bandwidths and mcs */
- for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
- eht_rx_nss = max_t(u8, eht_rx_nss,
- u8_get_bits(rx_nss_mcs[i],
- IEEE80211_EHT_MCS_NSS_RX));
- }
-
- if (link_sta->pub->he_cap.has_he) {
- int i;
- u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
- const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
- u16 mcs_160_map =
- le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
- u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
-
- for (i = 7; i >= 0; i--) {
- u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
-
- if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
- rx_mcs_160 = i + 1;
- break;
- }
- }
- for (i = 7; i >= 0; i--) {
- u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
-
- if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
- rx_mcs_80 = i + 1;
- break;
- }
- }
-
- support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
- IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
-
- if (support_160)
- he_rx_nss = min(rx_mcs_80, rx_mcs_160);
- else
- he_rx_nss = rx_mcs_80;
- }
-
- if (link_sta->pub->ht_cap.ht_supported) {
- if (link_sta->pub->ht_cap.mcs.rx_mask[0])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[1])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[2])
- ht_rx_nss++;
- if (link_sta->pub->ht_cap.mcs.rx_mask[3])
- ht_rx_nss++;
- /* FIXME: consider rx_highest? */
- }
-
- if (link_sta->pub->vht_cap.vht_supported) {
- int i;
- u16 rx_mcs_map;
-
- rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
-
- for (i = 7; i >= 0; i--) {
- u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
-
- if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
- vht_rx_nss = i + 1;
- break;
- }
- }
- /* FIXME: consider rx_highest? */
- }
-
- rx_nss = max(vht_rx_nss, ht_rx_nss);
- rx_nss = max(he_rx_nss, rx_nss);
- rx_nss = max(eht_rx_nss, rx_nss);
- rx_nss = max_t(u8, 1, rx_nss);
- link_sta->capa_nss = rx_nss;
-
- /* that shouldn't be set yet, but we can handle it anyway */
- if (link_sta->op_mode_nss)
- link_sta->pub->rx_nss =
- min_t(u8, rx_nss, link_sta->op_mode_nss);
- else
- link_sta->pub->rx_nss = rx_nss;
-}
-
u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta,
u8 opmode, enum nl80211_band band)
--
2.53.0