* [PATCH 01/20] wifi: mac80211: remove NAN guards on ieee80211_sta_cur_vht_bw() calls
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
@ 2026-04-15 12:41 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 02/20] wifi: mac80211: set cur_max_bandwidth to maximum Johannes Berg
` (18 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:41 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The NAN guards here make little sense, just don't WARN inside
the function (and return maximum instead of minimum). Otherwise
we need to guard more calls, such as in EHT in the future.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/he.c | 5 ++---
net/mac80211/vht.c | 9 ++++-----
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index a3e16a5bec22..92d9c4f095b3 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -3,7 +3,7 @@
* HE handling
*
* Copyright(c) 2017 Intel Deutschland GmbH
- * Copyright(c) 2019-2025 Intel Corporation
+ * Copyright(c) 2019-2026 Intel Corporation
*/
#include "ieee80211_i.h"
@@ -160,8 +160,7 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
- if (sdata->vif.type != NL80211_IFTYPE_NAN)
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
if (he_6ghz_capa)
ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index f3bb5a561a38..edce0b16dc3d 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -324,8 +324,7 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
IEEE80211_STA_RX_BW_160;
}
- if (sdata->vif.type != NL80211_IFTYPE_NAN)
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
* Work around the Cisco 9115 FW 17.3 bug by taking the min of
@@ -528,9 +527,9 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
struct ieee80211_bss_conf *link_conf;
/* NAN operates on multiple channels so a chandef must be given */
- if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
- sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
- return IEEE80211_STA_RX_BW_20;
+ if (sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ return IEEE80211_STA_RX_BW_MAX;
rcu_read_lock();
link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 02/20] wifi: mac80211: set cur_max_bandwidth to maximum
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
2026-04-15 12:41 ` [PATCH 01/20] wifi: mac80211: remove NAN guards on ieee80211_sta_cur_vht_bw() calls Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 03/20] wifi: mac80211: use max BW for HT channel width update Johannes Berg
` (17 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
Instead of calculating the individual maximum for each
station from its capabilities, just unconditionally set
cur_max_bandwidth to IEEE80211_STA_RX_BW_MAX. This still
works because cur_max_bandwidth is only used together
with the capabilities of the station anyway, and then
adjusted by HT channel width notification or VHT opmode
notification action frames.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/eht.c | 3 +--
net/mac80211/he.c | 1 -
net/mac80211/ht.c | 4 ----
net/mac80211/sta_info.c | 2 ++
net/mac80211/sta_info.h | 2 +-
net/mac80211/vht.c | 23 -----------------------
6 files changed, 4 insertions(+), 31 deletions(-)
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index 768bfc4e737d..66dd104f8c73 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -2,7 +2,7 @@
/*
* EHT handling
*
- * Copyright(c) 2021-2025 Intel Corporation
+ * Copyright(c) 2021-2026 Intel Corporation
*/
#include "driver-ops.h"
@@ -74,7 +74,6 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
eht_cap->has_eht = true;
- link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 92d9c4f095b3..19e2f359b796 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -159,7 +159,6 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
- link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
if (he_6ghz_capa)
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 97719298e038..8f76440e9e48 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -291,10 +291,6 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
link_sta->pub->bandwidth = bw;
- link_sta->cur_max_bandwidth =
- ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
- IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
-
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 4c31ef8817ce..e08db1b0cb30 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -572,6 +572,8 @@ static int sta_info_alloc_link(struct ieee80211_local *local,
link_info->rx_omi_bw_tx = IEEE80211_STA_RX_BW_MAX;
link_info->rx_omi_bw_staging = IEEE80211_STA_RX_BW_MAX;
+ link_info->cur_max_bandwidth = IEEE80211_STA_RX_BW_MAX;
+
/*
* Cause (a) warning(s) if IEEE80211_STA_RX_BW_MAX != 320
* or if new values are added to the enum.
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 3e5d003bd31f..05a776dba3e9 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -504,7 +504,7 @@ struct ieee80211_fragment_cache {
* @status_stats.last_ack_signal: last ACK signal
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
- * @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
+ * @cur_max_bandwidth: dynamic bandwidth limit for the station,
* taken from HT/VHT capabilities or VHT operating mode notification.
* Invalid for NAN since that is operating on multiple bands.
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index edce0b16dc3d..6da8a5021136 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -301,29 +301,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
return;
}
- /* finally set up the bandwidth */
- switch (vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) {
- case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ:
- case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ:
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
- break;
- default:
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
-
- if (!(vht_cap->vht_mcs.tx_highest &
- cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE)))
- break;
-
- /*
- * If this is non-zero, then it does support 160 MHz after all,
- * in one form or the other. We don't distinguish here (or even
- * above) between 160 and 80+80 yet.
- */
- if (cap_info & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
- link_sta->cur_max_bandwidth =
- IEEE80211_STA_RX_BW_160;
- }
-
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 03/20] wifi: mac80211: use max BW for HT channel width update
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
2026-04-15 12:41 ` [PATCH 01/20] wifi: mac80211: remove NAN guards on ieee80211_sta_cur_vht_bw() calls Johannes Berg
2026-04-15 12:42 ` [PATCH 02/20] wifi: mac80211: set cur_max_bandwidth to maximum Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 04/20] wifi: mac80211: use chandef in ieee80211_get_sta_bw() Johannes Berg
` (16 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
When an HT channel width update comes in, don't use the
capability of the station, but rather set it to max as
the capability will be taken into account anyway when
using the value.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ht.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 8f76440e9e48..7d587f968a7f 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -631,7 +631,7 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
max_bw = IEEE80211_STA_RX_BW_20;
else
- max_bw = ieee80211_sta_cap_rx_bw(link_sta);
+ max_bw = IEEE80211_STA_RX_BW_MAX;
/* set cur_max_bandwidth and recalc sta bw */
link_sta->cur_max_bandwidth = max_bw;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 04/20] wifi: mac80211: use chandef in ieee80211_get_sta_bw()
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (2 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 03/20] wifi: mac80211: use max BW for HT channel width update Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 05/20] wifi: mac80211: use chandef in TDLS chanctx handling Johannes Berg
` (15 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
When getting the bandwidth the station uses in order to
calculate the channel context's min_def, pass the channel
for the link to _ieee80211_sta_cap_rx_bw() instead of using
ieee80211_sta_cap_rx_bw(), which looks it up.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index fda692316f08..5e93405d70a3 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -438,11 +438,12 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
return NULL;
}
-static enum nl80211_chan_width ieee80211_get_sta_bw(struct sta_info *sta,
- unsigned int link_id)
+static enum nl80211_chan_width
+ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
{
enum ieee80211_sta_rx_bandwidth width;
struct link_sta_info *link_sta;
+ int link_id = link->link_id;
link_sta = wiphy_dereference(sta->local->hw.wiphy, sta->link[link_id]);
@@ -457,7 +458,7 @@ static enum nl80211_chan_width ieee80211_get_sta_bw(struct sta_info *sta,
* capabilities here. Calling it RX bandwidth capability is a bit
* wrong though, since capabilities are in fact symmetric.
*/
- width = ieee80211_sta_cap_rx_bw(link_sta);
+ width = _ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
switch (width) {
case IEEE80211_STA_RX_BW_20:
@@ -492,7 +493,6 @@ static enum nl80211_chan_width
ieee80211_get_max_required_bw(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
- unsigned int link_id = link->link_id;
enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT;
struct sta_info *sta;
@@ -503,7 +503,7 @@ ieee80211_get_max_required_bw(struct ieee80211_link_data *link)
!(sta->sdata->bss && sta->sdata->bss == sdata->bss))
continue;
- max_bw = max(max_bw, ieee80211_get_sta_bw(sta, link_id));
+ max_bw = max(max_bw, ieee80211_get_sta_bw(sta, link));
}
return max_bw;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 05/20] wifi: mac80211: use chandef in TDLS chanctx handling
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (3 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 04/20] wifi: mac80211: use chandef in ieee80211_get_sta_bw() Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 06/20] wifi: mac80211: remove ieee80211_sta_cap_chan_bw() Johannes Berg
` (14 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
When getting the station's bandwidth for TDLS chanctx
updates, pass the chandef so that the band can be used
in _ieee80211_sta_cap_rx_bw(), instead of this using
ieee80211_sta_cap_rx_bw() which looks it up from the
link.
This removes the last user of ieee80211_sta_cap_rx_bw().
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/tdls.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index 44bba7c3e603..90a122dc274f 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -1334,7 +1334,8 @@ static void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_sta_rx_bandwidth bw;
bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
- bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink));
+ bw = min(bw, _ieee80211_sta_cap_rx_bw(&sta->deflink,
+ &conf->def));
if (bw != sta->sta.deflink.bandwidth) {
sta->sta.deflink.bandwidth = bw;
rate_control_rate_update(local, sband,
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 06/20] wifi: mac80211: remove ieee80211_sta_cap_chan_bw()
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (4 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 05/20] wifi: mac80211: use chandef in TDLS chanctx handling Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 07/20] wifi: nl80211: document channel opmode change channel width Johannes Berg
` (13 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
This function is only used by TDLS, but is more or less equivalent
to ieee80211_sta_cap_rx_bw() (which takes OMI into account, but that
won't be used in TDLS), except it tries to differentiate 80+80 and
160, but then caller doesn't care about that. Remove the function.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 2 --
net/mac80211/tdls.c | 19 +++++++++++--------
net/mac80211/vht.c | 24 ------------------------
3 files changed, 11 insertions(+), 34 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 2a693406294b..029600b3b7c0 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2316,8 +2316,6 @@ 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);
-enum nl80211_chan_width
-ieee80211_sta_cap_chan_bw(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/tdls.c b/net/mac80211/tdls.c
index 90a122dc274f..874752738c68 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -311,17 +311,20 @@ ieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
/* IEEE802.11ac-2013 Table E-4 */
static const u16 centers_80mhz[] = { 5210, 5290, 5530, 5610, 5690, 5775 };
struct cfg80211_chan_def uc = sta->tdls_chandef;
- enum nl80211_chan_width max_width =
- ieee80211_sta_cap_chan_bw(&sta->deflink);
+ enum nl80211_chan_width max_width;
int i;
- /* only support upgrading non-narrow channels up to 80Mhz */
- if (max_width == NL80211_CHAN_WIDTH_5 ||
- max_width == NL80211_CHAN_WIDTH_10)
- return;
-
- if (max_width > NL80211_CHAN_WIDTH_80)
+ switch (ieee80211_sta_cap_rx_bw(&sta->deflink)) {
+ case IEEE80211_STA_RX_BW_20:
+ max_width = NL80211_CHAN_WIDTH_20;
+ break;
+ case IEEE80211_STA_RX_BW_40:
+ max_width = NL80211_CHAN_WIDTH_40;
+ break;
+ default: /* 80 or higher, only support upgrade to 80 */
max_width = NL80211_CHAN_WIDTH_80;
+ break;
+ }
if (uc.width >= max_width)
return;
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 6da8a5021136..c25190bc7c5c 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -433,30 +433,6 @@ _ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
link_sta->rx_omi_bw_rx);
}
-enum nl80211_chan_width
-ieee80211_sta_cap_chan_bw(struct link_sta_info *link_sta)
-{
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
- u32 cap_width;
-
- if (!vht_cap->vht_supported) {
- if (!link_sta->pub->ht_cap.ht_supported)
- return NL80211_CHAN_WIDTH_20_NOHT;
-
- return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
- NL80211_CHAN_WIDTH_40 : NL80211_CHAN_WIDTH_20;
- }
-
- cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
-
- if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
- return NL80211_CHAN_WIDTH_160;
- else if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
- return NL80211_CHAN_WIDTH_80P80;
-
- return NL80211_CHAN_WIDTH_80;
-}
-
enum nl80211_chan_width
ieee80211_sta_rx_bw_to_chan_width(struct link_sta_info *link_sta)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 07/20] wifi: nl80211: document channel opmode change channel width
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (5 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 06/20] wifi: mac80211: remove ieee80211_sta_cap_chan_bw() Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 08/20] wifi: mac80211: simplify ieee80211_sta_rx_bw_to_chan_width() Johannes Berg
` (12 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The opmode change notification is entirely unused by existing
userspace except for printing out the values. As such, there's
no need to keep it perfectly accurate, and the implementation
in mac80211 doesn't report it correctly today. Add a note in
the documentation that it may not differentiate 80+80 and 160.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/uapi/linux/nl80211.h | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 3d55bf4be36f..072b383d7d3c 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1204,10 +1204,12 @@
* user space through the connect result as the user space would have
* initiated the connection through the connect request.
*
- * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notify station's
- * ht opmode or vht opmode changes using any of %NL80211_ATTR_SMPS_MODE,
- * %NL80211_ATTR_CHANNEL_WIDTH,%NL80211_ATTR_NSS attributes with its
- * address(specified in %NL80211_ATTR_MAC).
+ * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notifies that a station's
+ * HT opmode or VHT opmode changed using any of %NL80211_ATTR_SMPS_MODE,
+ * %NL80211_ATTR_CHANNEL_WIDTH, %NL80211_ATTR_NSS attributes with its
+ * address (specified in %NL80211_ATTR_MAC).
+ * Note that 80+80 and 160 MHz might not be differentiated, i.e. may
+ * report %NL80211_CHAN_WIDTH_160 instead of %NL80211_CHAN_WIDTH_80P80.
*
* @NL80211_CMD_GET_FTM_RESPONDER_STATS: Retrieve FTM responder statistics, in
* the %NL80211_ATTR_FTM_RESPONDER_STATS attribute.
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 08/20] wifi: mac80211: simplify ieee80211_sta_rx_bw_to_chan_width()
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (6 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 07/20] wifi: nl80211: document channel opmode change channel width Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 09/20] wifi: mac80211: clean up STA NSS handling Johannes Berg
` (11 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
This function is only called for at least HT capable stations,
so doesn't need to differentiate 20/20_NOHT. Also, the check
for VHT 160 MHz support is wrong, since a station could have
support for both and the AP is using 80+80, but nothing cares
anyway, so we don't need that.
Simplify the function and move it to util.c since it now no
longer is related to VHT, and also doesn't need a station.
Also use the new function in ieee80211_get_sta_bw() for the
chandef calculations, it just needs to handle the 20/20-noht
separately; while at it fix that to handle HE stations.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 38 +++++++++++---------------------------
net/mac80211/ht.c | 3 +--
net/mac80211/ieee80211_i.h | 5 +++--
net/mac80211/util.c | 19 +++++++++++++++++++
net/mac80211/vht.c | 34 +---------------------------------
5 files changed, 35 insertions(+), 64 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 5e93405d70a3..b3d810e3691f 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -460,33 +460,17 @@ ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
*/
width = _ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
- switch (width) {
- case IEEE80211_STA_RX_BW_20:
- if (link_sta->pub->ht_cap.ht_supported)
- return NL80211_CHAN_WIDTH_20;
- else
- return NL80211_CHAN_WIDTH_20_NOHT;
- case IEEE80211_STA_RX_BW_40:
- return NL80211_CHAN_WIDTH_40;
- case IEEE80211_STA_RX_BW_80:
- return NL80211_CHAN_WIDTH_80;
- case IEEE80211_STA_RX_BW_160:
- /*
- * This applied for both 160 and 80+80. since we use
- * the returned value to consider degradation of
- * ctx->conf.min_def, we have to make sure to take
- * the bigger one (NL80211_CHAN_WIDTH_160).
- * Otherwise we might try degrading even when not
- * needed, as the max required sta_bw returned (80+80)
- * might be smaller than the configured bw (160).
- */
- return NL80211_CHAN_WIDTH_160;
- case IEEE80211_STA_RX_BW_320:
- return NL80211_CHAN_WIDTH_320;
- default:
- WARN_ON(1);
- return NL80211_CHAN_WIDTH_20;
- }
+ if (width == IEEE80211_STA_RX_BW_20 &&
+ !link_sta->pub->ht_cap.ht_supported &&
+ !link_sta->pub->he_cap.has_he)
+ return NL80211_CHAN_WIDTH_20_NOHT;
+
+ /*
+ * This returns 160 for both 160 and 80+80. Since we use
+ * the returned value to consider narrowing for
+ * ctx->conf.min_def, that's correct and necessary.
+ */
+ return ieee80211_sta_rx_bw_to_chan_width(width);
}
static enum nl80211_chan_width
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 7d587f968a7f..3775d57d6c22 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -642,8 +642,7 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
link_sta->pub->bandwidth = new_bw;
sband = local->hw.wiphy->bands[band];
- sta_opmode.bw =
- ieee80211_sta_rx_bw_to_chan_width(link_sta);
+ sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED;
rate_control_rate_update(local, sband, link_sta,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 029600b3b7c0..3ab8368acaf9 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2329,8 +2329,6 @@ void ieee80211_apply_vhtcap_overrides(struct ieee80211_sub_if_data *sdata,
struct ieee80211_sta_vht_cap *vht_cap);
void ieee80211_get_vht_mask_from_cap(__le16 vht_cap,
u16 vht_mask[NL80211_VHT_NSS_MAX]);
-enum nl80211_chan_width
-ieee80211_sta_rx_bw_to_chan_width(struct link_sta_info *sta);
/* HE */
void
@@ -2716,6 +2714,9 @@ void ieee80211_add_s1g_capab_ie(struct ieee80211_sub_if_data *sdata,
void ieee80211_add_aid_request_ie(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb);
+enum nl80211_chan_width
+ieee80211_sta_rx_bw_to_chan_width(enum ieee80211_sta_rx_bandwidth bw);
+
/* element building in SKBs */
int ieee80211_put_srates_elem(struct sk_buff *skb,
const struct ieee80211_supported_band *sband,
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index b093bc203c81..a2ca8fd0c78a 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3840,6 +3840,25 @@ void ieee80211_chandef_downgrade(struct cfg80211_chan_def *c,
WARN_ON_ONCE(!cfg80211_chandef_valid(c));
}
+enum nl80211_chan_width
+ieee80211_sta_rx_bw_to_chan_width(enum ieee80211_sta_rx_bandwidth bw)
+{
+ switch (bw) {
+ case IEEE80211_STA_RX_BW_20:
+ return NL80211_CHAN_WIDTH_20;
+ case IEEE80211_STA_RX_BW_40:
+ return NL80211_CHAN_WIDTH_40;
+ case IEEE80211_STA_RX_BW_80:
+ return NL80211_CHAN_WIDTH_80;
+ case IEEE80211_STA_RX_BW_160:
+ return NL80211_CHAN_WIDTH_160;
+ case IEEE80211_STA_RX_BW_320:
+ return NL80211_CHAN_WIDTH_320;
+ default:
+ return NL80211_CHAN_WIDTH_20;
+ }
+}
+
int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata,
struct cfg80211_csa_settings *csa_settings)
{
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index c25190bc7c5c..17f3f281abe1 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -433,38 +433,6 @@ _ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
link_sta->rx_omi_bw_rx);
}
-enum nl80211_chan_width
-ieee80211_sta_rx_bw_to_chan_width(struct link_sta_info *link_sta)
-{
- enum ieee80211_sta_rx_bandwidth cur_bw =
- link_sta->pub->bandwidth;
- struct ieee80211_sta_vht_cap *vht_cap =
- &link_sta->pub->vht_cap;
- u32 cap_width;
-
- switch (cur_bw) {
- case IEEE80211_STA_RX_BW_20:
- if (!link_sta->pub->ht_cap.ht_supported)
- return NL80211_CHAN_WIDTH_20_NOHT;
- else
- return NL80211_CHAN_WIDTH_20;
- case IEEE80211_STA_RX_BW_40:
- return NL80211_CHAN_WIDTH_40;
- case IEEE80211_STA_RX_BW_80:
- return NL80211_CHAN_WIDTH_80;
- case IEEE80211_STA_RX_BW_160:
- cap_width =
- vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
-
- if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
- return NL80211_CHAN_WIDTH_160;
-
- return NL80211_CHAN_WIDTH_80P80;
- default:
- return NL80211_CHAN_WIDTH_20;
- }
-}
-
/* FIXME: rename/move - this deals with everything not just VHT */
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
@@ -671,7 +639,7 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
new_bw = ieee80211_sta_cur_vht_bw(link_sta);
if (new_bw != link_sta->pub->bandwidth) {
link_sta->pub->bandwidth = new_bw;
- sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(link_sta);
+ sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
changed |= IEEE80211_RC_BW_CHANGED;
sta_opmode.changed |= STA_OPMODE_MAX_BW_CHANGED;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 09/20] wifi: mac80211: clean up STA NSS handling
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (7 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 08/20] wifi: mac80211: simplify ieee80211_sta_rx_bw_to_chan_width() Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 13:02 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 10/20] wifi: mac80211: clean up initial STA NSS/bandwidth handling Johannes Berg
` (10 subsequent siblings)
19 siblings, 1 reply; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless
Cc: Johannes Berg, iil_jenkins iil_jenkins, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
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
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 10/20] wifi: mac80211: clean up initial STA NSS/bandwidth handling
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (8 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 09/20] wifi: mac80211: clean up STA NSS handling Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 11/20] wifi: mac80211: clean up ieee80211_sta_cap_rx_bw() Johannes Berg
` (9 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
Currently, the initial STA bandwidth is set during each
parsing of HT/VHT/... elements to the station capabilities,
multiple times, in a confusing way that's not very good in
the case of NAN stations either.
For now, keep the NULL chandef pointer and all that, but
clean up the initial handling of NSS/BW capabilities and
then apply the VHT operation mode on top of that. This
clarifies the code and the client code now also handles
the bandwidth change from Operating Mode Notification in
association response.
The HT code is completely unnecessary now, since the VHT
(soon to be renamed) function will be called and handles
HT as well.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 10 +++++++++-
net/mac80211/eht.c | 2 --
net/mac80211/he.c | 2 --
net/mac80211/ht.c | 38 --------------------------------------
net/mac80211/ibss.c | 3 ++-
net/mac80211/mesh_plink.c | 3 ++-
net/mac80211/mlme.c | 21 ++++++---------------
net/mac80211/ocb.c | 3 ++-
net/mac80211/sta_info.c | 18 +++++++++++-------
net/mac80211/sta_info.h | 3 ++-
net/mac80211/vht.c | 2 --
11 files changed, 34 insertions(+), 71 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 7b77d57c9f96..15a68d3f8e73 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2221,7 +2221,15 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
link_sta);
- ieee80211_sta_init_nss(link_sta);
+ switch (sdata->vif.type) {
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ /* not applicable - they don't use NSS/BW as capability */
+ break;
+ default:
+ ieee80211_sta_init_nss_bw_capa(link_sta, &link->conf->chanreq.oper);
+ break;
+ }
if (params->opmode_notif_used) {
enum nl80211_chan_width width = link->conf->chanreq.oper.width;
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index 66dd104f8c73..9edfe3380e17 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -74,8 +74,6 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
eht_cap->has_eht = true;
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
-
/*
* The MPDU length bits are reserved on all but 2.4 GHz and get set via
* VHT (5 GHz) or HE (6 GHz) capabilities.
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 19e2f359b796..e6e9c378ed3a 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -159,8 +159,6 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
-
if (he_6ghz_capa)
ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 3775d57d6c22..e32e40db081b 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -140,14 +140,11 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_ht_cap *ht_cap_ie,
struct link_sta_info *link_sta)
{
- struct ieee80211_bss_conf *link_conf;
struct sta_info *sta = link_sta->sta;
struct ieee80211_sta_ht_cap ht_cap, own_cap;
u8 ampdu_info, tx_mcs_set_cap;
int i, max_tx_streams;
bool changed;
- enum ieee80211_sta_rx_bandwidth bw;
- enum nl80211_chan_width width;
memset(&ht_cap, 0, sizeof(ht_cap));
@@ -256,41 +253,6 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
memcpy(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
- rcu_read_lock();
- link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
- if (WARN_ON(!link_conf)) {
- width = NL80211_CHAN_WIDTH_20_NOHT;
- } else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
- sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
- /* In NAN, link_sta->bandwidth is invalid since NAN operates on
- * multiple channels. Just take the maximum.
- */
- width = NL80211_CHAN_WIDTH_320;
- } else {
- width = link_conf->chanreq.oper.width;
- }
-
- switch (width) {
- default:
- WARN_ON_ONCE(1);
- fallthrough;
- case NL80211_CHAN_WIDTH_20_NOHT:
- case NL80211_CHAN_WIDTH_20:
- bw = IEEE80211_STA_RX_BW_20;
- break;
- case NL80211_CHAN_WIDTH_40:
- case NL80211_CHAN_WIDTH_80:
- case NL80211_CHAN_WIDTH_80P80:
- case NL80211_CHAN_WIDTH_160:
- case NL80211_CHAN_WIDTH_320:
- bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
- IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
- break;
- }
- rcu_read_unlock();
-
- link_sta->pub->bandwidth = bw;
-
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 96f040b4672e..08690342cfaa 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -553,7 +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);
+ ieee80211_sta_init_nss_bw_capa(&sta->deflink,
+ &sdata->deflink.conf->chanreq.oper);
ibss_dbg(sdata, "Adding new IBSS station %pM\n", addr);
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 94201840677d..37adb053213e 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -470,7 +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);
+ ieee80211_sta_init_nss_bw_capa(&sta->deflink,
+ &sdata->deflink.conf->chanreq.oper);
if (bw != sta->sta.deflink.bandwidth)
changed |= IEEE80211_RC_BW_CHANGED;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 438cc2d4731d..ea190d54d195 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -5748,22 +5748,13 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
* next beacon and update then.
*/
- /*
- * 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)) {
- u8 nss;
+ ieee80211_sta_init_nss_bw_capa(link_sta, &bss_conf->chanreq.oper);
- nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
- nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
- nss += 1;
- link_sta->op_mode_nss = nss;
- }
-
- ieee80211_sta_init_nss(link_sta);
+ /* If an operating mode notification element is present, use it. */
+ if (elems->opmode_notif)
+ __ieee80211_vht_handle_opmode(sdata, link_sta,
+ *elems->opmode_notif,
+ sband->band);
/*
* Always handle WMM once after association regardless
diff --git a/net/mac80211/ocb.c b/net/mac80211/ocb.c
index 447c84235c1c..e9bf4ba3e60b 100644
--- a/net/mac80211/ocb.c
+++ b/net/mac80211/ocb.c
@@ -92,7 +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);
+ ieee80211_sta_init_nss_bw_capa(&sta->deflink,
+ &sdata->deflink.conf->chanreq.oper);
ocb_dbg(sdata, "Adding new IBSS station %pM (dev=%s)\n",
addr, sdata->name);
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 0f174a6a04a8..d53cd8c5df4c 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3426,7 +3426,7 @@ 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)
+static u8 ieee80211_sta_nss_capability(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;
@@ -3509,13 +3509,17 @@ void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
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;
+ return rx_nss;
+}
+
+void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
+{
+ link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
+ link_sta->pub->rx_nss = link_sta->capa_nss;
+
+ link_sta->pub->bandwidth = _ieee80211_sta_cur_vht_bw(link_sta, chandef);
}
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index d0987b546bb7..f0497d7d488a 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -997,7 +997,8 @@ 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_init_nss_bw_capa(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef);
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 b93d6a3200c8..2a85c578e252 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -301,8 +301,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
return;
}
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
-
/*
* Work around the Cisco 9115 FW 17.3 bug by taking the min of
* both reported MPDU lengths.
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 11/20] wifi: mac80211: clean up ieee80211_sta_cap_rx_bw()
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (9 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 10/20] wifi: mac80211: clean up initial STA NSS/bandwidth handling Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 12/20] wifi: mac80211: remove ieee80211_sta_cur_vht_bw() Johannes Berg
` (8 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
There are three versions of this function, but now there's
no caller to ieee80211_sta_cap_rx_bw() and the chandef is
always given. Rename the functions to have one underscore
less and rely on the chandef being passed.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 2 +-
net/mac80211/ieee80211_i.h | 9 ++-------
net/mac80211/tdls.c | 6 +++---
net/mac80211/vht.c | 32 ++++++++------------------------
4 files changed, 14 insertions(+), 35 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index b3d810e3691f..7cbe2ce97d3b 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -458,7 +458,7 @@ ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
* capabilities here. Calling it RX bandwidth capability is a bit
* wrong though, since capabilities are in fact symmetric.
*/
- width = _ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
+ width = ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
if (width == IEEE80211_STA_RX_BW_20 &&
!link_sta->pub->ht_cap.ht_supported &&
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 76213b50fe57..0c4796d96e65 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2300,13 +2300,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_vht_cap *vht_cap_ie2,
struct link_sta_info *link_sta);
enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-static inline enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta)
-{
- return _ieee80211_sta_cap_rx_bw(link_sta, NULL);
-}
+ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef);
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef);
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index 874752738c68..dcb5fe98ec55 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -314,7 +314,7 @@ ieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
enum nl80211_chan_width max_width;
int i;
- switch (ieee80211_sta_cap_rx_bw(&sta->deflink)) {
+ switch (ieee80211_sta_cap_rx_bw(&sta->deflink, &uc)) {
case IEEE80211_STA_RX_BW_20:
max_width = NL80211_CHAN_WIDTH_20;
break;
@@ -1337,8 +1337,8 @@ static void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_sta_rx_bandwidth bw;
bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
- bw = min(bw, _ieee80211_sta_cap_rx_bw(&sta->deflink,
- &conf->def));
+ bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink,
+ &conf->def));
if (bw != sta->sta.deflink.bandwidth) {
sta->sta.deflink.bandwidth = bw;
rate_control_rate_update(local, sband,
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 2a85c578e252..cf7e5b8d373a 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -333,35 +333,16 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
/* FIXME: move this to some better location - parses HE/EHT now */
static enum ieee80211_sta_rx_bandwidth
-__ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
+_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta, enum nl80211_band band)
{
- unsigned int link_id = link_sta->link_id;
- struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
u32 cap_width;
if (he_cap->has_he) {
- enum nl80211_band band;
u8 info;
- if (chandef) {
- band = chandef->chan->band;
- } else {
- struct ieee80211_bss_conf *link_conf;
-
- if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
- sdata->vif.type == NL80211_IFTYPE_NAN))
- return IEEE80211_STA_RX_BW_20;
-
- rcu_read_lock();
- link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
- band = link_conf->chanreq.oper.chan->band;
- rcu_read_unlock();
- }
-
if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
info = eht_cap->eht_cap_elem.phy_cap_info[0];
@@ -410,8 +391,8 @@ __ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
}
enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
+ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
{
/*
* With RX OMI, also pretend that the STA's capability changed.
@@ -427,7 +408,7 @@ _ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
* the transition we already need to change TX/RX separately,
* so _ieee80211_sta_cur_vht_bw() below applies the _tx one.
*/
- return min(__ieee80211_sta_cap_rx_bw(link_sta, chandef),
+ return min(_ieee80211_sta_cap_rx_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
}
@@ -439,9 +420,11 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
struct sta_info *sta = link_sta->sta;
enum nl80211_chan_width bss_width;
enum ieee80211_sta_rx_bandwidth bw;
+ enum nl80211_band band;
if (chandef) {
bss_width = chandef->width;
+ band = chandef->chan->band;
} else {
struct ieee80211_bss_conf *link_conf;
@@ -457,11 +440,12 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
return IEEE80211_STA_RX_BW_20;
}
bss_width = link_conf->chanreq.oper.width;
+ band = link_conf->chanreq.oper.chan->band;
rcu_read_unlock();
}
/* intentionally do not take rx_bw_omi_rx into account */
- bw = __ieee80211_sta_cap_rx_bw(link_sta, chandef);
+ bw = _ieee80211_sta_cap_rx_bw(link_sta, band);
bw = min(bw, link_sta->cur_max_bandwidth);
/* but do apply rx_omi_bw_tx */
bw = min(bw, link_sta->rx_omi_bw_tx);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 12/20] wifi: mac80211: remove ieee80211_sta_cur_vht_bw()
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (10 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 11/20] wifi: mac80211: clean up ieee80211_sta_cap_rx_bw() Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 13/20] wifi: cfg80211: remove HE/SAE H2E required fields Johannes Berg
` (7 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
We can now easily always call _ieee80211_sta_cur_vht_bw() with
a valid chandef, so do that, remove ieee80211_sta_cur_vht_bw()
and drop the underscore prefix.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 4 ++--
net/mac80211/he.c | 2 +-
net/mac80211/ht.c | 7 ++++++-
net/mac80211/ieee80211_i.h | 9 ++-------
net/mac80211/mlme.c | 4 ++--
net/mac80211/sta_info.c | 2 +-
net/mac80211/vht.c | 41 ++++++++++++++------------------------
7 files changed, 29 insertions(+), 40 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 7cbe2ce97d3b..13c0d080ef2a 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -693,8 +693,8 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
else
new_chandef = &link_conf->chanreq.oper;
- new_sta_bw = _ieee80211_sta_cur_vht_bw(link_sta,
- new_chandef);
+ new_sta_bw = ieee80211_sta_cur_vht_bw(link_sta,
+ new_chandef);
/* nothing change */
if (new_sta_bw == link_sta->pub->bandwidth)
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index e6e9c378ed3a..1e8ce33bfe2e 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -273,7 +273,7 @@ static void ieee80211_link_sta_rc_update_omi(struct ieee80211_link_data *link,
band = link->conf->chanreq.oper.chan->band;
sband = sdata->local->hw.wiphy->bands[band];
- new_bw = ieee80211_sta_cur_vht_bw(link_sta);
+ new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index e32e40db081b..02ca6e1edc49 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -587,9 +587,14 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
enum ieee80211_sta_rx_bandwidth max_bw, new_bw;
struct ieee80211_supported_band *sband;
struct sta_opmode_info sta_opmode = {};
+ struct ieee80211_link_data *link;
lockdep_assert_wiphy(local->hw.wiphy);
+ link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
+ if (WARN_ON(!link))
+ return;
+
if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
max_bw = IEEE80211_STA_RX_BW_20;
else
@@ -597,7 +602,7 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
/* set cur_max_bandwidth and recalc sta bw */
link_sta->cur_max_bandwidth = max_bw;
- new_bw = ieee80211_sta_cur_vht_bw(link_sta);
+ new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 0c4796d96e65..6b72be762dbe 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2303,13 +2303,8 @@ enum ieee80211_sta_rx_bandwidth
ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef);
enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-static inline enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta)
-{
- return _ieee80211_sta_cur_vht_bw(link_sta, NULL);
-}
+ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef);
void ieee80211_process_mu_groups(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index ea190d54d195..6e0e5d3f5015 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -2571,8 +2571,8 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
return;
link_sta->pub->bandwidth =
- _ieee80211_sta_cur_vht_bw(link_sta,
- &link->csa.chanreq.oper);
+ ieee80211_sta_cur_vht_bw(link_sta,
+ &link->csa.chanreq.oper);
return;
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index d53cd8c5df4c..508aad14bdf6 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3519,7 +3519,7 @@ void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
link_sta->pub->rx_nss = link_sta->capa_nss;
- link_sta->pub->bandwidth = _ieee80211_sta_cur_vht_bw(link_sta, chandef);
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta, chandef);
}
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index cf7e5b8d373a..3df5f6c3f777 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -401,12 +401,12 @@ ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
* The purpose, however, is to save power, and that requires
* changing also transmissions to the AP and the chanctx. The
* transmissions depend on link_sta->bandwidth which is set in
- * _ieee80211_sta_cur_vht_bw() below, but the chanctx depends
+ * ieee80211_sta_cur_vht_bw() below, but the chanctx depends
* on the result of this function which is also called by
- * _ieee80211_sta_cur_vht_bw(), so we need to do that here as
+ * ieee80211_sta_cur_vht_bw(), so we need to do that here as
* well. This is sufficient for the steady state, but during
* the transition we already need to change TX/RX separately,
- * so _ieee80211_sta_cur_vht_bw() below applies the _tx one.
+ * so ieee80211_sta_cur_vht_bw() below applies the _tx one.
*/
return min(_ieee80211_sta_cap_rx_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
@@ -414,35 +414,19 @@ ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
/* FIXME: rename/move - this deals with everything not just VHT */
enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
+ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
{
struct sta_info *sta = link_sta->sta;
enum nl80211_chan_width bss_width;
enum ieee80211_sta_rx_bandwidth bw;
enum nl80211_band band;
- if (chandef) {
- bss_width = chandef->width;
- band = chandef->chan->band;
- } else {
- struct ieee80211_bss_conf *link_conf;
+ if (WARN_ON(!chandef))
+ return IEEE80211_STA_RX_BW_20;
- /* NAN operates on multiple channels so a chandef must be given */
- if (sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
- sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
- return IEEE80211_STA_RX_BW_MAX;
-
- rcu_read_lock();
- link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
- if (WARN_ON_ONCE(!link_conf)) {
- rcu_read_unlock();
- return IEEE80211_STA_RX_BW_20;
- }
- bss_width = link_conf->chanreq.oper.width;
- band = link_conf->chanreq.oper.chan->band;
- rcu_read_unlock();
- }
+ bss_width = chandef->width;
+ band = chandef->chan->band;
/* intentionally do not take rx_bw_omi_rx into account */
bw = _ieee80211_sta_cap_rx_bw(link_sta, band);
@@ -476,9 +460,14 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
{
enum ieee80211_sta_rx_bandwidth new_bw;
struct sta_opmode_info sta_opmode = {};
+ struct ieee80211_link_data *link;
u32 changed = 0;
u8 nss;
+ link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
+ if (WARN_ON(!link))
+ return 0;
+
/* ignore - no support for BF yet */
if (opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)
return 0;
@@ -525,7 +514,7 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
break;
}
- new_bw = ieee80211_sta_cur_vht_bw(link_sta);
+ new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
if (new_bw != link_sta->pub->bandwidth) {
link_sta->pub->bandwidth = new_bw;
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 13/20] wifi: cfg80211: remove HE/SAE H2E required fields
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (11 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 12/20] wifi: mac80211: remove ieee80211_sta_cur_vht_bw() Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 14/20] wifi: nl80211: reject beacons with bad HE operation Johannes Berg
` (6 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
These are not used by any drivers, even the HT/VHT ones
are only used by the qtnfmac driver. Remove the fields.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 9d3639ff9c28..8bebf45af95d 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1529,8 +1529,6 @@ struct cfg80211_s1g_short_beacon {
* @ht_required: stations must support HT
* @vht_required: stations must support VHT
* @twt_responder: Enable Target Wait Time
- * @he_required: stations must support HE
- * @sae_h2e_required: stations must support direct H2E technique in SAE
* @flags: flags, as defined in &enum nl80211_ap_settings_flags
* @he_obss_pd: OBSS Packet Detection settings
* @he_oper: HE operation IE (or %NULL if HE isn't enabled)
@@ -1566,7 +1564,7 @@ struct cfg80211_ap_settings {
const struct ieee80211_eht_cap_elem *eht_cap;
const struct ieee80211_eht_operation *eht_oper;
const struct ieee80211_uhr_operation *uhr_oper;
- bool ht_required, vht_required, he_required, sae_h2e_required;
+ bool ht_required, vht_required;
bool twt_responder;
u32 flags;
struct ieee80211_he_obss_pd he_obss_pd;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 14/20] wifi: nl80211: reject beacons with bad HE operation
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (12 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 13/20] wifi: cfg80211: remove HE/SAE H2E required fields Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-20 13:44 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 15/20] wifi: cfg80211: move AP HT/VHT/... operation to beacon info Johannes Berg
` (5 subsequent siblings)
19 siblings, 1 reply; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The HE operation element not only needs to be longer than
the fixed part, but also have an appropriate size for the
variable part inside of it. Check this.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/nl80211.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f334cdef8958..7a1c9faef443 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6641,10 +6641,6 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
params->ht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
params->vht_required = true;
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY)
- params->he_required = true;
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E)
- params->sae_h2e_required = true;
}
}
@@ -6677,8 +6673,12 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
params->he_cap = (void *)(cap->data + 1);
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
- if (cap && cap->datalen >= sizeof(*params->he_oper) + 1)
+ if (cap && cap->datalen >= sizeof(*params->he_oper) + 1) {
params->he_oper = (void *)(cap->data + 1);
+ /* takes extension ID into account */
+ if (cap->datalen < ieee80211_he_oper_size((void *)params->he_oper))
+ return -EINVAL;
+ }
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
if (cap) {
if (!cap->datalen)
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH 14/20] wifi: nl80211: reject beacons with bad HE operation
2026-04-15 12:42 ` [PATCH 14/20] wifi: nl80211: reject beacons with bad HE operation Johannes Berg
@ 2026-04-20 13:44 ` Johannes Berg
0 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-20 13:44 UTC (permalink / raw)
To: linux-wireless
On Wed, 2026-04-15 at 14:42 +0200, Johannes Berg wrote:
>
> --- a/net/wireless/nl80211.c
> +++ b/net/wireless/nl80211.c
> @@ -6641,10 +6641,6 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
> params->ht_required = true;
> if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
> params->vht_required = true;
> - if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY)
> - params->he_required = true;
> - if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E)
> - params->sae_h2e_required = true;
> }
> }
Not sure how that happened now when I was re-doing the patches on
wireless-next, but clearly this should be part of the previous patch,
not this one.
johannes
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 15/20] wifi: cfg80211: move AP HT/VHT/... operation to beacon info
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (13 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 14/20] wifi: nl80211: reject beacons with bad HE operation Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 16/20] wifi: nl80211: reject too short HT/VHT/HE/EHT capability/operation Johannes Berg
` (4 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The HT/VHT/HE/EHT/UHR operation can change, and might thus be
updated on each beacon update. Move them to the beacon struct
and parse them out of the beacon also on updates, not just on
starting the AP.
This also fixes checks in two ways:
- Regulatory checks in nl80211_validate_ap_phy_operation() are
now done also on updates, disallowing enabling HE/EHT/UHR on
channels that don't allow that after start. This checks only
operation now, but clients can't use it without operation.
- NL80211_ATTR_UHR_OPERATION is now required whenever UHR is
present in the beacon, and rejected otherwise.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
.../net/wireless/quantenna/qtnfmac/commands.c | 4 +-
include/net/cfg80211.h | 19 +--
net/mac80211/cfg.c | 10 +-
net/wireless/nl80211.c | 117 +++++++++++-------
4 files changed, 91 insertions(+), 59 deletions(-)
diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c
index d9c4dd6beaec..77929ed9ef78 100644
--- a/drivers/net/wireless/quantenna/qtnfmac/commands.c
+++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c
@@ -261,8 +261,8 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
cmd->p2p_ctwindow = s->p2p_ctwindow;
cmd->p2p_opp_ps = s->p2p_opp_ps;
cmd->pbss = s->pbss;
- cmd->ht_required = s->ht_required;
- cmd->vht_required = s->vht_required;
+ cmd->ht_required = s->beacon.ht_required;
+ cmd->vht_required = s->beacon.vht_required;
cmd->twt_responder = s->twt_responder;
if (s->he_obss_pd.enable) {
cmd->sr_params.sr_control |= QLINK_SR_SRG_INFORMATION_PRESENT;
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8bebf45af95d..7070e577342b 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1396,6 +1396,11 @@ struct cfg80211_rnr_elems {
* @he_bss_color: BSS Color settings
* @he_bss_color_valid: indicates whether bss color
* attribute is present in beacon data or not.
+ * @ht_required: stations must support HT
+ * @vht_required: stations must support VHT
+ * @he_oper: HE operation IE (or %NULL if HE isn't enabled)
+ * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
+ * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
*/
struct cfg80211_beacon_data {
unsigned int link_id;
@@ -1420,6 +1425,11 @@ struct cfg80211_beacon_data {
size_t civicloc_len;
struct cfg80211_he_bss_color he_bss_color;
bool he_bss_color_valid;
+
+ bool ht_required, vht_required;
+ const struct ieee80211_he_operation *he_oper;
+ const struct ieee80211_eht_operation *eht_oper;
+ const struct ieee80211_uhr_operation *uhr_oper;
};
struct mac_address {
@@ -1524,14 +1534,9 @@ struct cfg80211_s1g_short_beacon {
* @vht_cap: VHT capabilities (or %NULL if VHT isn't enabled)
* @he_cap: HE capabilities (or %NULL if HE isn't enabled)
* @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled)
- * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
- * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
- * @ht_required: stations must support HT
- * @vht_required: stations must support VHT
* @twt_responder: Enable Target Wait Time
* @flags: flags, as defined in &enum nl80211_ap_settings_flags
* @he_obss_pd: OBSS Packet Detection settings
- * @he_oper: HE operation IE (or %NULL if HE isn't enabled)
* @fils_discovery: FILS discovery transmission parameters
* @unsol_bcast_probe_resp: Unsolicited broadcast probe response parameters
* @mbssid_config: AP settings for multiple bssid
@@ -1560,11 +1565,7 @@ struct cfg80211_ap_settings {
const struct ieee80211_ht_cap *ht_cap;
const struct ieee80211_vht_cap *vht_cap;
const struct ieee80211_he_cap_elem *he_cap;
- const struct ieee80211_he_operation *he_oper;
const struct ieee80211_eht_cap_elem *eht_cap;
- const struct ieee80211_eht_operation *eht_oper;
- const struct ieee80211_uhr_operation *uhr_oper;
- bool ht_required, vht_required;
bool twt_responder;
u32 flags;
struct ieee80211_he_obss_pd he_obss_pd;
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 15a68d3f8e73..bc4a8e5ad039 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1541,13 +1541,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
}
- if (params->he_cap && params->he_oper) {
+ if (params->he_cap && params->beacon.he_oper) {
link_conf->he_support = true;
link_conf->htc_trig_based_pkt_ext =
- le32_get_bits(params->he_oper->he_oper_params,
+ le32_get_bits(params->beacon.he_oper->he_oper_params,
IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK);
link_conf->frame_time_rts_th =
- le32_get_bits(params->he_oper->he_oper_params,
+ le32_get_bits(params->beacon.he_oper->he_oper_params,
IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK);
changed |= BSS_CHANGED_HE_OBSS_PD;
@@ -1596,7 +1596,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_160MHZ |
IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ);
link_conf->eht_disable_mcs15 =
- u8_get_bits(params->eht_oper->params,
+ u8_get_bits(params->beacon.eht_oper->params,
IEEE80211_EHT_OPER_MCS15_DISABLE);
} else {
link_conf->eht_su_beamformer = false;
@@ -1604,7 +1604,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
link_conf->eht_mu_beamformer = false;
}
- if (params->uhr_oper) {
+ if (params->beacon.uhr_oper) {
if (!link_conf->eht_support)
return -EOPNOTSUPP;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 7a1c9faef443..e0ed9ed28093 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6628,7 +6628,7 @@ nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
return 0;
}
-static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
+static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
const struct element *rates)
{
int i;
@@ -6638,31 +6638,23 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
for (i = 0; i < rates->datalen; i++) {
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
- params->ht_required = true;
+ bcn->ht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
- params->vht_required = true;
+ bcn->vht_required = true;
}
}
/*
* Since the nl80211 API didn't include, from the beginning, attributes about
- * HT/VHT requirements/capabilities, we parse them out of the IEs for the
- * benefit of drivers that rebuild IEs in the firmware.
+ * HT/VHT/... capabilities, we parse them out of the elements and check for
+ * validity for use by drivers/mac80211.
*/
-static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
+static int nl80211_calculate_ap_capabilities(struct cfg80211_ap_settings *params)
{
- const struct cfg80211_beacon_data *bcn = ¶ms->beacon;
- size_t ies_len = bcn->tail_len;
- const u8 *ies = bcn->tail;
- const struct element *rates;
+ size_t ies_len = params->beacon.tail_len;
+ const u8 *ies = params->beacon.tail;
const struct element *cap;
- rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(params, rates);
-
- rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(params, rates);
-
cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->ht_cap))
params->ht_cap = (void *)cap->data;
@@ -6672,31 +6664,62 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
params->he_cap = (void *)(cap->data + 1);
- cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
- if (cap && cap->datalen >= sizeof(*params->he_oper) + 1) {
- params->he_oper = (void *)(cap->data + 1);
- /* takes extension ID into account */
- if (cap->datalen < ieee80211_he_oper_size((void *)params->he_oper))
- return -EINVAL;
- }
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
if (cap) {
- if (!cap->datalen)
- return -EINVAL;
params->eht_cap = (void *)(cap->data + 1);
if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap,
(const u8 *)params->eht_cap,
cap->datalen - 1, true))
return -EINVAL;
}
- cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
- if (cap) {
- if (!cap->datalen)
+
+ return 0;
+}
+
+/*
+ * Since the nl80211 API didn't include, from the beginning, attributes about
+ * HT/VHT/... operation, we parse them out of the elements and check for
+ * validity for use by drivers/mac80211.
+ */
+static int nl80211_calculate_ap_operation(struct genl_info *info,
+ struct cfg80211_beacon_data *bcn)
+{
+ size_t ies_len = bcn->tail_len;
+ const u8 *ies = bcn->tail;
+ const struct element *rates;
+ const struct element *op;
+
+ rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
+ if (op && op->datalen >= sizeof(*bcn->he_oper) + 1) {
+ bcn->he_oper = (void *)(op->data + 1);
+ /* takes extension ID into account */
+ if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper))
return -EINVAL;
- params->eht_oper = (void *)(cap->data + 1);
- if (!ieee80211_eht_oper_size_ok((const u8 *)params->eht_oper,
- cap->datalen - 1))
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (!ieee80211_eht_oper_size_ok(op->data + 1,
+ op->datalen - 1))
return -EINVAL;
+ bcn->eht_oper = (void *)(op->data + 1);
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
+ if (op) {
+ /* need full UHR operation separately */
+ if (!info->attrs[NL80211_ATTR_UHR_OPERATION])
+ return -EINVAL;
+ bcn->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
+ } else if (info->attrs[NL80211_ATTR_UHR_OPERATION]) {
+ GENL_SET_ERR_MSG(info, "unexpected UHR operation");
+ return -EINVAL;
}
return 0;
@@ -6826,19 +6849,16 @@ static void nl80211_send_ap_started(struct wireless_dev *wdev,
nlmsg_free(msg);
}
-static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params)
+static int nl80211_validate_ap_phy_operation(struct ieee80211_channel *channel,
+ struct cfg80211_beacon_data *bcn)
{
- struct ieee80211_channel *channel = params->chandef.chan;
-
- if ((params->he_cap || params->he_oper) &&
- (channel->flags & IEEE80211_CHAN_NO_HE))
+ if (bcn->he_oper && (channel->flags & IEEE80211_CHAN_NO_HE))
return -EOPNOTSUPP;
- if ((params->eht_cap || params->eht_oper) &&
- (channel->flags & IEEE80211_CHAN_NO_EHT))
+ if (bcn->eht_oper && (channel->flags & IEEE80211_CHAN_NO_EHT))
return -EOPNOTSUPP;
- if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
+ if (bcn->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
return -EOPNOTSUPP;
return 0;
@@ -7136,14 +7156,16 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
- err = nl80211_calculate_ap_params(params);
+ err = nl80211_calculate_ap_capabilities(params);
if (err)
goto out;
- if (info->attrs[NL80211_ATTR_UHR_OPERATION])
- params->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
+ err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
+ if (err)
+ goto out;
- err = nl80211_validate_ap_phy_operation(params);
+ err = nl80211_validate_ap_phy_operation(params->chandef.chan,
+ ¶ms->beacon);
if (err)
goto out;
@@ -7218,6 +7240,15 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
if (err)
goto out;
+ err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
+ if (err)
+ goto out;
+
+ err = nl80211_validate_ap_phy_operation(wdev->links[link_id].ap.chandef.chan,
+ ¶ms->beacon);
+ if (err)
+ goto out;
+
/* recheck beaconing is permitted with possibly changed power type */
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 16/20] wifi: nl80211: reject too short HT/VHT/HE/EHT capability/operation
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (14 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 15/20] wifi: cfg80211: move AP HT/VHT/... operation to beacon info Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon Johannes Berg
` (3 subsequent siblings)
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
If any of these are present, the code only assigns pointers when
they're also long enough. Instead of ignoring them in that case,
reject the configuration instead.
Also add error messages to existing error paths.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/nl80211.c | 51 +++++++++++++++++++++++++++++++++---------
1 file changed, 41 insertions(+), 10 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e0ed9ed28093..263d04a1366e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6649,28 +6649,49 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
* HT/VHT/... capabilities, we parse them out of the elements and check for
* validity for use by drivers/mac80211.
*/
-static int nl80211_calculate_ap_capabilities(struct cfg80211_ap_settings *params)
+static int nl80211_calculate_ap_capabilities(struct genl_info *info,
+ struct cfg80211_ap_settings *params)
{
size_t ies_len = params->beacon.tail_len;
const u8 *ies = params->beacon.tail;
const struct element *cap;
cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len);
- if (cap && cap->datalen >= sizeof(*params->ht_cap))
+ if (cap) {
+ if (cap->datalen < sizeof(*params->ht_cap)) {
+ GENL_SET_ERR_MSG(info, "bad HT capability in beacon");
+ return -EINVAL;
+ }
params->ht_cap = (void *)cap->data;
+ }
+
cap = cfg80211_find_elem(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
- if (cap && cap->datalen >= sizeof(*params->vht_cap))
+ if (cap) {
+ if (cap->datalen < sizeof(*params->vht_cap)) {
+ GENL_SET_ERR_MSG(info, "bad VHT capability in beacon");
+ return -EINVAL;
+ }
params->vht_cap = (void *)cap->data;
+ }
+
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
- if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
+ if (cap) {
+ if (cap->datalen < sizeof(*params->he_cap) + 1) {
+ GENL_SET_ERR_MSG(info, "bad HE capability in beacon");
+ return -EINVAL;
+ }
params->he_cap = (void *)(cap->data + 1);
+ }
+
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
if (cap) {
params->eht_cap = (void *)(cap->data + 1);
if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap,
(const u8 *)params->eht_cap,
- cap->datalen - 1, true))
+ cap->datalen - 1, true)) {
+ GENL_SET_ERR_MSG(info, "bad EHT capability in beacon");
return -EINVAL;
+ }
}
return 0;
@@ -6696,26 +6717,36 @@ static int nl80211_calculate_ap_operation(struct genl_info *info,
nl80211_check_ap_rate_selectors(bcn, rates);
op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
- if (op && op->datalen >= sizeof(*bcn->he_oper) + 1) {
+ if (op) {
+ if (op->datalen < sizeof(*bcn->he_oper) + 1) {
+ GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
+ return -EINVAL;
+ }
bcn->he_oper = (void *)(op->data + 1);
/* takes extension ID into account */
- if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper))
+ if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
+ GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
return -EINVAL;
+ }
}
op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
if (op) {
if (!ieee80211_eht_oper_size_ok(op->data + 1,
- op->datalen - 1))
+ op->datalen - 1)) {
+ GENL_SET_ERR_MSG(info, "bad EHT operation in beacon");
return -EINVAL;
+ }
bcn->eht_oper = (void *)(op->data + 1);
}
op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
if (op) {
/* need full UHR operation separately */
- if (!info->attrs[NL80211_ATTR_UHR_OPERATION])
+ if (!info->attrs[NL80211_ATTR_UHR_OPERATION]) {
+ GENL_SET_ERR_MSG(info, "missing UHR operation");
return -EINVAL;
+ }
bcn->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
} else if (info->attrs[NL80211_ATTR_UHR_OPERATION]) {
GENL_SET_ERR_MSG(info, "unexpected UHR operation");
@@ -7156,7 +7187,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
- err = nl80211_calculate_ap_capabilities(params);
+ err = nl80211_calculate_ap_capabilities(info, params);
if (err)
goto out;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (15 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 16/20] wifi: nl80211: reject too short HT/VHT/HE/EHT capability/operation Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 15:22 ` Pablo MARTIN-GOMEZ
2026-04-15 12:42 ` [PATCH 18/20] wifi: nl80211: always validate AP operation/PHY regulatory Johannes Berg
` (2 subsequent siblings)
19 siblings, 1 reply; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
In addition to providing HE/EHT/UHR operation, also check
and provide HT/VHT operation, so that drivers have it and
can use it, e.g. to correctly calculate station bandwidth.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 4 ++++
net/wireless/nl80211.c | 18 ++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 7070e577342b..a40ab36b8edb 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1398,6 +1398,8 @@ struct cfg80211_rnr_elems {
* attribute is present in beacon data or not.
* @ht_required: stations must support HT
* @vht_required: stations must support VHT
+ * @ht_oper: HT operation element (or %NULL if HT isn't enabled)
+ * @vht_oper: VHT operation element (or %NULL if VHT isn't enabled)
* @he_oper: HE operation IE (or %NULL if HE isn't enabled)
* @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
* @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
@@ -1427,6 +1429,8 @@ struct cfg80211_beacon_data {
bool he_bss_color_valid;
bool ht_required, vht_required;
+ const struct ieee80211_ht_operation *ht_oper;
+ const struct ieee80211_vht_operation *vht_oper;
const struct ieee80211_he_operation *he_oper;
const struct ieee80211_eht_operation *eht_oper;
const struct ieee80211_uhr_operation *uhr_oper;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 263d04a1366e..f65f3887ea62 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6730,6 +6730,24 @@ static int nl80211_calculate_ap_operation(struct genl_info *info,
}
}
+ op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->ht_oper)) {
+ GENL_SET_ERR_MSG(info, "bad HT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->ht_oper = (void *)op->data;
+ }
+
+ op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->vht_oper)) {
+ GENL_SET_ERR_MSG(info, "bad VHT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->vht_oper = (void *)op->data;
+ }
+
op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
if (op) {
if (!ieee80211_eht_oper_size_ok(op->data + 1,
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon
2026-04-15 12:42 ` [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon Johannes Berg
@ 2026-04-15 15:22 ` Pablo MARTIN-GOMEZ
2026-04-15 15:42 ` Johannes Berg
0 siblings, 1 reply; 27+ messages in thread
From: Pablo MARTIN-GOMEZ @ 2026-04-15 15:22 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
On 15/04/2026 14:42, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
>
> In addition to providing HE/EHT/UHR operation, also check
> and provide HT/VHT operation, so that drivers have it and
> can use it, e.g. to correctly calculate station bandwidth.
>
> Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> include/net/cfg80211.h | 4 ++++
> net/wireless/nl80211.c | 18 ++++++++++++++++++
> 2 files changed, 22 insertions(+)
>
> diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
> index 7070e577342b..a40ab36b8edb 100644
> --- a/include/net/cfg80211.h
> +++ b/include/net/cfg80211.h
> @@ -1398,6 +1398,8 @@ struct cfg80211_rnr_elems {
> * attribute is present in beacon data or not.
> * @ht_required: stations must support HT
> * @vht_required: stations must support VHT
> + * @ht_oper: HT operation element (or %NULL if HT isn't enabled)
Might be some nitpicking you can ignore, but HT operation element is optional when HT is enabled. With the current comment, someone might (wrongly) deduce that if `ht_oper` is NULL then HT is not enabled.
> + * @vht_oper: VHT operation element (or %NULL if VHT isn't enabled)
> * @he_oper: HE operation IE (or %NULL if HE isn't enabled)
> * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
> * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
> @@ -1427,6 +1429,8 @@ struct cfg80211_beacon_data {
> bool he_bss_color_valid;
>
> bool ht_required, vht_required;
> + const struct ieee80211_ht_operation *ht_oper;
> + const struct ieee80211_vht_operation *vht_oper;
> const struct ieee80211_he_operation *he_oper;
> const struct ieee80211_eht_operation *eht_oper;
> const struct ieee80211_uhr_operation *uhr_oper;
[...]
Pablo MG
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon
2026-04-15 15:22 ` Pablo MARTIN-GOMEZ
@ 2026-04-15 15:42 ` Johannes Berg
2026-04-15 16:02 ` Pablo MARTIN-GOMEZ
0 siblings, 1 reply; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 15:42 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless; +Cc: Miriam Rachel Korenblit
On Wed, 2026-04-15 at 17:22 +0200, Pablo MARTIN-GOMEZ wrote:
>
> > + * @ht_oper: HT operation element (or %NULL if HT isn't enabled)
> Might be some nitpicking you can ignore, but HT operation element is optional when HT is enabled. With the current comment, someone might (wrongly) deduce that if `ht_oper` is NULL then HT is not enabled.
>
Not sure how you mean that, but not really? If the AP has HT then it
must have HT operation, otherwise the clients can't use it?
johannes
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon
2026-04-15 15:42 ` Johannes Berg
@ 2026-04-15 16:02 ` Pablo MARTIN-GOMEZ
2026-04-15 16:05 ` Johannes Berg
0 siblings, 1 reply; 27+ messages in thread
From: Pablo MARTIN-GOMEZ @ 2026-04-15 16:02 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Miriam Rachel Korenblit
On 15/04/2026 17:42, Johannes Berg wrote:
> On Wed, 2026-04-15 at 17:22 +0200, Pablo MARTIN-GOMEZ wrote:
>>
>>> + * @ht_oper: HT operation element (or %NULL if HT isn't enabled)
>> Might be some nitpicking you can ignore, but HT operation element is optional when HT is enabled. With the current comment, someone might (wrongly) deduce that if `ht_oper` is NULL then HT is not enabled.
>>
>
> Not sure how you mean that, but not really? If the AP has HT then it
> must have HT operation, otherwise the clients can't use it?
Oh my bad, you can ignore my comment. I wasn't sure if HT operation
element was mandatory, so I quickly checked the standard and the wording
of second table of 6.5.3.3.2 misled me in thinking it was optional ("The
parameter is optionally present if dot11HighThroughputOptionImplemented
is true; otherwise not present.")
>
> johannes
Pablo MG
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon
2026-04-15 16:02 ` Pablo MARTIN-GOMEZ
@ 2026-04-15 16:05 ` Johannes Berg
0 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 16:05 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless; +Cc: Miriam Rachel Korenblit
On Wed, 2026-04-15 at 18:02 +0200, Pablo MARTIN-GOMEZ wrote:
> On 15/04/2026 17:42, Johannes Berg wrote:
> > On Wed, 2026-04-15 at 17:22 +0200, Pablo MARTIN-GOMEZ wrote:
> > >
> > > > + * @ht_oper: HT operation element (or %NULL if HT isn't enabled)
> > > Might be some nitpicking you can ignore, but HT operation element is optional when HT is enabled. With the current comment, someone might (wrongly) deduce that if `ht_oper` is NULL then HT is not enabled.
> > >
> >
> > Not sure how you mean that, but not really? If the AP has HT then it
> > must have HT operation, otherwise the clients can't use it?
> Oh my bad, you can ignore my comment. I wasn't sure if HT operation
> element was mandatory, so I quickly checked the standard and the wording
> of second table of 6.5.3.3.2 misled me in thinking it was optional ("The
> parameter is optionally present if dot11HighThroughputOptionImplemented
> is true; otherwise not present.")
>
Huh. I'm not sure what you're referring to (spec version is highly
relevant), but HT operation in a beacon frame, REVmf D2.0 says:
The HT Operation element is included by an AP and a mesh STA
when dot11HighThroughputOperationImplemented is true and the AP
or mesh STA is not a STA 6G.
johannes
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 18/20] wifi: nl80211: always validate AP operation/PHY regulatory
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (16 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 17/20] wifi: cfg80211: provide HT/VHT operation for AP beacon Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 19/20] wifi: mac80211: clarify per-STA bandwidth handling Johannes Berg
2026-04-15 12:42 ` [PATCH 20/20] wifi: mac80211: fix per-station PHY capability bandwidth Johannes Berg
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
Instead of validating the AP operation elements and PHY regulatory
individually in each caller, which missed CSA and color change,
pass the channel to the beacon parsing function and validate the
parameters there. This adds it to the missing places.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/nl80211.c | 260 +++++++++++++++++++----------------------
1 file changed, 123 insertions(+), 137 deletions(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f65f3887ea62..b9c7dc099b33 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6394,9 +6394,104 @@ static int nl80211_parse_he_bss_color(struct nlattr *attrs,
return 0;
}
+static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
+ const struct element *rates)
+{
+ int i;
+
+ if (!rates)
+ return;
+
+ for (i = 0; i < rates->datalen; i++) {
+ if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
+ bcn->ht_required = true;
+ if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
+ bcn->vht_required = true;
+ }
+}
+
+/*
+ * Since the nl80211 API didn't include, from the beginning, attributes about
+ * HT/VHT/... operation, we parse them out of the elements and check for
+ * validity for use by drivers/mac80211.
+ */
+static int nl80211_calculate_ap_operation(struct nlattr *attrs[],
+ struct cfg80211_beacon_data *bcn,
+ struct netlink_ext_ack *extack)
+{
+ size_t ies_len = bcn->tail_len;
+ const u8 *ies = bcn->tail;
+ const struct element *rates;
+ const struct element *op;
+
+ rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
+ nl80211_check_ap_rate_selectors(bcn, rates);
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->he_oper) + 1) {
+ NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
+ return -EINVAL;
+ }
+ bcn->he_oper = (void *)(op->data + 1);
+ /* takes extension ID into account */
+ if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
+ NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
+ return -EINVAL;
+ }
+ }
+
+ op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->ht_oper)) {
+ NL_SET_ERR_MSG(extack, "bad HT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->ht_oper = (void *)op->data;
+ }
+
+ op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (op->datalen < sizeof(*bcn->vht_oper)) {
+ NL_SET_ERR_MSG(extack, "bad VHT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->vht_oper = (void *)op->data;
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
+ if (op) {
+ if (!ieee80211_eht_oper_size_ok(op->data + 1,
+ op->datalen - 1)) {
+ NL_SET_ERR_MSG(extack, "bad EHT operation in beacon");
+ return -EINVAL;
+ }
+ bcn->eht_oper = (void *)(op->data + 1);
+ }
+
+ op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
+ if (op) {
+ /* need full UHR operation separately */
+ if (!attrs[NL80211_ATTR_UHR_OPERATION]) {
+ NL_SET_ERR_MSG(extack, "missing UHR operation");
+ return -EINVAL;
+ }
+ bcn->uhr_oper = nla_data(attrs[NL80211_ATTR_UHR_OPERATION]);
+ } else if (attrs[NL80211_ATTR_UHR_OPERATION]) {
+ NL_SET_ERR_MSG(extack, "unexpected UHR operation");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs[],
struct cfg80211_beacon_data *bcn,
+ struct ieee80211_channel *chan,
struct netlink_ext_ack *extack)
{
bool haveinfo = false;
@@ -6511,6 +6606,19 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
}
}
+ err = nl80211_calculate_ap_operation(attrs, bcn, extack);
+ if (err)
+ return err;
+
+ if (bcn->he_oper && (chan->flags & IEEE80211_CHAN_NO_HE))
+ return -EOPNOTSUPP;
+
+ if (bcn->eht_oper && (chan->flags & IEEE80211_CHAN_NO_EHT))
+ return -EOPNOTSUPP;
+
+ if (bcn->uhr_oper && (chan->flags & IEEE80211_CHAN_NO_UHR))
+ return -EOPNOTSUPP;
+
return 0;
}
@@ -6628,22 +6736,6 @@ nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
return 0;
}
-static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
- const struct element *rates)
-{
- int i;
-
- if (!rates)
- return;
-
- for (i = 0; i < rates->datalen; i++) {
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
- bcn->ht_required = true;
- if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
- bcn->vht_required = true;
- }
-}
-
/*
* Since the nl80211 API didn't include, from the beginning, attributes about
* HT/VHT/... capabilities, we parse them out of the elements and check for
@@ -6697,83 +6789,6 @@ static int nl80211_calculate_ap_capabilities(struct genl_info *info,
return 0;
}
-/*
- * Since the nl80211 API didn't include, from the beginning, attributes about
- * HT/VHT/... operation, we parse them out of the elements and check for
- * validity for use by drivers/mac80211.
- */
-static int nl80211_calculate_ap_operation(struct genl_info *info,
- struct cfg80211_beacon_data *bcn)
-{
- size_t ies_len = bcn->tail_len;
- const u8 *ies = bcn->tail;
- const struct element *rates;
- const struct element *op;
-
- rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(bcn, rates);
-
- rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
- nl80211_check_ap_rate_selectors(bcn, rates);
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->he_oper) + 1) {
- GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
- return -EINVAL;
- }
- bcn->he_oper = (void *)(op->data + 1);
- /* takes extension ID into account */
- if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
- GENL_SET_ERR_MSG(info, "bad HE operation in beacon");
- return -EINVAL;
- }
- }
-
- op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->ht_oper)) {
- GENL_SET_ERR_MSG(info, "bad HT operation in beacon");
- return -EINVAL;
- }
- bcn->ht_oper = (void *)op->data;
- }
-
- op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
- if (op) {
- if (op->datalen < sizeof(*bcn->vht_oper)) {
- GENL_SET_ERR_MSG(info, "bad VHT operation in beacon");
- return -EINVAL;
- }
- bcn->vht_oper = (void *)op->data;
- }
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
- if (op) {
- if (!ieee80211_eht_oper_size_ok(op->data + 1,
- op->datalen - 1)) {
- GENL_SET_ERR_MSG(info, "bad EHT operation in beacon");
- return -EINVAL;
- }
- bcn->eht_oper = (void *)(op->data + 1);
- }
-
- op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
- if (op) {
- /* need full UHR operation separately */
- if (!info->attrs[NL80211_ATTR_UHR_OPERATION]) {
- GENL_SET_ERR_MSG(info, "missing UHR operation");
- return -EINVAL;
- }
- bcn->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
- } else if (info->attrs[NL80211_ATTR_UHR_OPERATION]) {
- GENL_SET_ERR_MSG(info, "unexpected UHR operation");
- return -EINVAL;
- }
-
- return 0;
-}
-
static bool nl80211_get_ap_channel(struct cfg80211_registered_device *rdev,
struct cfg80211_ap_settings *params)
{
@@ -6898,21 +6913,6 @@ static void nl80211_send_ap_started(struct wireless_dev *wdev,
nlmsg_free(msg);
}
-static int nl80211_validate_ap_phy_operation(struct ieee80211_channel *channel,
- struct cfg80211_beacon_data *bcn)
-{
- if (bcn->he_oper && (channel->flags & IEEE80211_CHAN_NO_HE))
- return -EOPNOTSUPP;
-
- if (bcn->eht_oper && (channel->flags & IEEE80211_CHAN_NO_EHT))
- return -EOPNOTSUPP;
-
- if (bcn->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
- return -EOPNOTSUPP;
-
- return 0;
-}
-
static int
nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs,
@@ -6985,11 +6985,6 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (!params)
return -ENOMEM;
- err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
- info->extack);
- if (err)
- goto out;
-
params->beacon_interval =
nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
params->dtim_period =
@@ -7106,6 +7101,11 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
+ err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
+ params->chandef.chan, info->extack);
+ if (err)
+ goto out;
+
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
beacon_check.reg_power =
@@ -7209,15 +7209,6 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (err)
goto out;
- err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
- if (err)
- goto out;
-
- err = nl80211_validate_ap_phy_operation(params->chandef.chan,
- ¶ms->beacon);
- if (err)
- goto out;
-
if (info->attrs[NL80211_ATTR_AP_SETTINGS_FLAGS])
params->flags = nla_get_u32(
info->attrs[NL80211_ATTR_AP_SETTINGS_FLAGS]);
@@ -7285,19 +7276,11 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
return -ENOMEM;
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms->beacon,
+ wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
- err = nl80211_calculate_ap_operation(info, ¶ms->beacon);
- if (err)
- goto out;
-
- err = nl80211_validate_ap_phy_operation(wdev->links[link_id].ap.chandef.chan,
- ¶ms->beacon);
- if (err)
- goto out;
-
/* recheck beaconing is permitted with possibly changed power type */
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
@@ -11898,11 +11881,16 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
params.count = cs_count;
+ err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
+ ¶ms.chandef);
+ if (err)
+ goto free;
+
if (!need_new_beacon)
goto skip_beacons;
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_after,
- info->extack);
+ params.chandef.chan, info->extack);
if (err)
goto free;
@@ -11919,6 +11907,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
err = nl80211_parse_beacon(rdev, csa_attrs, ¶ms.beacon_csa,
+ wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto free;
@@ -11947,11 +11936,6 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
skip_beacons:
- err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
- ¶ms.chandef);
- if (err)
- goto free;
-
if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, ¶ms.chandef,
wdev->iftype)) {
err = -EINVAL;
@@ -18405,6 +18389,7 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
params.color = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COLOR]);
err = nl80211_parse_beacon(rdev, info->attrs, ¶ms.beacon_next,
+ wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
return err;
@@ -18420,6 +18405,7 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
goto out;
err = nl80211_parse_beacon(rdev, tb, ¶ms.beacon_color_change,
+ wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 19/20] wifi: mac80211: clarify per-STA bandwidth handling
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (17 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 18/20] wifi: nl80211: always validate AP operation/PHY regulatory Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
2026-04-15 12:42 ` [PATCH 20/20] wifi: mac80211: fix per-station PHY capability bandwidth Johannes Berg
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
The per-STA bandwidth handling is completely confusing, given
the function names etc.
Move everything to sta_info.c and rename the functions to
accurately reflect what they return:
- ieee80211_sta_bw_capability()
- ieee80211_sta_current_bw() can return the appropriate
bandwidth in the desired direction (a new enum)
At any given time, the bandwidth with which we expect to receive
frames from a station may differ from the bandwidth with which
we may transmit frames to the station; this will happen either
during RX OMI negotiation, or for a long time if the station
used an HT Notify Channel Width frame. We also implement the
(VHT) Operating Mode Notification as an asymmetric setting, even
if the spec would seem to imply it could be symmetric.
Also rename the 'cur_max_bandwidth' value to 'op_mode_bw' which
matches the 'op_mode_nss', indicating more clearly what it _is_
rather than how it's _used_. It's not quite precise (NSS is)
since it's also HT chanwidth notification, but that seems OK.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/chan.c | 11 +--
net/mac80211/he.c | 3 +-
net/mac80211/ht.c | 7 +-
net/mac80211/ieee80211_i.h | 7 --
net/mac80211/mlme.c | 5 +-
net/mac80211/sta_info.c | 157 ++++++++++++++++++++++++++++++++++++-
net/mac80211/sta_info.h | 14 +++-
net/mac80211/tdls.c | 8 +-
net/mac80211/vht.c | 136 ++------------------------------
9 files changed, 193 insertions(+), 155 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 13c0d080ef2a..ecc0123ed448 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -455,10 +455,10 @@ ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
* We assume that TX/RX might be asymmetric (so e.g. VHT operating
* mode notification changes what a STA wants to receive, but not
* necessarily what it will transmit to us), and therefore use the
- * capabilities here. Calling it RX bandwidth capability is a bit
- * wrong though, since capabilities are in fact symmetric.
+ * "from station" bandwidth here.
*/
- width = ieee80211_sta_cap_rx_bw(link_sta, &link->conf->chanreq.oper);
+ width = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_RX_FROM_STA);
if (width == IEEE80211_STA_RX_BW_20 &&
!link_sta->pub->ht_cap.ht_supported &&
@@ -693,8 +693,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
else
new_chandef = &link_conf->chanreq.oper;
- new_sta_bw = ieee80211_sta_cur_vht_bw(link_sta,
- new_chandef);
+ new_sta_bw = ieee80211_sta_current_bw(link_sta,
+ new_chandef,
+ IEEE80211_STA_BW_TX_TO_STA);
/* nothing change */
if (new_sta_bw == link_sta->pub->bandwidth)
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 1e8ce33bfe2e..b7d9e4cb6ba6 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -273,7 +273,8 @@ static void ieee80211_link_sta_rc_update_omi(struct ieee80211_link_data *link,
band = link->conf->chanreq.oper.chan->band;
sband = sdata->local->hw.wiphy->bands[band];
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 02ca6e1edc49..6285ac15c16c 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -600,9 +600,10 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
else
max_bw = IEEE80211_STA_RX_BW_MAX;
- /* set cur_max_bandwidth and recalc sta bw */
- link_sta->cur_max_bandwidth = max_bw;
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ /* set op_mode_bw and recalc sta bw */
+ link_sta->op_mode_bw = max_bw;
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 6b72be762dbe..f1bab633697e 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2299,13 +2299,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_vht_cap *vht_cap_ie,
const struct ieee80211_vht_cap *vht_cap_ie2,
struct link_sta_info *link_sta);
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef);
-
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/mlme.c b/net/mac80211/mlme.c
index 6e0e5d3f5015..00b4beff0e43 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -2571,8 +2571,9 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
return;
link_sta->pub->bandwidth =
- ieee80211_sta_cur_vht_bw(link_sta,
- &link->csa.chanreq.oper);
+ ieee80211_sta_current_bw(link_sta,
+ &link->csa.chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
return;
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 508aad14bdf6..ec043d88a6a9 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -572,13 +572,13 @@ static int sta_info_alloc_link(struct ieee80211_local *local,
link_info->rx_omi_bw_tx = IEEE80211_STA_RX_BW_MAX;
link_info->rx_omi_bw_staging = IEEE80211_STA_RX_BW_MAX;
- link_info->cur_max_bandwidth = IEEE80211_STA_RX_BW_MAX;
+ link_info->op_mode_bw = IEEE80211_STA_RX_BW_MAX;
/*
* Cause (a) warning(s) if IEEE80211_STA_RX_BW_MAX != 320
* or if new values are added to the enum.
*/
- switch (link_info->cur_max_bandwidth) {
+ switch (link_info->op_mode_bw) {
case IEEE80211_STA_RX_BW_20:
case IEEE80211_STA_RX_BW_40:
case IEEE80211_STA_RX_BW_80:
@@ -3519,7 +3519,9 @@ void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
link_sta->pub->rx_nss = link_sta->capa_nss;
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta, chandef);
+ link_sta->pub->bandwidth =
+ ieee80211_sta_current_bw(link_sta, chandef,
+ IEEE80211_STA_BW_TX_TO_STA);
}
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
@@ -3554,3 +3556,152 @@ bool lockdep_sta_mutex_held(struct ieee80211_sta *pubsta)
}
EXPORT_SYMBOL(lockdep_sta_mutex_held);
#endif
+
+/**
+ * ieee80211_sta_bw_capability - get STA's bandwidth capability
+ * @link_sta: the (link) STA to get the capability for
+ * @band: the band to get the capability on
+ *
+ * Return: the maximum bandwidth supported by the STA
+ */
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
+ enum nl80211_band band)
+{
+ struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
+ struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
+ struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
+ u32 cap_width;
+
+ if (he_cap->has_he) {
+ u8 info;
+
+ if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
+ info = eht_cap->eht_cap_elem.phy_cap_info[0];
+
+ if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
+ return IEEE80211_STA_RX_BW_320;
+ }
+
+ info = he_cap->he_cap_elem.phy_cap_info[0];
+
+ if (band == NL80211_BAND_2GHZ) {
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
+ return IEEE80211_STA_RX_BW_40;
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
+ info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
+ return IEEE80211_STA_RX_BW_160;
+
+ if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
+ return IEEE80211_STA_RX_BW_80;
+
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (!vht_cap->vht_supported)
+ return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
+ IEEE80211_STA_RX_BW_40 :
+ IEEE80211_STA_RX_BW_20;
+
+ cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
+
+ if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
+ cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
+ return IEEE80211_STA_RX_BW_160;
+
+ /*
+ * If this is non-zero, then it does support 160 MHz after all,
+ * in one form or the other. We don't distinguish here (or even
+ * above) between 160 and 80+80 yet.
+ */
+ if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
+ return IEEE80211_STA_RX_BW_160;
+
+ return IEEE80211_STA_RX_BW_80;
+}
+
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
+{
+ /*
+ * Take RX OMI into account. The value "rx_omi_bw_rx" is what
+ * we've indicated to the STA we can currently receive.
+ *
+ * This is needed since the RX OMI is done by us to save power,
+ * requiring changing both our TX (rate control) and RX (chanctx),
+ * which in turn needs to be done in the right order (stop TX
+ * at a higher bandwidth first while reducing bandwidth, and
+ * change the chanctx only after the peer accepts, etc.)
+ */
+ return min(ieee80211_sta_bw_capability(link_sta, chandef->chan->band),
+ link_sta->rx_omi_bw_rx);
+}
+
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef)
+{
+ struct sta_info *sta = link_sta->sta;
+ enum nl80211_chan_width bss_width;
+ enum ieee80211_sta_rx_bandwidth bw;
+ enum nl80211_band band;
+
+ bss_width = chandef->width;
+ band = chandef->chan->band;
+
+ bw = ieee80211_sta_bw_capability(link_sta, band);
+ bw = min(bw, link_sta->op_mode_bw);
+ /* also limit to RX OMI bandwidth we TX to the STA */
+ bw = min(bw, link_sta->rx_omi_bw_tx);
+
+ /* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
+ * IEEE80211-2016 specification makes higher bandwidth operation
+ * possible on the TDLS link if the peers have wider bandwidth
+ * capability.
+ *
+ * However, in this case, and only if the TDLS peer is authorized,
+ * limit to the tdls_chandef so that the configuration here isn't
+ * wider than what's actually requested on the channel context.
+ */
+ if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
+ test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
+ test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
+ sta->tdls_chandef.chan)
+ bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
+ else
+ bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
+
+ return bw;
+}
+
+/**
+ * ieee80211_sta_current_bw - get STA's current usable bandwidth
+ * @link_sta: the (link) STA to get the bandwidth for
+ * @chandef: the chandef for the channel the STA is on
+ * @direction: the direction (to or from STA)
+ *
+ * Return: the maximum bandwidth that the station can/may
+ * (currently) use in the given direction
+ */
+enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef,
+ enum ieee80211_sta_bw_direction direction)
+{
+ if (WARN_ON(!chandef))
+ return IEEE80211_STA_RX_BW_20;
+
+ switch (direction) {
+ case IEEE80211_STA_BW_RX_FROM_STA:
+ return ieee80211_sta_current_bw_rx_from_sta(link_sta, chandef);
+ case IEEE80211_STA_BW_TX_TO_STA:
+ return ieee80211_sta_current_bw_tx_to_sta(link_sta, chandef);
+ }
+
+ /* unreachable */
+ return IEEE80211_STA_RX_BW_20;
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index f0497d7d488a..39608a0abbb5 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -504,7 +504,7 @@ struct ieee80211_fragment_cache {
* @status_stats.last_ack_signal: last ACK signal
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
- * @cur_max_bandwidth: dynamic bandwidth limit for the station,
+ * @op_mode_bw: dynamic bandwidth limit to transmit to the STA,
* taken from HT/VHT capabilities or VHT operating mode notification.
* Invalid for NAN since that is operating on multiple bands.
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
@@ -558,7 +558,7 @@ struct link_sta_info {
u64 msdu[IEEE80211_NUM_TIDS + 1];
} tx_stats;
- enum ieee80211_sta_rx_bandwidth cur_max_bandwidth;
+ enum ieee80211_sta_rx_bandwidth op_mode_bw;
enum ieee80211_sta_rx_bandwidth rx_omi_bw_rx,
rx_omi_bw_tx,
rx_omi_bw_staging;
@@ -1005,6 +1005,16 @@ void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
void __ieee80211_sta_recalc_aggregates(struct sta_info *sta, u16 active_links);
+enum ieee80211_sta_bw_direction {
+ IEEE80211_STA_BW_RX_FROM_STA,
+ IEEE80211_STA_BW_TX_TO_STA,
+};
+
+enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_current_bw(struct link_sta_info *link_sta,
+ struct cfg80211_chan_def *chandef,
+ enum ieee80211_sta_bw_direction direction);
+
enum sta_stats_type {
STA_STATS_RATE_TYPE_INVALID = 0,
STA_STATS_RATE_TYPE_LEGACY,
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index dcb5fe98ec55..ffd575a8d188 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -314,7 +314,8 @@ ieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
enum nl80211_chan_width max_width;
int i;
- switch (ieee80211_sta_cap_rx_bw(&sta->deflink, &uc)) {
+ switch (ieee80211_sta_current_bw(&sta->deflink, &uc,
+ IEEE80211_STA_BW_RX_FROM_STA)) {
case IEEE80211_STA_RX_BW_20:
max_width = NL80211_CHAN_WIDTH_20;
break;
@@ -1337,8 +1338,9 @@ static void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_sta_rx_bandwidth bw;
bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
- bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink,
- &conf->def));
+ bw = min(bw, ieee80211_sta_current_bw(&sta->deflink,
+ &conf->def,
+ IEEE80211_STA_BW_RX_FROM_STA));
if (bw != sta->sta.deflink.bandwidth) {
sta->sta.deflink.bandwidth = bw;
rate_control_rate_update(local, sband,
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index 3df5f6c3f777..92a4e0450593 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -331,129 +331,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
}
-/* FIXME: move this to some better location - parses HE/EHT now */
-static enum ieee80211_sta_rx_bandwidth
-_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta, enum nl80211_band band)
-{
- struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
- struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
- struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
- u32 cap_width;
-
- if (he_cap->has_he) {
- u8 info;
-
- if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
- info = eht_cap->eht_cap_elem.phy_cap_info[0];
-
- if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
- return IEEE80211_STA_RX_BW_320;
- }
-
- info = he_cap->he_cap_elem.phy_cap_info[0];
-
- if (band == NL80211_BAND_2GHZ) {
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
- return IEEE80211_STA_RX_BW_40;
- return IEEE80211_STA_RX_BW_20;
- }
-
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
- info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
- return IEEE80211_STA_RX_BW_160;
-
- if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
- return IEEE80211_STA_RX_BW_80;
-
- return IEEE80211_STA_RX_BW_20;
- }
-
- if (!vht_cap->vht_supported)
- return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
- IEEE80211_STA_RX_BW_40 :
- IEEE80211_STA_RX_BW_20;
-
- cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
-
- if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
- cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
- return IEEE80211_STA_RX_BW_160;
-
- /*
- * If this is non-zero, then it does support 160 MHz after all,
- * in one form or the other. We don't distinguish here (or even
- * above) between 160 and 80+80 yet.
- */
- if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
- return IEEE80211_STA_RX_BW_160;
-
- return IEEE80211_STA_RX_BW_80;
-}
-
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
-{
- /*
- * With RX OMI, also pretend that the STA's capability changed.
- * Of course this isn't really true, it didn't change, only our
- * RX capability was changed by notifying RX OMI to the STA.
- * The purpose, however, is to save power, and that requires
- * changing also transmissions to the AP and the chanctx. The
- * transmissions depend on link_sta->bandwidth which is set in
- * ieee80211_sta_cur_vht_bw() below, but the chanctx depends
- * on the result of this function which is also called by
- * ieee80211_sta_cur_vht_bw(), so we need to do that here as
- * well. This is sufficient for the steady state, but during
- * the transition we already need to change TX/RX separately,
- * so ieee80211_sta_cur_vht_bw() below applies the _tx one.
- */
- return min(_ieee80211_sta_cap_rx_bw(link_sta, chandef->chan->band),
- link_sta->rx_omi_bw_rx);
-}
-
-/* FIXME: rename/move - this deals with everything not just VHT */
-enum ieee80211_sta_rx_bandwidth
-ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
- struct cfg80211_chan_def *chandef)
-{
- struct sta_info *sta = link_sta->sta;
- enum nl80211_chan_width bss_width;
- enum ieee80211_sta_rx_bandwidth bw;
- enum nl80211_band band;
-
- if (WARN_ON(!chandef))
- return IEEE80211_STA_RX_BW_20;
-
- bss_width = chandef->width;
- band = chandef->chan->band;
-
- /* intentionally do not take rx_bw_omi_rx into account */
- bw = _ieee80211_sta_cap_rx_bw(link_sta, band);
- bw = min(bw, link_sta->cur_max_bandwidth);
- /* but do apply rx_omi_bw_tx */
- bw = min(bw, link_sta->rx_omi_bw_tx);
-
- /* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
- * IEEE80211-2016 specification makes higher bandwidth operation
- * possible on the TDLS link if the peers have wider bandwidth
- * capability.
- *
- * However, in this case, and only if the TDLS peer is authorized,
- * limit to the tdls_chandef so that the configuration here isn't
- * wider than what's actually requested on the channel context.
- */
- if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
- test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
- test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
- sta->tdls_chandef.chan)
- bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
- else
- bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
-
- return bw;
-}
-
u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta,
u8 opmode, enum nl80211_band band)
@@ -496,25 +373,26 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
switch (opmode & IEEE80211_OPMODE_NOTIF_CHANWIDTH_MASK) {
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_20MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_20;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_20;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_40MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_40;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_40;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_80MHZ:
if (opmode & IEEE80211_OPMODE_NOTIF_BW_160_80P80)
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
else
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_80;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_160MHZ:
/* legacy only, no longer used by newer spec */
- link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+ link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
break;
}
- new_bw = ieee80211_sta_cur_vht_bw(link_sta, &link->conf->chanreq.oper);
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
if (new_bw != link_sta->pub->bandwidth) {
link_sta->pub->bandwidth = new_bw;
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH 20/20] wifi: mac80211: fix per-station PHY capability bandwidth
2026-04-15 12:41 [PATCH 00/20] wifi: mac80211: clean up and fix per-STA BW handling Johannes Berg
` (18 preceding siblings ...)
2026-04-15 12:42 ` [PATCH 19/20] wifi: mac80211: clarify per-STA bandwidth handling Johannes Berg
@ 2026-04-15 12:42 ` Johannes Berg
19 siblings, 0 replies; 27+ messages in thread
From: Johannes Berg @ 2026-04-15 12:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit
From: Johannes Berg <johannes.berg@intel.com>
When a (link) station connected to an AP interface is not
capable of EHT, it's possible that the AP interface is in
160 MHz but the HE channel is narrower, e.g. when EHT has
puncturing. In this case, the code doesn't correctly set
the STAs bandwidth, the station might be capable of using
160 MHz, but it can't use EHT 160 MHz with puncturing, so
it must be set to narrower.
Track the AP's 'he_and_lower_bw' bandwidth, use that when
calculating the maximum bandwidth to transmit to/from any
station not capable of EHT, and update all stations and
the chanctx min_def when it changes.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 116 +++++++++++++++++++++++++++++++++++++
net/mac80211/chan.c | 9 +--
net/mac80211/ieee80211_i.h | 4 ++
net/mac80211/sta_info.c | 47 ++++++++++++++-
4 files changed, 170 insertions(+), 6 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index bc4a8e5ad039..00261bd6674b 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1312,6 +1312,120 @@ ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst,
return offset;
}
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
+{
+ const struct ieee80211_vht_operation *vht_oper = params->vht_oper;
+ int ccfs0, ccfs1;
+
+ if (params->he_oper) {
+ const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
+
+ if (params->he_oper->he_oper_params &
+ cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO))
+ vht_oper = (void *)params->he_oper->optional;
+
+ he_6ghz_oper = ieee80211_he_6ghz_oper(params->he_oper);
+ if (he_6ghz_oper) {
+ switch (u8_get_bits(he_6ghz_oper->control,
+ IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH)) {
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_20MHZ:
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_40MHZ:
+ return IEEE80211_STA_RX_BW_40;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_80MHZ:
+ return IEEE80211_STA_RX_BW_80;
+ case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ:
+ return IEEE80211_STA_RX_BW_160;
+ }
+ }
+ }
+
+ if (vht_oper) {
+ switch (vht_oper->chan_width) {
+ case IEEE80211_VHT_CHANWIDTH_USE_HT:
+ /* check for HT (or fall down to 20) below */
+ break;
+ case IEEE80211_VHT_CHANWIDTH_160MHZ:
+ case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+ /* deprecated encodings */
+ return IEEE80211_STA_RX_BW_160;
+ case IEEE80211_VHT_CHANWIDTH_80MHZ:
+ /*
+ * See IEEE 802.11-2020 Table 9-352-BSS bandwidth
+ * when the VHT Operation Information field Channel
+ * Width subfield is 1
+ *
+ * (IEEE80211_VHT_CHANWIDTH_80MHZ == 1)
+ */
+ ccfs0 = vht_oper->center_freq_seg0_idx;
+ ccfs1 = vht_oper->center_freq_seg1_idx;
+ if (!ccfs0)
+ return IEEE80211_STA_RX_BW_80;
+ if (ccfs1 && abs(ccfs1 - ccfs0) == 8)
+ return IEEE80211_STA_RX_BW_160;
+ /* 80+80 - RX BW doesn't cover that / uses 160 */
+ if (ccfs1 && abs(ccfs1 - ccfs0) > 16)
+ return IEEE80211_STA_RX_BW_160;
+ fallthrough;
+ default:
+ /* reserved encoding - assume 80 */
+ return IEEE80211_STA_RX_BW_80;
+ }
+ }
+
+ if (params->ht_oper) {
+ switch (u8_get_bits(params->ht_oper->ht_param,
+ IEEE80211_HT_PARAM_CHA_SEC_OFFSET)) {
+ case IEEE80211_HT_PARAM_CHA_SEC_NONE:
+ default: /* invalid values */
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+ case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+ return IEEE80211_STA_RX_BW_40;
+ }
+ }
+
+ /* nothing found, must be 20 MHz */
+ return IEEE80211_STA_RX_BW_20;
+}
+
+static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
+ struct cfg80211_beacon_data *params)
+{
+ struct ieee80211_local *local = link->sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+
+ /*
+ * Updating the beacon might, without even changing the channel, cause
+ * the usable bandwidth for some stations to be changed, for example
+ * if the beacon configuration is EHT with 160 MHz, HE could change
+ * between 20, 40, 80 and 160 MHz, and HE (or lower) clients need to
+ * be handled accordingly.
+ * Calculate the HE and lower bandwidth and apply that to all stations.
+ *
+ * In the future, this also needs to calculate EHT bandwidth and apply
+ * it to all stations not using UHR DBE, since the chandef would then
+ * include DBE.
+ */
+
+ if (link->conf->chanreq.oper.chan->band == NL80211_BAND_S1GHZ)
+ return;
+
+ link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params);
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+
+ /*
+ * Note: this relies on ieee80211_recalc_chanctx_min_def() having
+ * the side effect of updating all stations, if they changed; that
+ * was normally for when the chandef changed but is used here too.
+ */
+ ieee80211_recalc_chanctx_min_def(local, chanctx);
+}
+
static int
ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
@@ -1450,6 +1564,8 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
if (old)
kfree_rcu(old, rcu_head);
+ ieee80211_update_ap_bandwidth(link, params);
+
*changed |= _changed;
return 0;
}
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index ecc0123ed448..248531051a4e 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -718,6 +718,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
* recalc the min required chan width of the channel context, which is
* the max of min required widths of all the interfaces bound to this
* channel context.
+ *
+ * Note: ieee80211_update_ap_bandwidth() relies on this iterating all
+ * affected stations, even if min_def didn't change.
*/
static void
_ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
@@ -728,13 +731,11 @@ _ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
u32 changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
check_reserved);
- if (!changed)
- return;
-
/* check is BW narrowed */
ieee80211_chan_bw_change(local, ctx, false, true);
- drv_change_chanctx(local, ctx, changed);
+ if (changed)
+ drv_change_chanctx(local, ctx, changed);
/* check is BW wider */
ieee80211_chan_bw_change(local, ctx, false, false);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index f1bab633697e..e23e347f870d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1133,6 +1133,10 @@ struct ieee80211_link_data {
struct ieee80211_bss_conf *conf;
+ struct {
+ enum ieee80211_sta_rx_bandwidth he_and_lower;
+ } bss_bw;
+
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index ec043d88a6a9..aba2fabfe0db 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3623,6 +3623,49 @@ ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
return IEEE80211_STA_RX_BW_80;
}
+/**
+ * ieee80211_sta_usable_bw - get STA's usable bandwidth capability
+ * @link_sta: the (link) STA to get the capability for
+ * @band: the band to get the capability on
+ *
+ * If the STA is on an AP interface, take into account the AP's
+ * bandwidth corresponding to this station's PHY capability
+ *
+ * Return: the maximum bandwidth supported by the STA on the
+ * connection to the interface it's connected to
+ */
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
+ enum nl80211_band band)
+{
+ struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
+ enum ieee80211_sta_rx_bandwidth bw;
+ struct ieee80211_link_data *link;
+
+ bw = ieee80211_sta_bw_capability(link_sta, band);
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+ sdata = get_bss_sdata(sdata);
+
+ /* for a STA to exist on VLAN, it must have AP */
+ if (WARN_ON(!sdata))
+ return IEEE80211_STA_RX_BW_20;
+ }
+
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ return bw;
+
+ /* for a link STA to exist, vif must have the link */
+ link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
+ if (WARN_ON(!link))
+ return IEEE80211_STA_RX_BW_20;
+
+ if (link_sta->pub->eht_cap.has_eht)
+ return bw;
+
+ return min(bw, link->bss_bw.he_and_lower);
+}
+
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
@@ -3637,7 +3680,7 @@ ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
* at a higher bandwidth first while reducing bandwidth, and
* change the chanctx only after the peer accepts, etc.)
*/
- return min(ieee80211_sta_bw_capability(link_sta, chandef->chan->band),
+ return min(ieee80211_sta_usable_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
}
@@ -3653,7 +3696,7 @@ ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
bss_width = chandef->width;
band = chandef->chan->band;
- bw = ieee80211_sta_bw_capability(link_sta, band);
+ bw = ieee80211_sta_usable_bw(link_sta, band);
bw = min(bw, link_sta->op_mode_bw);
/* also limit to RX OMI bandwidth we TX to the STA */
bw = min(bw, link_sta->rx_omi_bw_tx);
--
2.53.0
^ permalink raw reply related [flat|nested] 27+ messages in thread