* [RFC PATCH 17/19] wifi: nl80211: always validate AP operation/PHY regulatory
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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
* [RFC PATCH 08/19] wifi: mac80211: clean up STA NSS handling
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
Signed-off-by: Johannes Berg <johannes.berg@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 1e1ab25d9d8d..b65f090a35dc 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 f8f728619249..a683a79b7dcc 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2293,7 +2293,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 803106fc3134..99c666fb2d17 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 7fc5616cb244..3a0fd3dc976c 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
@@ -10620,7 +10622,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 b3a016f3736b..31cf45095c60 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3401,6 +3401,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 92ba477e7c37..9c827199f949 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -996,6 +996,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 a72cf532bec7..63aa64b77ca0 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -475,99 +475,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
* [RFC PATCH 07/19] wifi: mac80211: simplify ieee80211_sta_rx_bw_to_chan_width()
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 57e8f8db3d9d..606d768b62cb 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -405,33 +405,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 d951a4302e9a..614fa7a9d027 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -629,8 +629,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 24ff9073b3ff..f8f728619249 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2307,8 +2307,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
@@ -2694,6 +2692,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 8987a4504520..b4caa31f6359 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3764,6 +3764,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 8a2ebbbc1807..a72cf532bec7 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -425,38 +425,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,
@@ -658,7 +626,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
* [RFC PATCH 16/19] wifi: cfg80211: provide HT/VHT operation for AP beacon
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 f3e46f6d7410..4705c53bd228 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
* [RFC PATCH 05/19] wifi: mac80211: remove ieee80211_sta_cap_chan_bw()
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 bacb49ad2817..24ff9073b3ff 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2294,8 +2294,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 148a4cde5ec7..8a2ebbbc1807 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -425,30 +425,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
* [RFC PATCH 15/19] wifi: nl80211: reject too short HT/VHT/HE/EHT capability/operation
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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
* [RFC PATCH 06/19] wifi: nl80211: document channel opmode change channel width
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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
* [RFC PATCH 14/19] wifi: cfg80211: move AP HT/VHT/... operation to beacon info
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 17 +++---
net/mac80211/cfg.c | 10 ++--
net/wireless/nl80211.c | 117 ++++++++++++++++++++++++++---------------
3 files changed, 89 insertions(+), 55 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8bebf45af95d..f3e46f6d7410 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,11 @@ 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 +1567,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 4011d9d1ffcc..88057efae8e3 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1539,13 +1539,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;
@@ -1594,7 +1594,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;
@@ -1602,7 +1602,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
* [RFC PATCH 04/19] wifi: mac80211: use chandef in TDLS chanctx handling
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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().
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
* [RFC PATCH 13/19] wifi: nl80211: reject beacons with bad HE operation
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/nl80211.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 18fbe6c78e82..7a1c9faef443 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6673,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
* [RFC PATCH 01/19] wifi: mac80211: set cur_max_bandwidth to maximum
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 93e0342cff4f..70da246f8a2d 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -155,7 +155,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 410e2354f33a..27b5578601ff 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -280,10 +280,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) {
enum ieee80211_smps_mode smps_mode;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 7923ee9eafab..b3a016f3736b 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 58ccbea7f6f6..92ba477e7c37 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
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
* @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index a6570781740a..148a4cde5ec7 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -297,29 +297,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
* [RFC PATCH 03/19] wifi: mac80211: use chandef in ieee80211_get_sta_bw()
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 dd99fdc1ea9d..57e8f8db3d9d 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -383,11 +383,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]);
@@ -402,7 +403,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:
@@ -437,7 +438,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;
@@ -448,7 +448,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
* [RFC PATCH 12/19] wifi: cfg80211: remove HE/SAE H2E required fields
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 4 +---
net/wireless/nl80211.c | 4 ----
2 files changed, 1 insertion(+), 7 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;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index f334cdef8958..18fbe6c78e82 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;
}
}
--
2.53.0
^ permalink raw reply related
* [RFC PATCH 02/19] wifi: mac80211: use max BW for HT channel width update
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 27b5578601ff..d951a4302e9a 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -618,7 +618,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
* [RFC PATCH 11/19] wifi: mac80211: remove ieee80211_sta_cur_vht_bw()
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 | 36 +++++++++++++++---------------------
7 files changed, 29 insertions(+), 35 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 6beebbc93c3c..62f6d7f4be0d 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -626,8 +626,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 028c5a8459b3..2f7f7a834da6 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -269,7 +269,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 d5528fbc5fbe..f0d5c9363a5f 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -581,9 +581,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
@@ -591,7 +596,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 8a5640cdf298..29f51ec142ed 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2281,13 +2281,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 06300cf1c8ec..3cd5fe11756f 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 51d5618bd3f9..9ae32d601b61 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3494,7 +3494,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 f8992d71fd19..3e3efabe9149 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -397,12 +397,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);
@@ -410,30 +410,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;
- 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);
@@ -467,9 +456,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;
@@ -516,7 +510,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
* [RFC PATCH 10/19] wifi: mac80211: clean up ieee80211_sta_cap_rx_bw()
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 | 28 ++++++++--------------------
4 files changed, 14 insertions(+), 31 deletions(-)
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 606d768b62cb..6beebbc93c3c 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -403,7 +403,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 a683a79b7dcc..8a5640cdf298 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2278,13 +2278,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 1bde9adac5a6..f8992d71fd19 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -329,31 +329,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;
-
- 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];
@@ -402,8 +387,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.
@@ -419,7 +404,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);
}
@@ -431,9 +416,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;
@@ -444,11 +431,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
* [RFC PATCH 09/19] wifi: mac80211: clean up initial STA NSS/bandwidth handling
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260327093659.711584-21-johannes@sipsolutions.net>
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.
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 | 31 -------------------------------
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(+), 64 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index b6163dcc7e92..4011d9d1ffcc 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2176,7 +2176,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 70da246f8a2d..028c5a8459b3 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -155,8 +155,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 614fa7a9d027..d5528fbc5fbe 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));
@@ -252,34 +249,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
- 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) {
enum ieee80211_smps_mode smps_mode;
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index b65f090a35dc..41572eaf6f44 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 99c666fb2d17..502304747d4e 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 3a0fd3dc976c..06300cf1c8ec 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 31cf45095c60..51d5618bd3f9 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3401,7 +3401,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;
@@ -3484,13 +3484,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 9c827199f949..46e133dafc61 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -996,7 +996,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 63aa64b77ca0..1bde9adac5a6 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -297,8 +297,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
* [RFC PATCH 00/19] wifi: mac80211: per-STA bandwidth cleanups/fixes
From: Johannes Berg @ 2026-03-27 9:31 UTC (permalink / raw)
To: linux-wireless
Hi,
First, I should say this is more of a rough draft. As I noted in my
pull request, I'll be mostly offline next week through April 6th,
and this was something I had discussed with folks about a week and
a half ago, and wanted to get out for everyone to look. But it's a
rough draft because I cherry-picked it from our tree which has some
additional work and had a bunch of conflicts, and I don't know if
now (in a hurry) I resolved it all correctly, and if it'll come in
before all that other work (e.g. the NAN work Miri already posted
has conflicts with this, and NAN work will probably go first.)
In discussions about a week and a half ago, we noticed that station
bandwidth handling is broken if we have an AP with EHT with e.g.
160 MHz with puncturing, making HE e.g. 80 MHz, but now a station
connects that has 160 MHz HE capability - we'll set it to have a
bandwidth of 160 MHz, which is clearly wrong.
This is what I'm currently trying to do to fix this, but I did a
lot of cleanups to get there, and also had to have the AP be more
strict so we can actually have the right information about the HT,
VHT and HE operation.
johannes
^ permalink raw reply
* Re: [PATCH ath-current] wifi: ath10k: fix station lookup failure during disconnect
From: Paul Menzel @ 2026-03-27 9:27 UTC (permalink / raw)
To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath10k
In-Reply-To: <3faa1fbb-afd8-4631-b3d1-ca81aaac8817@oss.qualcomm.com>
Dear Baochen,
Thank you for your reply.
Am 27.03.26 um 10:11 schrieb Baochen Qiang:
> On 3/27/2026 4:43 PM, Paul Menzel wrote:
>> Am 27.03.26 um 03:54 schrieb Baochen Qiang:
>>
>>> On 3/27/2026 7:31 AM, Paul Menzel wrote:
>>
>> […]
>>
>>>> Am 25.03.26 um 04:05 schrieb Baochen Qiang:
>>>>> Recent commit [1] moved station statistics collection to an earlier stage
>>>>> of the disconnect flow. With this change in place, ath10k fails to resolve
>>>>> the station entry when handling a peer stats event triggered during
>>>>> disconnect, resulting in log messages such as:
>>>>>
>>>>> wlp58s0: deauthenticating from 74:1a:e0:e7:b4:c8 by local choice (Reason: 3=DEAUTH_LEAVING)
>>>>> ath10k_pci 0000:3a:00.0: not found station for peer stats
>>>>> ath10k_pci 0000:3a:00.0: failed to parse stats info tlv: -22
>>>>>
>>>>> The failure occurs because ath10k relies on ieee80211_find_sta_by_ifaddr()
>>>>> for station lookup. That function uses local->sta_hash, but by the time
>>>>> the peer stats request is triggered during disconnect, mac80211 has
>>>>> already removed the station from that hash table, leading to lookup
>>>>> failure.
>>>>>
>>>>> Before commit [1], this issue was not visible because the transition from
>>>>> IEEE80211_STA_NONE to IEEE80211_STA_NOTEXIST prevented ath10k from sending
>>>>> a peer stats request at all: ath10k_mac_sta_get_peer_stats_info() would
>>>>> fail early to find the peer and skip requesting statistics.
>>>>>
>>>>> Fix this by switching the lookup path to ath10k_peer_find(), which queries
>>>>> ath10k's internal peer table. At the point where the firmware emits the
>>>>> peer stats event, the peer entry is still present in the driver's list,
>>>>> ensuring lookup succeeds.
>>>>
>>>> Out of curiosity, how can the statistics be printed?
>>>
>>> not quite understand your question, can you be more detailed?
>>
>> The commit message starts with:
>>
>> “… moved station statistics collection to an earlier stage …”
>>
>> I was wondering how to print/dump these statistics.
>
> still not quite understand, but let me try to answer:
>
> mac80211 collects stats and send it to userspace over nl80211,
> userpsace tools like iw can print it out.
Thank you. It looks like, one way is `sudo iw dev wlp58s0 station dump`.
>>>>> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1
>>>>>
>>>>> Fixes: a203dbeeca15 ("wifi: mac80211: collect station statistics earlier when disconnect") # [1]
>>>>> Reported-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>>>> Closes: https://lore.kernel.org/ath10k/57671b89-ec9f-4e6c-992c-45eb8e75929c@molgen.mpg.de
>>>>> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>>>>> ---
>>>>> drivers/net/wireless/ath/ath10k/wmi-tlv.c | 26 +++++++++++++++-----------
>>>>> 1 file changed, 15 insertions(+), 11 deletions(-)
>>>>>
>>>>> diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/
>>>>> ath10k/wmi-tlv.c
>>>>> index ec8e91707f84..01f2d1fa9d7d 100644
>>>>> --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>>>> +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>>>> @@ -3,7 +3,7 @@
>>>>> * Copyright (c) 2005-2011 Atheros Communications Inc.
>>>>> * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
>>>>> * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
>>>>> - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
>>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>>>> */
>>>>> #include "core.h"
>>>>> #include "debug.h"
>>>>> @@ -14,6 +14,7 @@
>>>>> #include "wmi-tlv.h"
>>>>> #include "p2p.h"
>>>>> #include "testmode.h"
>>>>> +#include "txrx.h"
>>>>> #include <linux/bitfield.h>
>>>>> /***************/
>>>>> @@ -224,8 +225,9 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar,
>>>>> u16 tag, u16
>>>>> const void *ptr, void *data)
>>>>> {
>>>>> const struct wmi_tlv_peer_stats_info *stat = ptr;
>>>>> - struct ieee80211_sta *sta;
>>>>> + u32 vdev_id = *(u32 *)data;
>>>>> struct ath10k_sta *arsta;
>>>>> + struct ath10k_peer *peer;
>>>>> if (tag != WMI_TLV_TAG_STRUCT_PEER_STATS_INFO)
>>>>> return -EPROTO;
>>>>> @@ -241,20 +243,20 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k
>>>>> *ar, u16 tag, u16
>>>>> __le32_to_cpu(stat->last_tx_rate_code),
>>>>> __le32_to_cpu(stat->last_tx_bitrate_kbps));
>>>>> - rcu_read_lock();
>>>>> - sta = ieee80211_find_sta_by_ifaddr(ar->hw, stat->peer_macaddr.addr, NULL);
>>>>> - if (!sta) {
>>>>> - rcu_read_unlock();
>>>>> - ath10k_warn(ar, "not found station for peer stats\n");
>>>>> + guard(spinlock_bh)(&ar->data_lock);
>>>>> +
>>>>> + peer = ath10k_peer_find(ar, vdev_id, stat->peer_macaddr.addr);
>>>>> + if (!peer || !peer->sta) {
>>>>> + ath10k_warn(ar, "not found %s with vdev id %u mac addr %pM for peer stats\n",
>>>>> + peer ? "sta" : "peer", vdev_id, stat->peer_macaddr.addr);
>>>>> return -EINVAL;
>>>>> }
>>>>> - arsta = (struct ath10k_sta *)sta->drv_priv;
>>>>> + arsta = (struct ath10k_sta *)peer->sta->drv_priv;
>>>>> arsta->rx_rate_code = __le32_to_cpu(stat->last_rx_rate_code);
>>>>> arsta->rx_bitrate_kbps = __le32_to_cpu(stat->last_rx_bitrate_kbps);
>>>>> arsta->tx_rate_code = __le32_to_cpu(stat->last_tx_rate_code);
>>>>> arsta->tx_bitrate_kbps = __le32_to_cpu(stat->last_tx_bitrate_kbps);
>>>>> - rcu_read_unlock();
>>>>> return 0;
>>>>> }
>>>>> @@ -266,6 +268,7 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>>>>> const struct wmi_tlv_peer_stats_info_ev *ev;
>>>>> const void *data;
>>>>> u32 num_peer_stats;
>>>>> + u32 vdev_id;
>>>>> int ret;
>>>>> tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
>>>>> @@ -284,15 +287,16 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>>>>> }
>>>>> num_peer_stats = __le32_to_cpu(ev->num_peers);
>>>>> + vdev_id = __le32_to_cpu(ev->vdev_id);
>>>>> ath10k_dbg(ar, ATH10K_DBG_WMI,
>>>>> "wmi tlv peer stats info update peer vdev id %d peers %i more data %d\n",
>>>>> - __le32_to_cpu(ev->vdev_id),
>>>>> + vdev_id,
>>>>> num_peer_stats,
>>>>> __le32_to_cpu(ev->more_data));
>>>>> ret = ath10k_wmi_tlv_iter(ar, data, ath10k_wmi_tlv_len(data),
>>>>> - ath10k_wmi_tlv_parse_peer_stats_info, NULL);
>>>>> + ath10k_wmi_tlv_parse_peer_stats_info, &vdev_id);
>>>>> if (ret)
>>>>> ath10k_warn(ar, "failed to parse stats info tlv: %d\n", ret);
>>>>
>>>> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>>> Tested-by: Paul Menzel <pmenzel@molgen.mpg.de>
Kind regards,
Paul
PS: For the record:
```
$ sudo iw dev wlp58s0 station dump
Station 8c:6a:8d:xx:xx:xx (on wlp58s0)
authorized: yes
authenticated: yes
associated: yes
preamble: long
WMM/WME: yes
MFP: no
TDLS peer: no
inactive time: 136 ms
rx bytes: 42923460
rx packets: 30379
tx bytes: 527970
tx packets: 4543
tx retries: 0
tx failed: 0
beacon loss: 0
beacon rx: 254
rx drop misc: 104
signal: -35 [-43, -39] dBm
signal avg: -35 [-44, -37] dBm
beacon signal avg: -35 dBm
tx bitrate: 144.4 MBit/s MCS 15 short GI
tx duration: 1386481 us
rx bitrate: 144.4 MBit/s MCS 15 short GI
rx duration: 0 us
airtime weight: 256
DTIM period: 1
beacon interval:100
short slot time:yes
connected time: 61 seconds
associated at [boottime]: 6663.920s
associated at: 1774603392069 ms
current time: 1774603452468 ms
```
^ permalink raw reply
* Re: [PATCH ath-current] wifi: ath10k: fix station lookup failure during disconnect
From: Baochen Qiang @ 2026-03-27 9:11 UTC (permalink / raw)
To: Paul Menzel, Jeff Johnson; +Cc: linux-wireless, ath10k
In-Reply-To: <4c8c17c1-424c-4a7b-af68-81c099497bd7@molgen.mpg.de>
On 3/27/2026 4:43 PM, Paul Menzel wrote:
> Dear Baochen,
>
>
> Am 27.03.26 um 03:54 schrieb Baochen Qiang:
>
>> On 3/27/2026 7:31 AM, Paul Menzel wrote:
>
> […]
>
>>> Am 25.03.26 um 04:05 schrieb Baochen Qiang:
>>>> Recent commit [1] moved station statistics collection to an earlier stage
>>>> of the disconnect flow. With this change in place, ath10k fails to resolve
>>>> the station entry when handling a peer stats event triggered during
>>>> disconnect, resulting in log messages such as:
>>>>
>>>> wlp58s0: deauthenticating from 74:1a:e0:e7:b4:c8 by local choice (Reason:
>>>> 3=DEAUTH_LEAVING)
>>>> ath10k_pci 0000:3a:00.0: not found station for peer stats
>>>> ath10k_pci 0000:3a:00.0: failed to parse stats info tlv: -22
>>>>
>>>> The failure occurs because ath10k relies on ieee80211_find_sta_by_ifaddr()
>>>> for station lookup. That function uses local->sta_hash, but by the time
>>>> the peer stats request is triggered during disconnect, mac80211 has
>>>> already removed the station from that hash table, leading to lookup
>>>> failure.
>>>>
>>>> Before commit [1], this issue was not visible because the transition from
>>>> IEEE80211_STA_NONE to IEEE80211_STA_NOTEXIST prevented ath10k from sending
>>>> a peer stats request at all: ath10k_mac_sta_get_peer_stats_info() would
>>>> fail early to find the peer and skip requesting statistics.
>>>>
>>>> Fix this by switching the lookup path to ath10k_peer_find(), which queries
>>>> ath10k's internal peer table. At the point where the firmware emits the
>>>> peer stats event, the peer entry is still present in the driver's list,
>>>> ensuring lookup succeeds.
>>>
>>> Out of curiosity, how can the statistics be printed?
>>
>> not quite understand your question, can you be more detailed?
>
> The commit message starts with:
>
> “… moved station statistics collection to an earlier stage …”
>
> I was wondering how to print/dump these statistics.
still not quite understand, but let me try to answer:
mac80211 collects stats and send it to userspace over nl80211, userpsace tools like iw can
print it out.
>
>
> Kind regards,
>
> Paul
>
>
>>>> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1
>>>>
>>>> Fixes: a203dbeeca15 ("wifi: mac80211: collect station statistics earlier when
>>>> disconnect") # [1]
>>>> Reported-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>>> Closes: https://lore.kernel.org/ath10k/57671b89-ec9f-4e6c-992c-45eb8e75929c@molgen.mpg.de
>>>> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>>>> ---
>>>> drivers/net/wireless/ath/ath10k/wmi-tlv.c | 26 +++++++++++++++-----------
>>>> 1 file changed, 15 insertions(+), 11 deletions(-)
>>>>
>>>> diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/
>>>> ath10k/wmi-tlv.c
>>>> index ec8e91707f84..01f2d1fa9d7d 100644
>>>> --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>>> +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>>> @@ -3,7 +3,7 @@
>>>> * Copyright (c) 2005-2011 Atheros Communications Inc.
>>>> * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
>>>> * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
>>>> - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
>>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>>> */
>>>> #include "core.h"
>>>> #include "debug.h"
>>>> @@ -14,6 +14,7 @@
>>>> #include "wmi-tlv.h"
>>>> #include "p2p.h"
>>>> #include "testmode.h"
>>>> +#include "txrx.h"
>>>> #include <linux/bitfield.h>
>>>> /***************/
>>>> @@ -224,8 +225,9 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar,
>>>> u16 tag, u16
>>>> const void *ptr, void *data)
>>>> {
>>>> const struct wmi_tlv_peer_stats_info *stat = ptr;
>>>> - struct ieee80211_sta *sta;
>>>> + u32 vdev_id = *(u32 *)data;
>>>> struct ath10k_sta *arsta;
>>>> + struct ath10k_peer *peer;
>>>> if (tag != WMI_TLV_TAG_STRUCT_PEER_STATS_INFO)
>>>> return -EPROTO;
>>>> @@ -241,20 +243,20 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k
>>>> *ar, u16 tag, u16
>>>> __le32_to_cpu(stat->last_tx_rate_code),
>>>> __le32_to_cpu(stat->last_tx_bitrate_kbps));
>>>> - rcu_read_lock();
>>>> - sta = ieee80211_find_sta_by_ifaddr(ar->hw, stat->peer_macaddr.addr, NULL);
>>>> - if (!sta) {
>>>> - rcu_read_unlock();
>>>> - ath10k_warn(ar, "not found station for peer stats\n");
>>>> + guard(spinlock_bh)(&ar->data_lock);
>>>> +
>>>> + peer = ath10k_peer_find(ar, vdev_id, stat->peer_macaddr.addr);
>>>> + if (!peer || !peer->sta) {
>>>> + ath10k_warn(ar, "not found %s with vdev id %u mac addr %pM for peer stats\n",
>>>> + peer ? "sta" : "peer", vdev_id, stat->peer_macaddr.addr);
>>>> return -EINVAL;
>>>> }
>>>> - arsta = (struct ath10k_sta *)sta->drv_priv;
>>>> + arsta = (struct ath10k_sta *)peer->sta->drv_priv;
>>>> arsta->rx_rate_code = __le32_to_cpu(stat->last_rx_rate_code);
>>>> arsta->rx_bitrate_kbps = __le32_to_cpu(stat->last_rx_bitrate_kbps);
>>>> arsta->tx_rate_code = __le32_to_cpu(stat->last_tx_rate_code);
>>>> arsta->tx_bitrate_kbps = __le32_to_cpu(stat->last_tx_bitrate_kbps);
>>>> - rcu_read_unlock();
>>>> return 0;
>>>> }
>>>> @@ -266,6 +268,7 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>>>> const struct wmi_tlv_peer_stats_info_ev *ev;
>>>> const void *data;
>>>> u32 num_peer_stats;
>>>> + u32 vdev_id;
>>>> int ret;
>>>> tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
>>>> @@ -284,15 +287,16 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k
>>>> *ar,
>>>> }
>>>> num_peer_stats = __le32_to_cpu(ev->num_peers);
>>>> + vdev_id = __le32_to_cpu(ev->vdev_id);
>>>> ath10k_dbg(ar, ATH10K_DBG_WMI,
>>>> "wmi tlv peer stats info update peer vdev id %d peers %i more data %d\n",
>>>> - __le32_to_cpu(ev->vdev_id),
>>>> + vdev_id,
>>>> num_peer_stats,
>>>> __le32_to_cpu(ev->more_data));
>>>> ret = ath10k_wmi_tlv_iter(ar, data, ath10k_wmi_tlv_len(data),
>>>> - ath10k_wmi_tlv_parse_peer_stats_info, NULL);
>>>> + ath10k_wmi_tlv_parse_peer_stats_info, &vdev_id);
>>>> if (ret)
>>>> ath10k_warn(ar, "failed to parse stats info tlv: %d\n", ret);
>>>
>>> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>> Tested-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>>
>>>
>>> Kind regards,
>>>
>>> Paul
^ permalink raw reply
* Re: [PATCH ath-current] wifi: ath10k: fix station lookup failure during disconnect
From: Paul Menzel @ 2026-03-27 8:43 UTC (permalink / raw)
To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath10k
In-Reply-To: <5033b613-3514-4686-895f-75bb8f523303@oss.qualcomm.com>
Dear Baochen,
Am 27.03.26 um 03:54 schrieb Baochen Qiang:
> On 3/27/2026 7:31 AM, Paul Menzel wrote:
[…]
>> Am 25.03.26 um 04:05 schrieb Baochen Qiang:
>>> Recent commit [1] moved station statistics collection to an earlier stage
>>> of the disconnect flow. With this change in place, ath10k fails to resolve
>>> the station entry when handling a peer stats event triggered during
>>> disconnect, resulting in log messages such as:
>>>
>>> wlp58s0: deauthenticating from 74:1a:e0:e7:b4:c8 by local choice (Reason: 3=DEAUTH_LEAVING)
>>> ath10k_pci 0000:3a:00.0: not found station for peer stats
>>> ath10k_pci 0000:3a:00.0: failed to parse stats info tlv: -22
>>>
>>> The failure occurs because ath10k relies on ieee80211_find_sta_by_ifaddr()
>>> for station lookup. That function uses local->sta_hash, but by the time
>>> the peer stats request is triggered during disconnect, mac80211 has
>>> already removed the station from that hash table, leading to lookup
>>> failure.
>>>
>>> Before commit [1], this issue was not visible because the transition from
>>> IEEE80211_STA_NONE to IEEE80211_STA_NOTEXIST prevented ath10k from sending
>>> a peer stats request at all: ath10k_mac_sta_get_peer_stats_info() would
>>> fail early to find the peer and skip requesting statistics.
>>>
>>> Fix this by switching the lookup path to ath10k_peer_find(), which queries
>>> ath10k's internal peer table. At the point where the firmware emits the
>>> peer stats event, the peer entry is still present in the driver's list,
>>> ensuring lookup succeeds.
>>
>> Out of curiosity, how can the statistics be printed?
>
> not quite understand your question, can you be more detailed?
The commit message starts with:
“… moved station statistics collection to an earlier stage …”
I was wondering how to print/dump these statistics.
Kind regards,
Paul
>>> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1
>>>
>>> Fixes: a203dbeeca15 ("wifi: mac80211: collect station statistics earlier when disconnect") # [1]
>>> Reported-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>> Closes: https://lore.kernel.org/ath10k/57671b89-ec9f-4e6c-992c-45eb8e75929c@molgen.mpg.de
>>> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>>> ---
>>> drivers/net/wireless/ath/ath10k/wmi-tlv.c | 26 +++++++++++++++-----------
>>> 1 file changed, 15 insertions(+), 11 deletions(-)
>>>
>>> diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/
>>> ath10k/wmi-tlv.c
>>> index ec8e91707f84..01f2d1fa9d7d 100644
>>> --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>> +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>>> @@ -3,7 +3,7 @@
>>> * Copyright (c) 2005-2011 Atheros Communications Inc.
>>> * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
>>> * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
>>> - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
>>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>>> */
>>> #include "core.h"
>>> #include "debug.h"
>>> @@ -14,6 +14,7 @@
>>> #include "wmi-tlv.h"
>>> #include "p2p.h"
>>> #include "testmode.h"
>>> +#include "txrx.h"
>>> #include <linux/bitfield.h>
>>> /***************/
>>> @@ -224,8 +225,9 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar, u16 tag, u16
>>> const void *ptr, void *data)
>>> {
>>> const struct wmi_tlv_peer_stats_info *stat = ptr;
>>> - struct ieee80211_sta *sta;
>>> + u32 vdev_id = *(u32 *)data;
>>> struct ath10k_sta *arsta;
>>> + struct ath10k_peer *peer;
>>> if (tag != WMI_TLV_TAG_STRUCT_PEER_STATS_INFO)
>>> return -EPROTO;
>>> @@ -241,20 +243,20 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar, u16 tag, u16
>>> __le32_to_cpu(stat->last_tx_rate_code),
>>> __le32_to_cpu(stat->last_tx_bitrate_kbps));
>>> - rcu_read_lock();
>>> - sta = ieee80211_find_sta_by_ifaddr(ar->hw, stat->peer_macaddr.addr, NULL);
>>> - if (!sta) {
>>> - rcu_read_unlock();
>>> - ath10k_warn(ar, "not found station for peer stats\n");
>>> + guard(spinlock_bh)(&ar->data_lock);
>>> +
>>> + peer = ath10k_peer_find(ar, vdev_id, stat->peer_macaddr.addr);
>>> + if (!peer || !peer->sta) {
>>> + ath10k_warn(ar, "not found %s with vdev id %u mac addr %pM for peer stats\n",
>>> + peer ? "sta" : "peer", vdev_id, stat->peer_macaddr.addr);
>>> return -EINVAL;
>>> }
>>> - arsta = (struct ath10k_sta *)sta->drv_priv;
>>> + arsta = (struct ath10k_sta *)peer->sta->drv_priv;
>>> arsta->rx_rate_code = __le32_to_cpu(stat->last_rx_rate_code);
>>> arsta->rx_bitrate_kbps = __le32_to_cpu(stat->last_rx_bitrate_kbps);
>>> arsta->tx_rate_code = __le32_to_cpu(stat->last_tx_rate_code);
>>> arsta->tx_bitrate_kbps = __le32_to_cpu(stat->last_tx_bitrate_kbps);
>>> - rcu_read_unlock();
>>> return 0;
>>> }
>>> @@ -266,6 +268,7 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>>> const struct wmi_tlv_peer_stats_info_ev *ev;
>>> const void *data;
>>> u32 num_peer_stats;
>>> + u32 vdev_id;
>>> int ret;
>>> tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
>>> @@ -284,15 +287,16 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>>> }
>>> num_peer_stats = __le32_to_cpu(ev->num_peers);
>>> + vdev_id = __le32_to_cpu(ev->vdev_id);
>>> ath10k_dbg(ar, ATH10K_DBG_WMI,
>>> "wmi tlv peer stats info update peer vdev id %d peers %i more data %d\n",
>>> - __le32_to_cpu(ev->vdev_id),
>>> + vdev_id,
>>> num_peer_stats,
>>> __le32_to_cpu(ev->more_data));
>>> ret = ath10k_wmi_tlv_iter(ar, data, ath10k_wmi_tlv_len(data),
>>> - ath10k_wmi_tlv_parse_peer_stats_info, NULL);
>>> + ath10k_wmi_tlv_parse_peer_stats_info, &vdev_id);
>>> if (ret)
>>> ath10k_warn(ar, "failed to parse stats info tlv: %d\n", ret);
>>
>> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
>> Tested-by: Paul Menzel <pmenzel@molgen.mpg.de>
>>
>>
>> Kind regards,
>>
>> Paul
^ permalink raw reply
* RE: [PATCH rtw-next v2] wifi: rtw88: Fill fw_version member of struct wiphy
From: Ping-Ke Shih @ 2026-03-27 6:23 UTC (permalink / raw)
To: Jeff Johnson, Bitterblue Smith, linux-wireless@vger.kernel.org
In-Reply-To: <2c90c306-f285-4ea7-b795-37a0f14ec27b@oss.qualcomm.com>
Jeff Johnson <jeff.johnson@oss.qualcomm.com> wrote:
>
> On 3/26/2026 8:21 AM, Bitterblue Smith wrote:
> > Let userspace tools like lshw show the firmware version by filling the
> > fw_version member of struct wiphy.
> >
> > Before:
> >
> > configuration: broadcast=yes driver=rtw88_8814au
> > driverversion=6.19.6-arch1-1 firmware=N/A link=no multicast=yes
> > wireless=IEEE 802.11
> >
> > After:
> >
> > configuration: broadcast=yes driver=rtw88_8814au
> > driverversion=6.19.6-arch1-1 firmware=33.6.0 link=no multicast=yes
> > wireless=IEEE 802.11
> >
> > Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
> > ---
> > v2:
> > - Use local wiphy variable.
> > ---
> > drivers/net/wireless/realtek/rtw88/main.c | 6 ++++++
> > 1 file changed, 6 insertions(+)
> >
> > diff --git a/drivers/net/wireless/realtek/rtw88/main.c
> b/drivers/net/wireless/realtek/rtw88/main.c
> > index c4f9758b4e96..cd9254370fcc 100644
> > --- a/drivers/net/wireless/realtek/rtw88/main.c
> > +++ b/drivers/net/wireless/realtek/rtw88/main.c
> > @@ -1805,6 +1805,7 @@ static void rtw_load_firmware_cb(const struct firmware
> *firmware, void *context)
> > {
> > struct rtw_fw_state *fw = context;
> > struct rtw_dev *rtwdev = fw->rtwdev;
> > + struct wiphy *wiphy = rtwdev->hw->wiphy;
> >
> > if (!firmware || !firmware->data) {
> > rtw_err(rtwdev, "failed to request firmware\n");
> > @@ -1819,6 +1820,11 @@ static void rtw_load_firmware_cb(const struct firmware
> *firmware, void *context)
> > rtw_info(rtwdev, "%sFirmware version %u.%u.%u, H2C version %u\n",
> > fw->type == RTW_WOWLAN_FW ? "WOW " : "",
> > fw->version, fw->sub_version, fw->sub_index,
> fw->h2c_version);
> > +
> > + if (fw->type == RTW_NORMAL_FW)
> > + snprintf(wiphy->fw_version, sizeof(wiphy->fw_version),
> > + "%u.%u.%u",
> > + fw->version, fw->sub_version, fw->sub_index);
> > }
> >
> > static int rtw_load_firmware(struct rtw_dev *rtwdev, enum rtw_fw_type type)
>
> generically speaking how useful is this?
> with MLO and multi-radio wiphy there can be multiple underlying hardware with
> different firmware versions. in recent ath drivers we are not filling this.
Thanks for the info.
Realtek WiFi has single one firmware version, I'd check firmware version by
kernel log personally. By commit message, Bitterblue uses lshw tool to read
the version, and I personally don't object this patch.
Ping-Ke
^ permalink raw reply
* [PATCH wireless-next] wifi: wilc1000: use kzalloc_flex
From: Rosen Penev @ 2026-03-27 3:06 UTC (permalink / raw)
To: linux-wireless; +Cc: Ajay Singh, Claudiu Beznea, open list
Because key is a flexible array member, kzalloc_flex can be used to
handle the math properly and simplify the code slightly.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/wireless/microchip/wilc1000/hif.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/microchip/wilc1000/hif.c b/drivers/net/wireless/microchip/wilc1000/hif.c
index 944b2a812b63..009c4770a6f9 100644
--- a/drivers/net/wireless/microchip/wilc1000/hif.c
+++ b/drivers/net/wireless/microchip/wilc1000/hif.c
@@ -1123,7 +1123,7 @@ int wilc_add_ptk(struct wilc_vif *vif, const u8 *ptk, u8 ptk_key_len,
wid_list[0].size = sizeof(char);
wid_list[0].val = (s8 *)&cipher_mode;
- key_buf = kzalloc(sizeof(*key_buf) + t_key_len, GFP_KERNEL);
+ key_buf = kzalloc_flex(*key_buf, key, t_key_len);
if (!key_buf)
return -ENOMEM;
@@ -1151,7 +1151,7 @@ int wilc_add_ptk(struct wilc_vif *vif, const u8 *ptk, u8 ptk_key_len,
struct wid wid;
struct wilc_sta_wpa_ptk *key_buf;
- key_buf = kzalloc(sizeof(*key_buf) + t_key_len, GFP_KERNEL);
+ key_buf = kzalloc_flex(*key_buf, key, t_key_len);
if (!key_buf)
return -ENOMEM;
@@ -1186,7 +1186,7 @@ int wilc_add_igtk(struct wilc_vif *vif, const u8 *igtk, u8 igtk_key_len,
struct wid wid;
struct wilc_wpa_igtk *key_buf;
- key_buf = kzalloc(sizeof(*key_buf) + t_key_len, GFP_KERNEL);
+ key_buf = kzalloc_flex(*key_buf, key, t_key_len);
if (!key_buf)
return -ENOMEM;
@@ -1217,7 +1217,7 @@ int wilc_add_rx_gtk(struct wilc_vif *vif, const u8 *rx_gtk, u8 gtk_key_len,
struct wilc_gtk_key *gtk_key;
int t_key_len = gtk_key_len + WILC_RX_MIC_KEY_LEN + WILC_TX_MIC_KEY_LEN;
- gtk_key = kzalloc(sizeof(*gtk_key) + t_key_len, GFP_KERNEL);
+ gtk_key = kzalloc_flex(*gtk_key, key, t_key_len);
if (!gtk_key)
return -ENOMEM;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH ath-current] wifi: ath10k: fix station lookup failure during disconnect
From: Baochen Qiang @ 2026-03-27 2:54 UTC (permalink / raw)
To: Paul Menzel, Jeff Johnson; +Cc: linux-wireless, ath10k
In-Reply-To: <7af13762-3e98-4d77-bde2-c14cdb3b1e3c@molgen.mpg.de>
On 3/27/2026 7:31 AM, Paul Menzel wrote:
> Dear Baochen,
>
>
> Thank you for sending the patch out.
>
> Am 25.03.26 um 04:05 schrieb Baochen Qiang:
>> Recent commit [1] moved station statistics collection to an earlier stage
>> of the disconnect flow. With this change in place, ath10k fails to resolve
>> the station entry when handling a peer stats event triggered during
>> disconnect, resulting in log messages such as:
>>
>> wlp58s0: deauthenticating from 74:1a:e0:e7:b4:c8 by local choice (Reason: 3=DEAUTH_LEAVING)
>> ath10k_pci 0000:3a:00.0: not found station for peer stats
>> ath10k_pci 0000:3a:00.0: failed to parse stats info tlv: -22
>>
>> The failure occurs because ath10k relies on ieee80211_find_sta_by_ifaddr()
>> for station lookup. That function uses local->sta_hash, but by the time
>> the peer stats request is triggered during disconnect, mac80211 has
>> already removed the station from that hash table, leading to lookup
>> failure.
>>
>> Before commit [1], this issue was not visible because the transition from
>> IEEE80211_STA_NONE to IEEE80211_STA_NOTEXIST prevented ath10k from sending
>> a peer stats request at all: ath10k_mac_sta_get_peer_stats_info() would
>> fail early to find the peer and skip requesting statistics.
>>
>> Fix this by switching the lookup path to ath10k_peer_find(), which queries
>> ath10k's internal peer table. At the point where the firmware emits the
>> peer stats event, the peer entry is still present in the driver's list,
>> ensuring lookup succeeds.
>
> Out of curiosity, how can the statistics be printed?
not quite understand your question, can you be more detailed ?
>
>> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00309-QCARMSWPZ-1
>>
>> Fixes: a203dbeeca15 ("wifi: mac80211: collect station statistics earlier when
>> disconnect") # [1]
>> Reported-by: Paul Menzel <pmenzel@molgen.mpg.de>
>> Closes: https://lore.kernel.org/ath10k/57671b89-ec9f-4e6c-992c-45eb8e75929c@molgen.mpg.de
>> Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>> ---
>> drivers/net/wireless/ath/ath10k/wmi-tlv.c | 26 +++++++++++++++-----------
>> 1 file changed, 15 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath10k/wmi-tlv.c b/drivers/net/wireless/ath/
>> ath10k/wmi-tlv.c
>> index ec8e91707f84..01f2d1fa9d7d 100644
>> --- a/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>> +++ b/drivers/net/wireless/ath/ath10k/wmi-tlv.c
>> @@ -3,7 +3,7 @@
>> * Copyright (c) 2005-2011 Atheros Communications Inc.
>> * Copyright (c) 2011-2017 Qualcomm Atheros, Inc.
>> * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
>> - * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
>> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
>> */
>> #include "core.h"
>> #include "debug.h"
>> @@ -14,6 +14,7 @@
>> #include "wmi-tlv.h"
>> #include "p2p.h"
>> #include "testmode.h"
>> +#include "txrx.h"
>> #include <linux/bitfield.h>
>> /***************/
>> @@ -224,8 +225,9 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar,
>> u16 tag, u16
>> const void *ptr, void *data)
>> {
>> const struct wmi_tlv_peer_stats_info *stat = ptr;
>> - struct ieee80211_sta *sta;
>> + u32 vdev_id = *(u32 *)data;
>> struct ath10k_sta *arsta;
>> + struct ath10k_peer *peer;
>> if (tag != WMI_TLV_TAG_STRUCT_PEER_STATS_INFO)
>> return -EPROTO;
>> @@ -241,20 +243,20 @@ static int ath10k_wmi_tlv_parse_peer_stats_info(struct ath10k *ar,
>> u16 tag, u16
>> __le32_to_cpu(stat->last_tx_rate_code),
>> __le32_to_cpu(stat->last_tx_bitrate_kbps));
>> - rcu_read_lock();
>> - sta = ieee80211_find_sta_by_ifaddr(ar->hw, stat->peer_macaddr.addr, NULL);
>> - if (!sta) {
>> - rcu_read_unlock();
>> - ath10k_warn(ar, "not found station for peer stats\n");
>> + guard(spinlock_bh)(&ar->data_lock);
>> +
>> + peer = ath10k_peer_find(ar, vdev_id, stat->peer_macaddr.addr);
>> + if (!peer || !peer->sta) {
>> + ath10k_warn(ar, "not found %s with vdev id %u mac addr %pM for peer stats\n",
>> + peer ? "sta" : "peer", vdev_id, stat->peer_macaddr.addr);
>> return -EINVAL;
>> }
>> - arsta = (struct ath10k_sta *)sta->drv_priv;
>> + arsta = (struct ath10k_sta *)peer->sta->drv_priv;
>> arsta->rx_rate_code = __le32_to_cpu(stat->last_rx_rate_code);
>> arsta->rx_bitrate_kbps = __le32_to_cpu(stat->last_rx_bitrate_kbps);
>> arsta->tx_rate_code = __le32_to_cpu(stat->last_tx_rate_code);
>> arsta->tx_bitrate_kbps = __le32_to_cpu(stat->last_tx_bitrate_kbps);
>> - rcu_read_unlock();
>> return 0;
>> }
>> @@ -266,6 +268,7 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>> const struct wmi_tlv_peer_stats_info_ev *ev;
>> const void *data;
>> u32 num_peer_stats;
>> + u32 vdev_id;
>> int ret;
>> tb = ath10k_wmi_tlv_parse_alloc(ar, skb->data, skb->len, GFP_ATOMIC);
>> @@ -284,15 +287,16 @@ static int ath10k_wmi_tlv_op_pull_peer_stats_info(struct ath10k *ar,
>> }
>> num_peer_stats = __le32_to_cpu(ev->num_peers);
>> + vdev_id = __le32_to_cpu(ev->vdev_id);
>> ath10k_dbg(ar, ATH10K_DBG_WMI,
>> "wmi tlv peer stats info update peer vdev id %d peers %i more data %d\n",
>> - __le32_to_cpu(ev->vdev_id),
>> + vdev_id,
>> num_peer_stats,
>> __le32_to_cpu(ev->more_data));
>> ret = ath10k_wmi_tlv_iter(ar, data, ath10k_wmi_tlv_len(data),
>> - ath10k_wmi_tlv_parse_peer_stats_info, NULL);
>> + ath10k_wmi_tlv_parse_peer_stats_info, &vdev_id);
>> if (ret)
>> ath10k_warn(ar, "failed to parse stats info tlv: %d\n", ret);
>
> Reviewed-by: Paul Menzel <pmenzel@molgen.mpg.de>
> Tested-by: Paul Menzel <pmenzel@molgen.mpg.de>
>
>
> Kind regards,
>
> Paul
^ permalink raw reply
* Re: [PATCH 1/3] wifi: mt76: connac: use a helper to cache txpower_cur
From: Lucid Duck @ 2026-03-27 2:15 UTC (permalink / raw)
To: sean.wang
Cc: nbd, lorenzo.bianconi, linux-wireless, linux-mediatek, morrownr,
satadru
In-Reply-To: <20260325043318.13298-1-sean.wang@kernel.org>
Adding Tested-by from Satadru Pramanik, who tested the full 3-patch
series on MT7925U with kernel 7.0-rc5 (US regdomain):
Before patch (5 GHz ch 36, 80 MHz): txpower 3.00 dBm
After patch (5 GHz ch 36, 80 MHz): txpower 23.00 dBm
Tested-by: Satadru Pramanik <satadru@gmail.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox