* Re: [PATCH] rtw88: usb: retry control message on -EPROTO error
From: Bitterblue Smith @ 2026-05-29 13:45 UTC (permalink / raw)
To: Ping-Ke Shih, VolcomIlluminated; +Cc: Linux Wireless
In-Reply-To: <c3e40e8b0a3f4b1e96ca76072700175f@realtek.com>
On 29/05/2026 04:09, Ping-Ke Shih wrote:
>
> VolcomIlluminated <volcomilluminated@tuta.com> wrote:
>> --- /tmp/linux-6.18/drivers/net/wireless/realtek/rtw88/usb.c 2025-11-30 17:42:10.000000000 -0500
>> +++ /home/ptpx86mm1/kernelbuild/linux-6.18/drivers/net/wireless/realtek/rtw88/usb.c 2026-05-24 20:06:27.798337237 -0400
>
> Your git repository looks weird.
>
> Please clone https://github.com/pkshih/rtw.git and switch to rtw-next branch.
>
> By the way, the subject prefix should be "[PATCH rtw-next] wifi: rtw88: ...".
>
>> @@ -140,6 +140,16 @@
>> ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
>> RTW_USB_CMD_REQ, RTW_USB_CMD_WRITE,
>> addr, 0, data, len, 500);
>> + if (ret == -EPROTO) {
>> + int retry;
>> +
>> + for (retry = 0; retry < 3 && ret == -EPROTO; retry++) {
>> + msleep(10);
>> + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
>> + RTW_USB_CMD_REQ, RTW_USB_CMD_WRITE,
>> + addr, 0, data, len, 500);
>
> Don't duplicate the code of identical usb_control_msg(...).
>
> Just
>
> for (retry = 0; retry < 3; retry++) {
> ret = usb_control_msg(...);
> if (ret != -EPROTO)
> break;
>
> msleep(10); /* delay before retrying */
> }
>
> Bitterblue, could you have some inputs about this retry, since I don't have
> much knowledge about USB?
>
I think it's a good idea to retry in case of errors. The vendor drivers
try the control messages up to 10 times, both reads and writes, and not
just in case of -EPROTO.
Except when writing the firmware for the 8051 chips (address range
0x1000..0x1fff). Those writes are not retried, instead the entire
firmware download process is retried if it fails.
Also, they don't sleep between attempts.
But I would like to know more about the problem fixed by this patch.
What register writes fail with -EPROTO? How often does it happen?
How many times was this patch tested?
I wonder if the problem is simply the write to register 0xc4
(REG_PAD_CTRL2) which triggers the switch to USB 3? Like with the
wifi 6 and 7 chips, that call to usb_control_msg() always returns
-EPROTO. I assume it's because it makes the USB device disappear.
>> + }
>> + }
>> if (ret < 0 && ret != -ENODEV && count++ < 4)
>> rtw_err(rtwdev, "write register 0x%x failed with %d\n",
>> addr, ret);
>
^ permalink raw reply
* Re: (subset) [PATCH v2 0/4] Use the QMI service IDs from the QMI header
From: Srinivas Kandagatla @ 2026-05-29 13:02 UTC (permalink / raw)
To: konradybcio, andersson, Daniel Lezcano
Cc: linux-kernel, Alex Elder, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Jeff Johnson,
Mathieu Poirier, Jaroslav Kysela, Takashi Iwai, Kees Cook,
Greg Kroah-Hartman, Arnd Bergmann, Mark Brown, Wesley Cheng,
netdev, linux-wireless, ath10k, ath11k, ath12k, linux-arm-msm,
linux-remoteproc, linux-sound
In-Reply-To: <20260316171419.2619620-1-daniel.lezcano@oss.qualcomm.com>
On Mon, 16 Mar 2026 18:14:10 +0100, Daniel Lezcano wrote:
> The different subsystems implementing the QMI service protocol are
> using their own definition of the service id. It is not a problem but
> it results on having those duplicated with different names but the
> same value and without consistency in their name.
>
> The QMI service IDs are defined in the qmi.h header file. Use those
> instead of defining the IDs in the protocol implementation file. It
> will result in unifying and providing a consistent way to represent
> the supported protocols.
>
> [...]
Applied, thanks!
[3/4] slimbus: qcom-ngd-ctrl: Use the unified QMI service ID instead of defining it locally
commit: 05f3f5984121e1956867b309c76a750c1d4c9682
Best regards,
--
Srinivas Kandagatla <srini@kernel.org>
^ permalink raw reply
* Re: [PATCH v4 0/7] b43: complete N-PHY rev 8 + radio 2057 rev 8 support
From: Joshua Peisach @ 2026-05-29 12:01 UTC (permalink / raw)
To: Alessio Ferri, linux-wireless, b43-dev, linux-kernel; +Cc: b43-dev
In-Reply-To: <20260528-b43_complete_n_phy_rev_8_radio_2057_rev_8_support-v4-0-464566194d47@gmail.com>
On Thu May 28, 2026 at 1:31 PM EDT, Alessio Ferri wrote:
> This series completes b43 support for the Broadcom N-PHY revision 8
> paired with radio 2057 revision 8. b43 already supports the surrounding
> PHY family - N-PHY rev 8 with radio 2057 rev 5 and rev 7 are handled,
> and rev 16 with radio 2057 rev 9 is handled - but the rev 8 + rev 8
> combination falls through four dispatcher gaps:
>
> - radio_2057.c, r2057_upload_inittabs(), case 8 lists radio_rev 5
> and 7 only;
> - radio_2057.c, r2057_get_chantabent_rev7(), case 8 lists radio_rev
> 5 only;
> - tables_nphy.c, b43_nphy_get_ipa_gain_table(), case 8 lists
> radio_rev 5 only;
> - radio_2057.c carries r2057_rev8_init[] as a 54-entry stub commented
> out with "TODO: Which devices should use it?".
>
> Two further pieces of plumbing are needed to reach those dispatchers
> in the first place: d11 core revision 0x16 is missing from the b43
> bcma id table, firmware name, and the corerev 22 / radio 2057 combination
> needs the 24-bit indirect radio access path that brcmsmac uses for
> the same silicon generation (see brcmsmac/phy/phy_cmn.c
> read_radio_reg() / write_radio_reg()).
>
> The series:
>
> 1/7 b43: add firmware and initvals names for rev22
> 2/7 b43: add d11 core revision 0x16 to id table
> 3/7 b43: route d11 corerev 22 to 24-bit indirect radio access
> 4/7 b43: support radio 2057 rev 8
> 5/7 b43: add IPA TX gain table for N-PHY r8 + radio 2057 r8
> 6/7 b43: add channel info table for N-PHY r8 + radio 2057 r8
> 7/7 b43: add RF power offset for N-PHY r8 + radio 2057 r8
>
> Patches almost reveal the bringup, the first two are swapped, as
> applying 2/7 without 1/7 generate an immediate kernel panic caused
> by a null deref.
> From the third, each one fixes the next visible failure
> in bring-up: 3/7 lets phy versioning read coherent radio identifiers,
> 4/7 unblocks the boot-time radio calibration that otherwise stalls,
> and 5/7-7/7 fill the remaining 2.4 GHz dispatcher entries so
> b43_nphy_set_channel completes to the default channel and core_init
> proceeds past PHY init.
>
> Tested on a D-Link DSL-3580L (Broadcom BCM6362 SoC, single-die 2.4 GHz
> N-PHY rev 8 + radio 2057 rev 8 in 2.4 GHz IPA mode).
>
> b43 is currently Orphan in MAINTAINERS. These patches do not add a
> new chip family or PHY infrastructure; they fill four explicit
> dispatcher gaps for a combination of an already-supported PHY and
> an already-supported radio.
>
> CHANGELOG:
> v4: reword patch 1/7 commit
> v3: no changes, fighting with b4
> v2:
> - Recovered the first patch of the series, so numbering is now /7
> - Added Assisted-By header
> v1: https://lore.kernel.org/linux-wireless/8c0a07d2-9ec9-43d6-bdf7-f625bbb4a38a@mythread.it/
>
> Assisted-by: Claude:claude-4.7-opus
> Signed-off-by: Alessio Ferri <alessio.ferri@mythread.it>
> ---
> ---
> Alessio Ferri (7):
> b43: add firmware mappings for rev22
> b43: add d11 core revision 0x16 to id table
> b43: route d11 corerev 22 to 24-bit indirect radio access
> b43: support radio 2057 rev 8
> b43: add IPA TX gain table for N-PHY r8 + radio 2057 r8
> b43: add channel info table for N-PHY r8 + radio 2057 r8
> b43: add RF power offset for N-PHY r8 + radio 2057 r8
>
> drivers/net/wireless/broadcom/b43/main.c | 22 ++-
> drivers/net/wireless/broadcom/b43/radio_2057.c | 230 ++++++++++++++++++++++--
> drivers/net/wireless/broadcom/b43/tables_nphy.c | 58 ++++++
> 3 files changed, 290 insertions(+), 20 deletions(-)
> ---
> base-commit: 8bc67e4db64aa72732c474b44ea8622062c903f0
> change-id: 20260521-b43_complete_n_phy_rev_8_radio_2057_rev_8_support-a3125f06e21e
>
> Best regards,
I'm assuming the extracted tables are correct (it would take me a while
to check) - and as mentioned this only is an addition, not a change, so
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
^ permalink raw reply
* [PATCH wireless-next 16/16] wifi: mac80211_hwsim: claim DBE capability
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Claim DBE capability in UHR MAC capabilities, hostapd will
have to sort out the actual DBE capabilities based on the
EHT capabilities.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
drivers/net/wireless/virtual/mac80211_hwsim_main.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 543793a39ad0..b7b85b2d9220 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -4708,6 +4708,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_2ghz[] = {
.has_uhr = true,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DBE_SUPP,
},
.phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
IEEE80211_UHR_PHY_CAP_ELR_TX),
@@ -4885,6 +4886,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = {
.has_uhr = true,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DBE_SUPP,
},
.phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX),
},
@@ -5017,6 +5019,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = {
.has_uhr = true,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DBE_SUPP,
},
.phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX),
},
@@ -5217,6 +5220,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
.has_uhr = true,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DBE_SUPP,
},
.phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX),
},
@@ -5370,6 +5374,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
.has_uhr = true,
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
+ [1] = IEEE80211_UHR_MAC_CAP1_DBE_SUPP,
},
.phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX),
},
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 15/16] wifi: mac80211: AP: handle DBE for clients
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
In AP mode, track the BSS non-DBE bandwidth and apply
that to all non-DBE clients, then track OMP updates
from the clients and enable/disable DBE accordingly.
For now don't send a response, clients need to have a
timer anyway (it's up to the driver to set the right
timeout in UHR capabilities.)
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-eht.h | 16 +++-
include/linux/ieee80211-uhr.h | 16 ++++
net/mac80211/ap.c | 146 ++++++++++++++++++++++++++++++++++
net/mac80211/cfg.c | 35 ++++++++
net/mac80211/ieee80211_i.h | 2 +
net/mac80211/rx.c | 9 +++
net/mac80211/sta_info.c | 10 ++-
net/mac80211/sta_info.h | 4 +
8 files changed, 232 insertions(+), 6 deletions(-)
diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index 73e97fe30724..18f9c662cf4c 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -393,14 +393,24 @@ ieee80211_eht_oper_size_ok(const u8 *data, u8 len)
return len >= needed;
}
+/* must validate ieee80211_eht_oper_size_ok() first */
+static inline const struct ieee80211_eht_operation_info *
+ieee80211_eht_oper_info(const struct ieee80211_eht_operation *eht_oper)
+{
+ if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT))
+ return NULL;
+
+ return (const void *)eht_oper->optional;
+}
+
/* must validate ieee80211_eht_oper_size_ok() first */
static inline u16
ieee80211_eht_oper_dis_subchan_bitmap(const struct ieee80211_eht_operation *eht_oper)
{
- const struct ieee80211_eht_operation_info *info =
- (const void *)eht_oper->optional;
+ const struct ieee80211_eht_operation_info *info;
- if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT))
+ info = ieee80211_eht_oper_info(eht_oper);
+ if (!info)
return 0;
if (!(eht_oper->params & IEEE80211_EHT_OPER_DISABLED_SUBCHANNEL_BITMAP_PRESENT))
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index f9592ffae3eb..bbfcb53f5987 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -628,4 +628,20 @@ struct ieee80211_uhr_mode_change_tuple {
u8 variable[];
} __packed;
+static inline int
+ieee80211_uhr_mode_change_tuple_size(const struct ieee80211_uhr_mode_change_tuple *tuple)
+{
+ return sizeof(*tuple) +
+ le16_get_bits(tuple->control,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_LENGTH);
+}
+
+#define for_each_uhr_mode_change_tuple(data, len, tuple) \
+ for (tuple = (const void *)(data); \
+ (len) - ((const u8 *)tuple - (data)) >= sizeof(*tuple) && \
+ (len) - ((const u8 *)tuple - (data)) >= \
+ ieee80211_uhr_mode_change_tuple_size(tuple); \
+ tuple = (const void *)((const u8 *)tuple + \
+ ieee80211_uhr_mode_change_tuple_size(tuple)))
+
#endif /* LINUX_IEEE80211_UHR_H */
diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c
index 6c7d2d51a372..e7ac99c5a22e 100644
--- a/net/mac80211/ap.c
+++ b/net/mac80211/ap.c
@@ -8,6 +8,7 @@
#include "driver-ops.h"
#include "ieee80211_i.h"
+#include "rate.h"
static void
ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
@@ -186,6 +187,113 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
}
+static void
+ieee80211_rx_uhr_link_reconfig_req(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ const struct element *sub;
+ struct sta_info *sta;
+
+ /*
+ * rx.c only accepts IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST
+ * which is valid, so no need to check the frame type/format/etc.
+ */
+
+ sta = sta_info_get_bss(sdata, mgmt->sa);
+ if (!sta)
+ return;
+
+ struct ieee802_11_elems *elems __free(kfree) =
+ ieee802_11_parse_elems(mgmt->u.action.uhr_link_reconf_req.variable,
+ skb->len - IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req),
+ IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION,
+ NULL);
+ /* STA will assume we processed it, not good */
+ if (!elems)
+ return;
+
+ if (!elems->ml_reconf)
+ return;
+
+ for_each_mle_subelement(sub, (u8 *)elems->ml_reconf,
+ elems->ml_reconf_len) {
+ const struct ieee80211_mle_per_sta_profile *prof =
+ (const void *)sub->data;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+ struct ieee80211_link_data *link;
+ struct link_sta_info *link_sta;
+ const struct element *chg;
+ u16 control;
+ u8 link_id;
+
+ if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
+ continue;
+
+ if (!ieee80211_mle_reconf_sta_prof_size_ok(sub->data,
+ sub->datalen))
+ return;
+
+ control = le16_to_cpu(prof->control);
+ link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID;
+
+ if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS)
+ return;
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (!link)
+ continue;
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf,
+ sdata);
+ if (!chanctx_conf)
+ continue;
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx,
+ conf);
+
+ link_sta = sdata_dereference(sta->link[link_id], sdata);
+ if (!link_sta)
+ continue;
+
+ /* do we need to handle any other bits? */
+ if (control & ~(IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID |
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE))
+ continue;
+
+ if (u16_get_bits(control, IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE) !=
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD)
+ continue;
+
+ for_each_element_extid(chg, WLAN_EID_EXT_UHR_MODE_CHG,
+ prof->variable + prof->sta_info_len - 1,
+ sub->datalen - sizeof(*prof) -
+ prof->sta_info_len + 1) {
+ const struct ieee80211_uhr_mode_change_tuple *tuple;
+
+ for_each_uhr_mode_change_tuple(chg->data + 1,
+ chg->datalen - 1,
+ tuple) {
+ u8 id = le16_get_bits(tuple->control,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID);
+ bool enabled = le16_get_bits(tuple->control,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE);
+
+ /* only handle DBE (for now?) */
+ if (id != IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE)
+ continue;
+
+ link_sta->uhr_dbe_enabled = enabled;
+ /* also recalculates and updates per-STA bw */
+ ieee80211_recalc_chanctx_min_def(sdata->local,
+ chanctx);
+ }
+ }
+ }
+
+ /* TODO: send a response */
+}
+
void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb)
{
@@ -203,5 +311,43 @@ void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
break;
}
break;
+ case WLAN_CATEGORY_PROTECTED_UHR:
+ switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST:
+ ieee80211_rx_uhr_link_reconfig_req(sdata, skb);
+ break;
+ }
+ break;
}
}
+
+void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link)
+{
+ struct ieee80211_sub_if_data *sdata = link->sdata;
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+ int link_id = link->link_id;
+ struct sta_info *sta;
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf, sdata);
+ if (!chanctx_conf)
+ return;
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+
+ list_for_each_entry(sta, &local->sta_list, list) {
+ struct link_sta_info *link_sta;
+
+ if (sta->sdata->bss != sdata->bss)
+ continue;
+
+ link_sta = sdata_dereference(sta->link[link_id], sdata);
+ if (!link_sta)
+ continue;
+
+ link_sta->uhr_dbe_enabled = false;
+ }
+
+ /* also recalculates and updates per-STA bw */
+ ieee80211_recalc_chanctx_min_def(local, chanctx);
+}
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index fb4c1c298159..19550ed3b908 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1391,6 +1391,36 @@ ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
return IEEE80211_STA_RX_BW_20;
}
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_calc_ap_eht_bw(struct cfg80211_beacon_data *params,
+ enum ieee80211_sta_rx_bandwidth he_and_lower)
+{
+ const struct ieee80211_eht_operation_info *info;
+
+ if (!params->eht_oper)
+ return he_and_lower;
+
+ info = ieee80211_eht_oper_info(params->eht_oper);
+ if (!info)
+ return he_and_lower;
+
+ switch (u8_get_bits(info->control, IEEE80211_EHT_OPER_CHAN_WIDTH)) {
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_20MHZ:
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_40MHZ:
+ return IEEE80211_STA_RX_BW_40;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ:
+ return IEEE80211_STA_RX_BW_80;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_160MHZ:
+ return IEEE80211_STA_RX_BW_160;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_320MHZ:
+ return IEEE80211_STA_RX_BW_320;
+ }
+
+ /* invalid setting, assume 20 MHz */
+ return IEEE80211_STA_RX_BW_20;
+}
+
static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
struct cfg80211_beacon_data *params)
{
@@ -1415,6 +1445,8 @@ static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
return;
link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params);
+ link->bss_bw.eht = ieee80211_calc_ap_eht_bw(params,
+ link->bss_bw.he_and_lower);
chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
@@ -4469,6 +4501,9 @@ static int __ieee80211_csa_finalize(struct ieee80211_link_data *link_data)
ieee80211_link_info_change_notify(sdata, link_data, changed);
+ if (sdata->vif.type == NL80211_IFTYPE_AP)
+ ieee80211_uhr_disable_dbe_all_stas(link_data);
+
ieee80211_vif_unblock_queues_csa(sdata);
err = drv_post_channel_switch(link_data);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 29bdfd2a39bd..34a9ea8b6f85 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1146,6 +1146,7 @@ struct ieee80211_link_data {
struct {
enum ieee80211_sta_rx_bandwidth he_and_lower;
+ enum ieee80211_sta_rx_bandwidth eht; /* and UHR non-DBE */
} bss_bw;
#ifdef CONFIG_MAC80211_DEBUGFS
@@ -2003,6 +2004,7 @@ bool ieee80211_is_our_addr(struct ieee80211_sub_if_data *sdata,
/* AP code */
void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb);
+void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link);
/* STA code */
void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 4579ebdebdf5..e4bd58c02c8f 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3952,6 +3952,15 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
break;
switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST:
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ break;
+ if (len < IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req))
+ goto invalid;
+ if (mgmt->u.action.uhr_link_reconf_req.type !=
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST)
+ break;
+ goto queue;
case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY:
if (sdata->vif.type != NL80211_IFTYPE_STATION)
break;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 6b44030659fc..873077dbd65b 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3691,10 +3691,14 @@ ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
if (WARN_ON(!link))
return IEEE80211_STA_RX_BW_20;
- if (link_sta->pub->eht_cap.has_eht)
- return bw;
+ if (!link_sta->pub->eht_cap.has_eht)
+ return min(bw, link->bss_bw.he_and_lower);
- return min(bw, link->bss_bw.he_and_lower);
+ if (!link_sta->pub->uhr_cap.has_uhr ||
+ !link_sta->uhr_dbe_enabled)
+ return min(bw, link->bss_bw.eht);
+
+ return bw;
}
static enum ieee80211_sta_rx_bandwidth
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 6974fccb839f..ec0bf56bfb05 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -514,6 +514,8 @@ struct ieee80211_fragment_cache {
* @uhr_usable_tx_width: bandwidth restriction for UHR for TX, only when
* the link_sta is an AP, to restrict TX to BSS width during DBE
* enablement
+ * @uhr_dbe_enabled: for STAs as clients to an AP interface indicates
+ * DBE is enabled by the STA
* @debugfs_dir: debug filesystem directory dentry
* @pub: public (driver visible) link STA data
*/
@@ -567,6 +569,8 @@ struct link_sta_info {
rx_omi_bw_staging;
enum ieee80211_sta_rx_bandwidth uhr_usable_tx_width;
+ bool uhr_dbe_enabled;
+
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 14/16] wifi: mac80211: parse and apply UHR DBE channel
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
When a UHR AP has DBE enabled, parse the channel and apply it
to the chandef. Apply for TX only after the OMP response (or
timeout) so that the AP doesn't receive frames with DBE width
before the station completed transition to DBE.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-eht.h | 18 +-
include/linux/ieee80211-uhr.h | 92 +++++++
net/mac80211/ieee80211_i.h | 15 +-
net/mac80211/mlme.c | 449 ++++++++++++++++++++++++++++++++-
net/mac80211/rx.c | 14 +
net/mac80211/sta_info.c | 16 ++
net/mac80211/sta_info.h | 4 +
net/mac80211/status.c | 25 ++
net/mac80211/tests/chan-mode.c | 4 +-
9 files changed, 616 insertions(+), 21 deletions(-)
diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index 87d92fb86fab..73e97fe30724 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -1038,13 +1038,17 @@ ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(const struct ieee80211_mle_per_sta
#define IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE 0x0010
#define IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT 0x0020
#define IEEE80211_MLE_STA_RECONF_CONTROL_AP_REM_TIMER_PRESENT 0x0040
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE 0x0780
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_AP_REM 0
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_OP_PARAM_UPDATE 1
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_ADD_LINK 2
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_DEL_LINK 3
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_NSTR_STATUS 4
-#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_PARAMS_PRESENT 0x0800
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE 0x0780
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_AP_REM 0
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_OP_PARAM_UPDATE 1
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_ADD_LINK 2
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_DEL_LINK 3
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_NSTR_STATUS 4
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD 5
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_PARAMS_PRESENT 0x0800
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_NSTR_BMAP_SIZE 0x1000
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_NSTR_IND_BMAP_PRES 0x2000
+#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_DSO_INFO_PRESENT 0x4000
/**
* ieee80211_mle_reconf_sta_prof_size_ok - validate reconfiguration multi-link
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index 0c1715280fe7..f9592ffae3eb 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -10,11 +10,13 @@
#include <linux/types.h>
#include <linux/bitfield.h>
#include <linux/if_ether.h>
+#include "ieee80211-eht.h"
#define IEEE80211_UHR_OPER_PARAMS_DPS_ENA 0x0001
#define IEEE80211_UHR_OPER_PARAMS_NPCA_ENA 0x0002
#define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0004
#define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0008
+#define IEEE80211_UHR_OPER_PARAMS_DBE_BW 0x0070
struct ieee80211_uhr_operation {
__le16 params;
@@ -177,6 +179,29 @@ enum ieee80211_uhr_dbe_oper_bw {
IEEE80211_UHR_DBE_OPER_BW_320_2 = 5,
};
+/**
+ * ieee80211_uhr_dbe_bw_mhz - get bandwidth in MHz from UHR DBE bandwidth
+ * @bw: UHR DBE bandwidth
+ *
+ * Return: the bandwidth in MHz, or -1 for invalid values
+ */
+static inline int ieee80211_uhr_dbe_bw_mhz(enum ieee80211_uhr_dbe_oper_bw bw)
+{
+ switch (bw) {
+ case IEEE80211_UHR_DBE_OPER_BW_40:
+ return 40;
+ case IEEE80211_UHR_DBE_OPER_BW_80:
+ return 80;
+ case IEEE80211_UHR_DBE_OPER_BW_160:
+ return 160;
+ case IEEE80211_UHR_DBE_OPER_BW_320_1:
+ case IEEE80211_UHR_DBE_OPER_BW_320_2:
+ return 320;
+ default:
+ return -1;
+ }
+}
+
/**
* struct ieee80211_uhr_dbe_info - DBE operation information
*
@@ -335,6 +360,35 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper)
return npca->dis_subch_bmap;
}
+/*
+ * Note: cannot call this on the element coming from a beacon,
+ * must ensure ieee80211_uhr_oper_size_ok(..., false) first
+ */
+static inline const struct ieee80211_uhr_dbe_info *
+ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper)
+{
+ const u8 *pos = oper->variable;
+
+ if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA)))
+ return NULL;
+
+ if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA))
+ pos += sizeof(struct ieee80211_uhr_dps_info);
+
+ if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) {
+ const struct ieee80211_uhr_npca_info *npca = (const void *)pos;
+
+ pos += sizeof(*npca);
+ if (npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES))
+ pos += sizeof(npca->dis_subch_bmap[0]);
+ }
+
+ if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA))
+ pos += sizeof(struct ieee80211_uhr_p_edca_info);
+
+ return (const void *)pos;
+}
+
#define IEEE80211_UHR_MAC_CAP0_DPS_SUPP 0x01
#define IEEE80211_UHR_MAC_CAP0_DPS_ASSIST_SUPP 0x02
#define IEEE80211_UHR_MAC_CAP0_DPS_AP_STATIC_HCM_SUPP 0x04
@@ -475,6 +529,44 @@ static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len,
return len >= needed;
}
+#define IEEE80211_UHR_OM_PU_TO_128TU 11
+
+/**
+ * ieee80211_uhr_capa_get_om_pu_to_us - get OM parameter update timeout in usec
+ * @cap: the UHR capability element, size must be validated
+ *
+ * Return: the OM parameter update timeout in usec, or -1 if it's not valid
+ */
+static inline int
+ieee80211_uhr_capa_get_om_pu_to_us(const struct ieee80211_uhr_cap *cap)
+{
+ u8 timeout;
+
+ timeout = u8_get_bits(cap->mac.mac_cap[3],
+ IEEE80211_UHR_MAC_CAP3_UHR_OM_PU_TO_HIGH);
+ timeout <<= 2;
+ timeout |= u8_get_bits(cap->mac.mac_cap[2],
+ IEEE80211_UHR_MAC_CAP2_UHR_OM_PU_TO_LOW);
+
+ if (timeout > IEEE80211_UHR_OM_PU_TO_128TU)
+ return -1;
+
+ if (!timeout)
+ return 0;
+
+ return 128 << (timeout - 1);
+}
+
+/* only valid from AP, must check ieee80211_uhr_capa_size_ok(..., true) */
+static inline const struct ieee80211_uhr_cap_dbe *
+ieee80211_uhr_dbe_cap(const struct ieee80211_uhr_cap *cap)
+{
+ if (!(cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP))
+ return NULL;
+
+ return (const void *)cap->variable;
+}
+
#define IEEE80211_SMD_INFO_CAPA_DL_DATA_FWD 0x01
#define IEEE80211_SMD_INFO_CAPA_MAX_NUM_PREP 0x0E
#define IEEE80211_SMD_INFO_CAPA_TYPE 0x10
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 339faa7a0a0e..29bdfd2a39bd 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -99,6 +99,7 @@ enum ieee80211_status_data {
IEEE80211_STATUS_TYPE_INVALID = 0,
IEEE80211_STATUS_TYPE_SMPS = 1,
IEEE80211_STATUS_TYPE_NEG_TTLM = 2,
+ IEEE80211_STATUS_TYPE_UHR_OMP = 3,
IEEE80211_STATUS_SUBDATA_MASK = 0x1ff0,
};
@@ -412,6 +413,7 @@ enum ieee80211_conn_bw_limit {
struct ieee80211_conn_settings {
enum ieee80211_conn_mode mode;
enum ieee80211_conn_bw_limit bw_limit;
+ bool dbe_enabled;
};
extern const struct ieee80211_conn_settings ieee80211_conn_settings_unlimited;
@@ -639,6 +641,15 @@ struct ieee80211_if_managed {
bool enabled;
u8 dialog_token;
} epcs;
+
+ struct {
+ struct wiphy_hrtimer_work status_work;
+ u32 timeout_us;
+ u16 links;
+ u16 pending, pending_init;
+ u8 dialog_token;
+ bool acked;
+ } uhr_omp;
};
struct ieee80211_if_ibss {
@@ -2790,6 +2801,7 @@ ieee80211_chanreq_downgrade(struct ieee80211_chan_req *chanreq,
return;
if (conn->mode < IEEE80211_CONN_MODE_EHT)
chanreq->ap.chan = NULL;
+ conn->dbe_enabled = false;
}
bool ieee80211_chanreq_identical(const struct ieee80211_chan_req *a,
@@ -2980,7 +2992,8 @@ void ieee80211_rearrange_tpe_psd(struct ieee80211_parsed_tpe_psd *psd,
struct ieee802_11_elems *
ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
struct ieee80211_conn_settings *conn,
- struct cfg80211_bss *cbss, int link_id,
+ struct cfg80211_bss *cbss,
+ struct link_sta_info *link_sta, int link_id,
struct ieee80211_chan_req *chanreq,
struct cfg80211_chan_def *ap_chandef,
unsigned long *userspace_selectors);
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 2b0be473c82c..655d782bd078 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -171,14 +171,24 @@ struct ieee80211_determine_ap_chan_data {
const struct ieee80211_conn_settings *conn;
u32 vht_cap_info;
bool ignore_ht_channel_mismatch;
+ const struct cfg80211_chan_def *cur_chandef;
+ bool cur_dbe_used;
/* target chandef is filled in */
struct cfg80211_chan_def *chandef;
};
+struct ieee80211_determine_ap_chan_output {
+ /* filled to indicate UHR DBE was used */
+ bool dbe_used;
+ /* and need to know non-DBE width */
+ enum nl80211_chan_width non_dbe_width;
+};
+
static enum ieee80211_conn_mode
ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_determine_ap_chan_data *data)
+ const struct ieee80211_determine_ap_chan_data *data,
+ struct ieee80211_determine_ap_chan_output *out)
{
bool ignore_ht_channel_mismatch = data->ignore_ht_channel_mismatch;
const struct ieee802_11_elems *elems = data->elems;
@@ -196,6 +206,8 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
bool no_vht = false;
u32 ht_cfreq;
+ memset(out, 0, sizeof(*out));
+
if (ieee80211_hw_check(&sdata->local->hw, STRICT))
ignore_ht_channel_mismatch = false;
@@ -396,6 +408,8 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
struct cfg80211_chan_def npca_chandef = *chandef;
const struct ieee80211_sta_uhr_cap *uhr_cap;
const struct ieee80211_uhr_npca_info *npca;
+ const struct ieee80211_uhr_dbe_info *dbe;
+ struct cfg80211_chan_def dbe_chandef;
/* frames other than beacons carry UHR capability too */
if (!elems->uhr_cap)
@@ -427,6 +441,96 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
if (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP)
*chandef = npca_chandef;
+
+ dbe = ieee80211_uhr_oper_dbe_info(uhr_oper);
+ if (dbe) {
+ const struct ieee80211_uhr_cap_dbe *dbe_cap;
+ u8 dbe_bw_oper;
+ u8 dbe_bw_cap;
+
+ dbe_cap = ieee80211_uhr_dbe_cap(elems->uhr_cap);
+
+ if (!dbe_cap) {
+ sdata_info(sdata,
+ "AP without UHR DBE capability uses it, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ dbe_bw_oper = u8_get_bits(dbe->params,
+ IEEE80211_UHR_DBE_OPER_BANDWIDTH);
+
+ if (le16_get_bits(uhr_oper->params,
+ IEEE80211_UHR_OPER_PARAMS_DBE_BW) != dbe_bw_oper) {
+ sdata_info(sdata,
+ "AP UHR DBE settings mismatch, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (ieee80211_uhr_dbe_bw_mhz(dbe_bw_oper) < 0) {
+ sdata_info(sdata,
+ "AP UHR DBE bandwidth invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ dbe_bw_cap = u8_get_bits(dbe_cap->cap,
+ IEEE80211_UHR_MAC_CAP_DBE_MAX_BW);
+
+ switch (dbe_bw_cap) {
+ case IEEE80211_UHR_DBE_MAX_BW_40:
+ case IEEE80211_UHR_DBE_MAX_BW_80:
+ case IEEE80211_UHR_DBE_MAX_BW_160:
+ case IEEE80211_UHR_DBE_MAX_BW_320:
+ break;
+ default:
+ sdata_info(sdata,
+ "AP UHR DBE capability invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ /* 1-4 are same in DBE capabilities, map 320-2 to 320 */
+ if (dbe_bw_oper == IEEE80211_UHR_DBE_OPER_BW_320_2)
+ dbe_bw_oper = IEEE80211_UHR_DBE_MAX_BW_320;
+ if (dbe_bw_oper > dbe_bw_cap) {
+ sdata_info(sdata,
+ "AP UHR DBE wider than capability, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+ }
+
+ dbe_chandef = *chandef;
+
+ if (cfg80211_chandef_add_dbe(&dbe_chandef, dbe)) {
+ sdata_info(sdata,
+ "AP UHR DBE settings invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (dbe &&
+ /* maybe driver would like to never use DBE */
+ uhr_cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP &&
+ ieee80211_chandef_usable(sdata, &dbe_chandef,
+ IEEE80211_CHAN_DISABLED)) {
+ out->non_dbe_width = chandef->width;
+ *chandef = dbe_chandef;
+ out->dbe_used = true;
+ }
+ } else if (data->cur_chandef && data->cur_dbe_used &&
+ cfg80211_chandef_compatible(chandef, data->cur_chandef)) {
+ u8 dbe_bw = le16_get_bits(uhr_oper->params,
+ IEEE80211_UHR_OPER_PARAMS_DBE_BW);
+ int dbe_bw_mhz;
+
+ dbe_bw_mhz = ieee80211_uhr_dbe_bw_mhz(dbe_bw);
+ if (dbe_bw_mhz < 0) {
+ sdata_info(sdata,
+ "AP UHR DBE bandwidth invalid, drop UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (cfg80211_chandef_get_width(data->cur_chandef) == dbe_bw_mhz) {
+ *chandef = *data->cur_chandef;
+ out->dbe_used = true;
+ }
}
return IEEE80211_CONN_MODE_UHR;
@@ -1069,7 +1173,8 @@ static void ieee80211_set_chanreq_ap(struct ieee80211_sub_if_data *sdata,
VISIBLE_IF_MAC80211_KUNIT struct ieee802_11_elems *
ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
struct ieee80211_conn_settings *conn,
- struct cfg80211_bss *cbss, int link_id,
+ struct cfg80211_bss *cbss,
+ struct link_sta_info *link_sta, int link_id,
struct ieee80211_chan_req *chanreq,
struct cfg80211_chan_def *ap_chandef,
unsigned long *userspace_selectors)
@@ -1099,6 +1204,7 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
enum ieee80211_conn_mode ap_mode;
unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
+ struct ieee80211_determine_ap_chan_output ap_chan_out;
struct ieee80211_determine_ap_chan_data ap_chan_data = {
.channel = channel,
.vht_cap_info = bss->vht_cap_info,
@@ -1115,7 +1221,9 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
return ERR_PTR(-ENOMEM);
ap_chan_data.elems = elems;
- ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data,
+ &ap_chan_out);
+ conn->dbe_enabled = ap_chan_out.dbe_used;
/* this should be impossible since parsing depends on our mode */
if (WARN_ON(ap_mode > conn->mode)) {
@@ -1331,6 +1439,10 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
goto free;
}
+ if (conn->dbe_enabled && link_sta)
+ link_sta->uhr_usable_tx_width =
+ ieee80211_chan_width_to_rx_bw(ap_chan_out.non_dbe_width);
+
return elems;
free:
kfree(elems);
@@ -1338,12 +1450,152 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode);
+static void ieee80211_send_uhr_omp_req_dbe(struct ieee80211_sub_if_data *sdata,
+ u16 link_mask, bool initial)
+{
+ struct ieee80211_mle_basic_common_info *common;
+ struct ieee80211_mle_per_sta_profile *per_sta;
+ struct ieee80211_uhr_mode_change_tuple *tuple;
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_multi_link_elem *mle;
+ struct ieee80211_link_data *link;
+ struct ieee80211_tx_info *info;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+ u8 *ml_elem_len;
+ size_t size;
+
+ if (initial) {
+ bool enabled = false;
+
+ for_each_link_data(sdata, link) {
+ if (!(link_mask & BIT(link->link_id)))
+ continue;
+ if (link->u.mgd.conn.dbe_enabled) {
+ enabled = true;
+ break;
+ }
+ }
+
+ if (!enabled)
+ return;
+ }
+
+ if (sdata->u.mgd.uhr_omp.links) {
+ if (initial)
+ sdata->u.mgd.uhr_omp.pending_init |= link_mask;
+ else
+ sdata->u.mgd.uhr_omp.pending |= link_mask;
+ return;
+ }
+
+ size = local->hw.extra_tx_headroom +
+ IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req) +
+ 3 + sizeof(*mle) + sizeof(*common) +
+ IEEE80211_MLD_MAX_NUM_LINKS *
+ (2 + sizeof(*per_sta) +
+ 3 + sizeof(*tuple) /* single tuple for each link */);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+
+ mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req));
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_UHR;
+ mgmt->u.action.action_code =
+ IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST;
+
+ sdata->u.mgd.dialog_token_alloc++;
+ /*
+ * NOTE:
+ * Driver and FW might both send these frames, and iwlwifi
+ * decided that the driver uses odd numbers, FW uses even
+ * numbers. For now, hardcode that here, until it matters
+ * to some other driver.
+ *
+ * Note also that there's currently no real synchronisation
+ * in this case, but it's not valid that both send such a
+ * frame at the same time, i.e. while waiting for a response
+ * there can't be another frame sent. This needs addressing
+ * in the future.
+ */
+ if (sdata->u.mgd.dialog_token_alloc % 2 == 0)
+ sdata->u.mgd.dialog_token_alloc++;
+
+ sdata->u.mgd.uhr_omp.dialog_token = sdata->u.mgd.dialog_token_alloc;
+ mgmt->u.action.uhr_link_reconf_req.dialog_token =
+ sdata->u.mgd.uhr_omp.dialog_token;
+ mgmt->u.action.uhr_link_reconf_req.type =
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST;
+
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ ml_elem_len = skb_put(skb, 1);
+ skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK);
+ mle = skb_put_zero(skb, sizeof(*mle));
+ mle->control = cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_RECONF |
+ IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR);
+
+ common = skb_put(skb, sizeof(*common));
+ common->len = sizeof(*common);
+ memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);
+
+ for_each_link_data(sdata, link) {
+ u8 *subelem_len;
+
+ if (!(link_mask & BIT(link->link_id)))
+ continue;
+
+ sdata->u.mgd.uhr_omp.links |= BIT(link->link_id);
+
+ skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE);
+ subelem_len = skb_put(skb, 1);
+ per_sta = skb_put_zero(skb, sizeof(*per_sta));
+ per_sta->control =
+ le16_encode_bits(link->link_id,
+ IEEE80211_MLE_STA_CONTROL_LINK_ID) |
+ le16_encode_bits(IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD,
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE);
+ per_sta->sta_info_len = 1; /* includes itself */
+
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ skb_put_u8(skb, 1 + sizeof(*tuple));
+ skb_put_u8(skb, WLAN_EID_EXT_UHR_MODE_CHG);
+ tuple = skb_put_zero(skb, sizeof(*tuple));
+ tuple->control =
+ le16_encode_bits(IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID);
+
+ if (link->u.mgd.conn.dbe_enabled)
+ tuple->control |=
+ cpu_to_le16(IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE);
+
+ ieee80211_fragment_element(skb, subelem_len,
+ IEEE80211_MLE_SUBELEM_FRAGMENT);
+ }
+
+ ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
+
+ info = IEEE80211_SKB_CB(skb);
+ info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
+ info->status_data = IEEE80211_STATUS_TYPE_UHR_OMP;
+ ieee80211_tx_skb(sdata, skb);
+}
+
static int ieee80211_config_bw(struct ieee80211_link_data *link,
struct ieee802_11_elems *elems,
bool update, u64 *changed, u16 stype)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
struct cfg80211_chan_def ap_chandef;
+ struct ieee80211_determine_ap_chan_output ap_chan_out;
struct ieee80211_determine_ap_chan_data ap_chan_data = {
.channel = channel,
.vht_cap_info = 0,
@@ -1351,6 +1603,8 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
.chandef = &ap_chandef,
.elems = elems,
.conn = &link->u.mgd.conn,
+ .cur_chandef = &link->conf->chanreq.oper,
+ .cur_dbe_used = link->u.mgd.conn.dbe_enabled,
};
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chanctx_conf *chanctx_conf;
@@ -1387,7 +1641,9 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link,
ap_chan_data.vht_cap_info =
le32_to_cpu(elems->vht_cap_elem->vht_cap_info);
- ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data,
+ &ap_chan_out);
+ link->u.mgd.conn.dbe_enabled = ap_chan_out.dbe_used;
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
@@ -2652,6 +2908,7 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
cfg80211_ch_switch_notify(sdata->dev, &link->csa.chanreq.oper,
link->link_id);
link->conf->csa_active = false;
+ link->u.mgd.conn.dbe_enabled = false;
ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
if (WARN_ON(!ap_sta))
@@ -2662,6 +2919,12 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
if (WARN_ON(!link_sta))
return;
+ /*
+ * If the link was somehow deactivated in the middle of enabling
+ * DBE while waiting for a response, this could be stuck, reset.
+ */
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+
link_sta->pub->bandwidth =
ieee80211_sta_current_bw(link_sta,
&link->csa.chanreq.oper,
@@ -2754,6 +3017,8 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+ struct link_sta_info *link_sta;
+ struct sta_info *ap_sta;
int ret;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -2763,6 +3028,7 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link)
ieee80211_vif_unblock_queues_csa(sdata);
link->conf->csa_active = false;
+ link->u.mgd.conn.dbe_enabled = false;
link->u.mgd.csa.blocked_tx = false;
link->u.mgd.csa.waiting_bcn = false;
@@ -2775,6 +3041,20 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link)
return;
}
+ ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!ap_sta))
+ return;
+
+ link_sta = sdata_dereference(ap_sta->link[link->link_id], sdata);
+ if (WARN_ON(!link_sta))
+ return;
+
+ /*
+ * If DBE was being activated and CSA happened, this could be
+ * on a wrong value. Reset it.
+ */
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+
cfg80211_ch_switch_notify(sdata->dev, &link->conf->chanreq.oper,
link->link_id);
}
@@ -4479,6 +4759,9 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
memset(ifmgd->userspace_selectors, 0,
sizeof(ifmgd->userspace_selectors));
+
+ ifmgd->uhr_omp.pending = 0;
+ ifmgd->uhr_omp.pending_init = 0;
}
static void ieee80211_reset_ap_probe(struct ieee80211_sub_if_data *sdata)
@@ -4799,6 +5082,101 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata)
ifmgd->reconnect = false;
}
+static void ieee80211_uhr_omp_req_status(struct ieee80211_sub_if_data *sdata)
+{
+ bool acked = sdata->u.mgd.uhr_omp.acked;
+ u16 links = sdata->u.mgd.uhr_omp.links;
+ struct ieee80211_link_data *link;
+ struct sta_info *ap;
+
+ /* timer and queued RX could overlap */
+ if (!links)
+ return;
+
+ sdata->u.mgd.uhr_omp.links = 0;
+ sdata->u.mgd.uhr_omp.acked = false;
+
+ if (!acked) {
+ sdata_dbg(sdata, "UHR OMP frame not ACKed - disconnect\n");
+ __ieee80211_disconnect(sdata);
+ return;
+ }
+
+ ap = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (!ap)
+ return;
+
+ for_each_link_data(sdata, link) {
+ struct link_sta_info *link_sta;
+
+ if (!(links & BIT(link->link_id)))
+ continue;
+
+ /* only handle transition to enabled for now */
+ if (!link->u.mgd.conn.dbe_enabled)
+ continue;
+
+ link_sta = sdata_dereference(ap->link[link->link_id], sdata);
+ if (WARN_ON(!link_sta))
+ continue;
+
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+ ieee80211_link_sta_update_rc_bw(link, link_sta);
+ }
+
+ /* next round - send pending frames if needed */
+
+ if (sdata->u.mgd.uhr_omp.pending_init) {
+ links = sdata->u.mgd.uhr_omp.pending_init;
+
+ sdata->u.mgd.uhr_omp.pending_init = 0;
+ ieee80211_send_uhr_omp_req_dbe(sdata, links, true);
+ return;
+ }
+
+ if (sdata->u.mgd.uhr_omp.pending) {
+ links = sdata->u.mgd.uhr_omp.pending;
+
+ sdata->u.mgd.uhr_omp.pending = 0;
+ ieee80211_send_uhr_omp_req_dbe(sdata, links, false);
+ return;
+ }
+}
+
+static void ieee80211_uhr_omp_req_status_wk(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct ieee80211_sub_if_data *sdata =
+ container_of(work, struct ieee80211_sub_if_data,
+ u.mgd.uhr_omp.status_work.work);
+
+ ieee80211_uhr_omp_req_status(sdata);
+}
+
+static void ieee80211_process_uhr_omp_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt)
+{
+ if (mgmt->u.action.uhr_link_reconf_notif.dialog_token !=
+ sdata->u.mgd.uhr_omp.dialog_token)
+ return;
+
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
+ &sdata->u.mgd.uhr_omp.status_work);
+ ieee80211_uhr_omp_req_status(sdata);
+}
+
+static void
+ieee80211_process_uhr_link_reconf_notif(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
+{
+ switch (mgmt->u.action.uhr_link_reconf_notif.type) {
+ case IEEE80211_UHR_LINK_RECONFIG_NOTIFY_OMP_RESPONSE:
+ ieee80211_process_uhr_omp_resp(sdata, mgmt);
+ break;
+ }
+}
+
static void ieee80211_beacon_connection_loss_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
@@ -5777,12 +6155,27 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
if (elems->uhr_operation && elems->uhr_cap &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) {
+ int omp_to_us;
+
ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
elems->uhr_cap,
elems->uhr_cap_len,
link_sta);
bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr;
+
+ /*
+ * This assumes that the timeout is the same across all links,
+ * maybe we should actually validate that.
+ */
+ omp_to_us = ieee80211_uhr_capa_get_om_pu_to_us(elems->uhr_cap);
+ if (omp_to_us < 0) {
+ ret = false;
+ link_info(link, "Invalid UHR OMP timeout\n");
+ goto out;
+ }
+
+ sdata->u.mgd.uhr_omp.timeout_us = omp_to_us;
} else {
bss_conf->uhr_support = false;
}
@@ -6312,6 +6705,7 @@ ieee80211_determine_our_sta_mode_assoc(struct ieee80211_sub_if_data *sdata,
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
+ struct link_sta_info *link_sta,
int link_id,
struct cfg80211_bss *cbss, bool mlo,
struct ieee80211_conn_settings *conn,
@@ -6327,7 +6721,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(local->hw.wiphy);
rcu_read_lock();
- elems = ieee80211_determine_chan_mode(sdata, conn, cbss, link_id,
+ elems = ieee80211_determine_chan_mode(sdata, conn, cbss,
+ link_sta, link_id,
&chanreq, &ap_chandef,
userspace_selectors);
@@ -6649,7 +7044,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
if (link_id != assoc_data->assoc_link_id) {
link->u.mgd.conn = assoc_data->link[link_id].conn;
- err = ieee80211_prep_channel(sdata, link, link_id, cbss,
+ err = ieee80211_prep_channel(sdata, link, link_sta,
+ link_id, cbss,
true, &link->u.mgd.conn,
sdata->u.mgd.userspace_selectors);
if (err) {
@@ -6730,6 +7126,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
+ ieee80211_send_uhr_omp_req_dbe(sdata, ~0, true);
+
return true;
out_err:
eth_zero_addr(sdata->vif.cfg.ap_addr);
@@ -9069,6 +9467,8 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
ieee80211_neg_ttlm_timeout_work);
wiphy_work_init(&ifmgd->teardown_ttlm_work,
ieee80211_teardown_ttlm_work);
+ wiphy_hrtimer_work_init(&ifmgd->uhr_omp.status_work,
+ ieee80211_uhr_omp_req_status_wk);
ifmgd->flags = 0;
ifmgd->powersave = sdata->wdev.ps;
@@ -9283,6 +9683,9 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
}
if (new_sta || override) {
+ struct link_sta_info *link_sta;
+ struct sta_info *ap;
+
/*
* Only set this if we're also going to calculate the AP
* settings etc., otherwise this was set before in a
@@ -9290,7 +9693,18 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata,
* if the settings were changed.
*/
link->u.mgd.conn = *conn;
- err = ieee80211_prep_channel(sdata, link, link->link_id, cbss,
+
+ ap = new_sta ?: have_sta;
+ link_sta = sdata_dereference(ap->link[link->link_id], sdata);
+ if (!link_sta) {
+ err = -EINVAL;
+ if (new_sta)
+ sta_info_free(local, new_sta);
+ goto out_err;
+ }
+
+ err = ieee80211_prep_channel(sdata, link, link_sta,
+ link->link_id, cbss,
mlo, &link->u.mgd.conn,
userspace_selectors);
if (err) {
@@ -10185,8 +10599,8 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
continue;
if (i == assoc_data->assoc_link_id)
continue;
- /* only calculate the mode, hence link == NULL */
- err = ieee80211_prep_channel(sdata, NULL, i,
+ /* only calculate the mode, hence link/link_sta == NULL */
+ err = ieee80211_prep_channel(sdata, NULL, NULL, i,
assoc_data->link[i].bss, true,
&assoc_data->link[i].conn,
sdata->u.mgd.userspace_selectors);
@@ -10369,6 +10783,8 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata)
&ifmgd->csa_connection_drop_work);
wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
&ifmgd->tdls_peer_del_work);
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
+ &ifmgd->uhr_omp.status_work);
if (ifmgd->assoc_data)
ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT);
@@ -10615,7 +11031,7 @@ ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
link->conf->dtim_period = link->u.mgd.dtim_period ?: 1;
link->u.mgd.conn = add_links_data->link[link_id].conn;
- if (ieee80211_prep_channel(sdata, link, link_id, cbss,
+ if (ieee80211_prep_channel(sdata, link, link_sta, link_id, cbss,
true, &link->u.mgd.conn,
sdata->u.mgd.userspace_selectors)) {
link_info(link, "mlo: reconf: prep_channel failed\n");
@@ -10660,6 +11076,8 @@ ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
ieee80211_recalc_ps(local);
ieee80211_recalc_ps_vif(sdata);
+ ieee80211_send_uhr_omp_req_dbe(sdata, link_mask, true);
+
done_data.buf = (const u8 *)mgmt;
done_data.len = orig_len;
done_data.added_links = link_mask;
@@ -11030,7 +11448,7 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
continue;
/* only used to verify the mode, nothing is allocated */
- err = ieee80211_prep_channel(sdata, NULL, link_id,
+ err = ieee80211_prep_channel(sdata, NULL, NULL, link_id,
data->link[link_id].bss,
true,
&data->link[link_id].conn,
@@ -11492,6 +11910,15 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
break;
}
break;
+ case WLAN_CATEGORY_PROTECTED_UHR:
+ switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY:
+ ieee80211_process_uhr_link_reconf_notif(sdata,
+ mgmt,
+ skb->len);
+ break;
+ }
+ break;
}
break;
}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index ef6086b183f7..4579ebdebdf5 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3947,6 +3947,20 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
break;
}
break;
+ case WLAN_CATEGORY_PROTECTED_UHR:
+ if (len < IEEE80211_MIN_ACTION_SIZE(action_code))
+ break;
+
+ switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY:
+ if (sdata->vif.type != NL80211_IFTYPE_STATION)
+ break;
+
+ if (len < IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_notif))
+ goto invalid;
+ goto queue;
+ }
+ break;
}
return RX_CONTINUE;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 85e0b6c5ff00..6b44030659fc 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -572,6 +572,20 @@ 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;
+ /*
+ * This will always be taken into account, so set to MAX.
+ * When mac80211 is the client on a UHR AP, it'll be used
+ * for the TX side, to limit the bandwidth to TX to the AP
+ * with, to limit to the BSS width during DBE enablement.
+ *
+ * This is needed since the chanreq, which normally has
+ * maximum bandwidth to use with the AP, will already be
+ * set to the DBE width during enablement to prepare for
+ * RX (and not be racy), but the TX can only use higher
+ * bandwidth after enablement finishes.
+ */
+ link_info->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+
link_info->op_mode_bw = IEEE80211_STA_RX_BW_MAX;
/*
@@ -3717,6 +3731,8 @@ ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
bw = min(bw, link_sta->op_mode_bw);
/* also limit to RX OMI bandwidth we TX to the STA */
bw = min(bw, link_sta->rx_omi_bw_tx);
+ /* and UHR DBE transition limits */
+ bw = min(bw, link_sta->uhr_usable_tx_width);
/* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
* IEEE80211-2016 specification makes higher bandwidth operation
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 1da074dfffb4..6974fccb839f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -511,6 +511,9 @@ struct ieee80211_fragment_cache {
* @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
* @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later
* during finalize
+ * @uhr_usable_tx_width: bandwidth restriction for UHR for TX, only when
+ * the link_sta is an AP, to restrict TX to BSS width during DBE
+ * enablement
* @debugfs_dir: debug filesystem directory dentry
* @pub: public (driver visible) link STA data
*/
@@ -562,6 +565,7 @@ struct link_sta_info {
enum ieee80211_sta_rx_bandwidth rx_omi_bw_rx,
rx_omi_bw_tx,
rx_omi_bw_staging;
+ enum ieee80211_sta_rx_bandwidth uhr_usable_tx_width;
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 8716eda8317d..dd1dbba06838 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -731,6 +731,28 @@ ieee80211_handle_teardown_ttlm_status(struct ieee80211_sub_if_data *sdata,
&sdata->u.mgd.teardown_ttlm_work);
}
+static void
+ieee80211_handle_uhr_omp_status(struct ieee80211_sub_if_data *sdata, bool acked)
+{
+ if (!sdata || !ieee80211_sdata_running(sdata))
+ return;
+
+ if (sdata->vif.type != NL80211_IFTYPE_STATION)
+ return;
+
+ sdata->u.mgd.uhr_omp.acked = acked;
+
+ if (!acked) {
+ wiphy_hrtimer_work_queue(sdata->local->hw.wiphy,
+ &sdata->u.mgd.uhr_omp.status_work, 0);
+ return;
+ }
+
+ wiphy_hrtimer_work_queue(sdata->local->hw.wiphy,
+ &sdata->u.mgd.uhr_omp.status_work,
+ us_to_ktime(sdata->u.mgd.uhr_omp.timeout_us));
+}
+
static void ieee80211_report_used_skb(struct ieee80211_local *local,
struct sk_buff *skb, bool dropped,
ktime_t ack_hwtstamp)
@@ -811,6 +833,9 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local,
case IEEE80211_STATUS_TYPE_NEG_TTLM:
ieee80211_handle_teardown_ttlm_status(sdata, acked);
break;
+ case IEEE80211_STATUS_TYPE_UHR_OMP:
+ ieee80211_handle_uhr_omp_status(sdata, acked);
+ break;
}
rcu_read_unlock();
}
diff --git a/net/mac80211/tests/chan-mode.c b/net/mac80211/tests/chan-mode.c
index fa370831d617..ab7d38ef6a3a 100644
--- a/net/mac80211/tests/chan-mode.c
+++ b/net/mac80211/tests/chan-mode.c
@@ -2,7 +2,7 @@
/*
* KUnit tests for channel mode functions
*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <net/cfg80211.h>
#include <kunit/test.h>
@@ -243,7 +243,7 @@ static void test_determine_chan_mode(struct kunit *test)
rcu_read_lock();
elems = ieee80211_determine_chan_mode(t_sdata->sdata, &conn, &cbss,
- 0, &chanreq, &ap_chandef,
+ NULL, 0, &chanreq, &ap_chandef,
userspace_selectors);
rcu_read_unlock();
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 13/16] wifi: mac80211: refactor link STA bandwidth update
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There's similar code in two places in HT and HE, and we need to add
the same again for UHR. Rename ieee80211_link_sta_rc_update_omi()
to ieee80211_link_sta_update_rc_bw() and move it to sta_info.c and
update existing code that can use it to do so.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/he.c | 25 ++-----------------------
net/mac80211/ht.c | 24 ++++++++----------------
net/mac80211/sta_info.c | 23 +++++++++++++++++++++++
net/mac80211/sta_info.h | 3 +++
4 files changed, 36 insertions(+), 39 deletions(-)
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index b7d9e4cb6ba6..5acf482c177a 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -262,27 +262,6 @@ ieee80211_he_spr_ie_to_bss_conf(struct ieee80211_vif *vif,
}
}
-static void ieee80211_link_sta_rc_update_omi(struct ieee80211_link_data *link,
- struct link_sta_info *link_sta)
-{
- struct ieee80211_sub_if_data *sdata = link->sdata;
- struct ieee80211_supported_band *sband;
- enum ieee80211_sta_rx_bandwidth new_bw;
- enum nl80211_band band;
-
- band = link->conf->chanreq.oper.chan->band;
- sband = sdata->local->hw.wiphy->bands[band];
-
- new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
- IEEE80211_STA_BW_TX_TO_STA);
- if (link_sta->pub->bandwidth == new_bw)
- return;
-
- link_sta->pub->bandwidth = new_bw;
- rate_control_rate_update(sdata->local, sband, link_sta,
- IEEE80211_RC_BW_CHANGED);
-}
-
bool ieee80211_prepare_rx_omi_bw(struct ieee80211_link_sta *pub_link_sta,
enum ieee80211_sta_rx_bandwidth bw)
{
@@ -323,7 +302,7 @@ bool ieee80211_prepare_rx_omi_bw(struct ieee80211_link_sta *pub_link_sta,
if (bw < link_sta->rx_omi_bw_staging) {
link_sta->rx_omi_bw_tx = bw;
- ieee80211_link_sta_rc_update_omi(link, link_sta);
+ ieee80211_link_sta_update_rc_bw(link, link_sta);
} else {
link_sta->rx_omi_bw_rx = bw;
ieee80211_recalc_chanctx_min_def(local, chanctx);
@@ -365,7 +344,7 @@ void ieee80211_finalize_rx_omi_bw(struct ieee80211_link_sta *pub_link_sta)
/* rate control in finalize only when widening bandwidth */
WARN_ON(link_sta->rx_omi_bw_tx > link_sta->rx_omi_bw_staging);
link_sta->rx_omi_bw_tx = link_sta->rx_omi_bw_staging;
- ieee80211_link_sta_rc_update_omi(link, link_sta);
+ ieee80211_link_sta_update_rc_bw(link, link_sta);
}
if (link_sta->rx_omi_bw_rx != link_sta->rx_omi_bw_staging) {
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 6285ac15c16c..e1e1b7f82f3f 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -584,9 +584,10 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
struct link_sta_info *link_sta,
u8 chanwidth, enum nl80211_band band)
{
- enum ieee80211_sta_rx_bandwidth max_bw, new_bw;
- struct ieee80211_supported_band *sband;
- struct sta_opmode_info sta_opmode = {};
+ enum ieee80211_sta_rx_bandwidth max_bw;
+ struct sta_opmode_info sta_opmode = {
+ .changed = STA_OPMODE_MAX_BW_CHANGED,
+ };
struct ieee80211_link_data *link;
lockdep_assert_wiphy(local->hw.wiphy);
@@ -602,21 +603,12 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
/* set op_mode_bw and recalc sta bw */
link_sta->op_mode_bw = max_bw;
- new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
- IEEE80211_STA_BW_TX_TO_STA);
- if (link_sta->pub->bandwidth == new_bw)
+ if (!ieee80211_link_sta_update_rc_bw(link, link_sta))
return;
- link_sta->pub->bandwidth = new_bw;
- sband = local->hw.wiphy->bands[band];
- sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
- sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED;
+ sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(link_sta->pub->bandwidth);
- rate_control_rate_update(local, sband, link_sta,
- IEEE80211_RC_BW_CHANGED);
- cfg80211_sta_opmode_change_notify(sdata->dev,
- sta->addr,
- &sta_opmode,
- GFP_KERNEL);
+ cfg80211_sta_opmode_change_notify(sdata->dev, sta->addr,
+ &sta_opmode, GFP_KERNEL);
}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 0ea37016cd4f..85e0b6c5ff00 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3765,3 +3765,26 @@ ieee80211_sta_current_bw(struct link_sta_info *link_sta,
/* unreachable */
return IEEE80211_STA_RX_BW_20;
}
+
+bool ieee80211_link_sta_update_rc_bw(struct ieee80211_link_data *link,
+ struct link_sta_info *link_sta)
+{
+ struct ieee80211_sub_if_data *sdata = link->sdata;
+ struct ieee80211_supported_band *sband;
+ enum ieee80211_sta_rx_bandwidth new_bw;
+ enum nl80211_band band;
+
+ band = link->conf->chanreq.oper.chan->band;
+ sband = sdata->local->hw.wiphy->bands[band];
+
+ new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
+ IEEE80211_STA_BW_TX_TO_STA);
+ if (link_sta->pub->bandwidth == new_bw)
+ return false;
+
+ link_sta->pub->bandwidth = new_bw;
+ rate_control_rate_update(sdata->local, sband, link_sta,
+ IEEE80211_RC_BW_CHANGED);
+
+ return true;
+}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 39608a0abbb5..1da074dfffb4 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -1015,6 +1015,9 @@ ieee80211_sta_current_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef,
enum ieee80211_sta_bw_direction direction);
+bool ieee80211_link_sta_update_rc_bw(struct ieee80211_link_data *link,
+ struct link_sta_info *link_sta);
+
enum sta_stats_type {
STA_STATS_RATE_TYPE_INVALID = 0,
STA_STATS_RATE_TYPE_LEGACY,
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 12/16] wifi: Update UHR MAC capabilities to D1.4
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There are now 8 more reserved bits in D1.4, update the code
accordingly.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-uhr.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index 120993897490..0c1715280fe7 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -398,7 +398,7 @@ enum ieee80211_uhr_dbe_max_supported_bw {
};
struct ieee80211_uhr_cap_mac {
- u8 mac_cap[5];
+ u8 mac_cap[6];
} __packed;
#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x00000001
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 11/16] wifi: Update UHR PHY capabilities to D1.4
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There are new capabilities in D1.4, and some reserved
bits. Update the code accordingly.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
.../wireless/intel/iwlwifi/iwl-nvm-parse.c | 10 +++--
drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 4 +-
.../wireless/virtual/mac80211_hwsim_main.c | 24 +++++------
include/linux/ieee80211-uhr.h | 40 ++++++++++++++-----
4 files changed, 49 insertions(+), 29 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
index 7027bca249a0..d47b4ae2f486 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c
@@ -693,8 +693,9 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = {
},
.uhr_cap = {
.has_uhr = true,
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ /* Note: asymmetry is fixed later */
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
+ IEEE80211_UHR_PHY_CAP_ELR_TX),
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP |
IEEE80211_UHR_MAC_CAP0_DPS_SUPP,
@@ -801,8 +802,9 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = {
},
.uhr_cap = {
.has_uhr = true,
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ /* Note: asymmetry is fixed later */
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
+ IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
};
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
index a03834d3ac65..edf9c735dd67 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c
@@ -114,8 +114,8 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld,
}
if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap &&
- uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX &&
- own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX)
+ uhr_cap->phy.cap & cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX) &&
+ own_uhr_cap->phy.cap & cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX))
flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK |
IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 6ea082157307..543793a39ad0 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -4593,8 +4593,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_2ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
+ IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
{
@@ -4709,8 +4709,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_2ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
+ IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
#ifdef CONFIG_MAC80211_MESH
@@ -4886,8 +4886,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
{
@@ -5019,8 +5018,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX),
},
},
#ifdef CONFIG_MAC80211_MESH
@@ -5220,8 +5218,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
{
@@ -5374,8 +5371,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX),
},
},
#ifdef CONFIG_MAC80211_MESH
@@ -5473,8 +5469,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
.mac.mac_cap = {
[0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP,
},
- .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX |
- IEEE80211_UHR_PHY_CAP_ELR_TX,
+ .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX |
+ IEEE80211_UHR_PHY_CAP_ELR_TX),
},
},
#endif
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index 71faf4a6825e..120993897490 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -401,17 +401,39 @@ struct ieee80211_uhr_cap_mac {
u8 mac_cap[5];
} __packed;
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x01
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x02
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x04
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_160 0x08
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_320 0x10
-#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_320 0x20
-#define IEEE80211_UHR_PHY_CAP_ELR_RX 0x40
-#define IEEE80211_UHR_PHY_CAP_ELR_TX 0x80
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x00000001
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x00000002
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x00000004
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_160 0x00000008
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_320 0x00000010
+#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_320 0x00000020
+#define IEEE80211_UHR_PHY_CAP_ELR_RX 0x00000040
+#define IEEE80211_UHR_PHY_CAP_ELR_TX 0x00000080
+#define IEEE80211_UHR_PHY_CAP_PART_BW_DL_MUMIMO 0x00000100
+#define IEEE80211_UHR_PHY_CAP_PART_BW_UL_MUMIMO 0x00000200
+#define IEEE80211_UHR_PHY_CAP_MCS15 0x00000400
+#define IEEE80211_UHR_PHY_CAP_2XLDPC_TX 0x00000800
+#define IEEE80211_UHR_PHY_CAP_2XLDPC_RX 0x00001000
+#define IEEE80211_UHR_PHY_CAP_UEQM_TX_MAX_NSS 0x00006000
+#define IEEE80211_UHR_PHY_CAP_UEQM_RX_MAX_NSS 0x00018000
+#define IEEE80211_UHR_PHY_CAP_CO_BF_JOINT_SOUNDING 0x00040000
+#define IEEE80211_UHR_PHY_CAP_IM_TX 0x00080000
+#define IEEE80211_UHR_PHY_CAP_IM_RX 0x00100000
+#define IEEE80211_UHR_PHY_CAP_CO_SR_MODE_1 0x00200000
+#define IEEE80211_UHR_PHY_CAP_CO_SR_MODE_2 0x00400000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_20_IN_PBW_20 0x00800000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_40_IN_PBW_40 0x01000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_80 0x02000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_160 0x04000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_320 0x08000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_20_IN_PBW_GE80 0x10000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_40_IN_PBW_GE80 0x20000000
+#define IEEE80211_UHR_PHY_CAP_DRU_DBW_60_IN_PBW_GE80 0x40000000
+#define IEEE80211_UHR_PHY_CAP_DRU_RRU_HYBRID_MODE 0x80000000
struct ieee80211_uhr_cap_phy {
- u8 cap;
+ __le32 cap;
+ u8 reserved;
} __packed;
struct ieee80211_uhr_cap {
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 10/16] wifi: mac80211: explain ieee80211_determine_chan_mode() parsing
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Looking through element parsing behaviour for multi-BSSID
and multi-link, this one seemed odd. Add a comment that
explains why it's written this way.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 949ea018079a..2b0be473c82c 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1077,6 +1077,14 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_bss_ies *ies = rcu_dereference(cbss->ies);
struct ieee80211_bss *bss = (void *)cbss->priv;
struct ieee80211_channel *channel = cbss->channel;
+ /*
+ * This is for parsing a beacon or probe response here, but it's
+ * using the *BSS* elements which are synthetic for multi-BSSID,
+ * created by cfg80211 based on multi-BSSID inheritance etc. As
+ * a result, this sets neither .bss (since multi-BSSID is parsed
+ * already) nor a valid .link_id (since it doesn't want to see
+ * the data from another link.)
+ */
struct ieee80211_elems_parse_params parse_params = {
.link_id = -1,
.from_ap = true,
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 09/16] wifi: mac80211: mlme: allow UHR only with MLO
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
UHR requires MLO, not just formally but also in order
for the client to understand AP BSS parameter changes,
since the Critical Update Counter is inside the Multi-
Link Element. Require MLO for UHR connections to avoid
otherwise needed complexity such as not enabling any
feature that would require tracking critical updates.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 181fe975fcd9..949ea018079a 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -389,7 +389,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
}
check_uhr:
- if (conn->mode < IEEE80211_CONN_MODE_UHR || !uhr_oper)
+ if (conn->mode < IEEE80211_CONN_MODE_UHR || !uhr_oper || !elems->ml_basic)
return IEEE80211_CONN_MODE_EHT;
if (elems->frame_type != (IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_BEACON)) {
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 08/16] wifi: mac80211: always expose multi-link element
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless
Cc: Johannes Berg, Miriam Rachel Korenblit, Benjamin Berg, Ilan Peer
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
During beacon processing, the parser is always called with
a BSS to find the correct multi-BSSID profile (if any) and
therefore never attempts to parse a multi-link element.
This means the code to handle cross-link CSA can effectively
never do anything.
Fix this by parsing the multi-link element in the regular
parser as well.
Fixes: 7ef8f6821d16 ("wifi: mac80211: mlme: handle cross-link CSA")
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Reviewed-by: Benjamin Berg <benjamin.berg@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/parse.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 1dc9b1fd225c..ab9b87fdba1e 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -54,7 +54,7 @@ struct ieee80211_elems_parse {
/* must be first for kfree to work */
struct ieee802_11_elems elems;
- struct ieee80211_elem_defrag ml_reconf, ml_epcs;
+ struct ieee80211_elem_defrag ml_reconf, ml_epcs, ml_basic;
bool inside_multilink;
bool skip_vendor;
@@ -169,6 +169,9 @@ ieee80211_parse_extension_element(u32 *crc,
IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC;
break;
}
+ elems_parse->ml_basic.elem = elem;
+ elems_parse->ml_basic.start = params->start;
+ elems_parse->ml_basic.len = params->len;
break;
case IEEE80211_ML_CONTROL_TYPE_RECONF:
elems_parse->ml_reconf.elem = elem;
@@ -1140,6 +1143,10 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
elems->ml_epcs = ieee80211_mle_defrag(elems_parse,
&elems_parse->ml_epcs,
&elems->ml_epcs_len);
+ if (!elems->ml_basic)
+ elems->ml_basic = ieee80211_mle_defrag(elems_parse,
+ &elems_parse->ml_basic,
+ &elems->ml_basic_len);
if (elems->tim && !elems->parse_error) {
const struct ieee80211_tim_ie *tim_ie = elems->tim;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 07/16] wifi: cfg80211: harden cfg80211_defragment_element()
From: Johannes Berg @ 2026-05-29 8:25 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg, Miriam Rachel Korenblit, Ilan Peer
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
A previous commit changed mac80211 to no longer make wrong
calls to cfg80211_defragment_element() with the element
pointing outside of the buffer. Additionally, harden this
function itself against that and always return -EINVAL in
case the element isn't inside the source buffer.
Reviewed-by: Miriam Rachel Korenblit <miriam.rachel.korenblit@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/scan.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 358cbc9e43d8..17f0032844ab 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -5,7 +5,7 @@
* Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2016 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/kernel.h>
#include <linux/slab.h>
@@ -2603,7 +2603,9 @@ ssize_t cfg80211_defragment_element(const struct element *elem, const u8 *ies,
ssize_t copied;
u8 elem_datalen;
- if (!elem)
+ if (!elem || (const u8 *)elem < ies ||
+ (const u8 *)elem + sizeof(*elem) > ies + ieslen ||
+ (const u8 *)elem + sizeof(*elem) + elem->datalen > ies + ieslen)
return -EINVAL;
/* elem might be invalid after the memmove */
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 06/16] wifi: mac80211: use local ml_basic_elem in parsing
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There's no need to store this pointer on the heap, it's
only used in a single function. Move it there. Also
clarify the comment referencing it, ml_basic_elem is
not actually relevant (any more.)
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/parse.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index aa06c1fdf472..1dc9b1fd225c 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -54,9 +54,6 @@ struct ieee80211_elems_parse {
/* must be first for kfree to work */
struct ieee802_11_elems elems;
- /* The basic Multi-Link element in the original elements */
- const struct element *ml_basic_elem;
-
struct ieee80211_elem_defrag ml_reconf, ml_epcs;
bool inside_multilink;
@@ -932,6 +929,7 @@ ieee80211_prep_mle_link_parse(struct ieee80211_elems_parse *elems_parse,
{
struct ieee802_11_elems *elems = &elems_parse->elems;
struct ieee80211_mle_per_sta_profile *prof;
+ const struct element *ml_basic_elem = NULL;
const struct element *tmp, *ret;
ssize_t ml_len;
const u8 *end;
@@ -951,12 +949,11 @@ ieee80211_prep_mle_link_parse(struct ieee80211_elems_parse *elems_parse,
IEEE80211_ML_CONTROL_TYPE_BASIC)
continue;
- elems_parse->ml_basic_elem = tmp;
+ ml_basic_elem = tmp;
break;
}
- ml_len = cfg80211_defragment_element(elems_parse->ml_basic_elem,
- elems->ie_start,
+ ml_len = cfg80211_defragment_element(ml_basic_elem, elems->ie_start,
elems->total_len,
elems_parse->scratch_pos,
elems_parse->scratch +
@@ -1115,7 +1112,10 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
if (params->bss->transmitted_bss && !non_inherit)
non_inherit = (const void *)empty_non_inheritance;
} else {
- /* must always parse to get elems_parse->ml_basic_elem */
+ /*
+ * Find the multi-link element and the non-inherit element inside
+ * the applicable profile, if requested by params->link_id >= 0.
+ */
non_inherit = ieee80211_prep_mle_link_parse(elems_parse, params,
&sub);
inside_multilink = true;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 05/16] wifi: mac80211: clarify beacon parsing with MBSSID/EMA
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
When connected to a non-transmitting BSS of multiple BSSID
set with EMA, the correct profile for the connection isn't
always present in the beacon. Indicate this in the parser
and use the information to not check everything in beacon
processing, since the information might not be correct if
taken only from the transmitted BSS.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 9 +++++
net/mac80211/mlme.c | 83 ++++++++++++++++++++++++++++----------
net/mac80211/parse.c | 4 ++
3 files changed, 75 insertions(+), 21 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 18a101710432..339faa7a0a0e 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1898,6 +1898,15 @@ struct ieee802_11_elems {
struct ieee80211_mle_per_sta_profile *prof;
size_t sta_prof_len;
+ /*
+ * When parsing the beacon with MBSSID (from a transmitted BSS), this
+ * indicates that the profile the parser was instructed to look for
+ * (via the bss value in &struct ieee80211_elems_parse_params) couldn't
+ * be found (due to EMA, or perhaps broken AP) and the result cannot be
+ * considered complete.
+ */
+ bool mbssid_nontx_profile_missing;
+
/* whether/which parse error occurred while retrieving these elements */
u8 parse_error;
};
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index c2ef6c66acde..181fe975fcd9 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -7668,8 +7668,6 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
struct link_sta_info *link_sta;
struct sta_info *sta;
u64 changed = 0;
- bool erp_valid;
- u8 erp_value = 0;
u32 ncrc = 0;
u8 *bssid, *variable = mgmt->u.beacon.variable;
u8 deauth_buf[IEEE80211_DEAUTH_FRAME_LEN];
@@ -7790,6 +7788,13 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
if (!elems)
return;
+ /*
+ * Note: with MBSSID and an EMA (or broken) AP, we could fail to find
+ * the correct multi-BSSID profile for the non-transmitting AP we're
+ * connected to. The result's elems->mbssid_nontx_profile_missing is
+ * indicating that, but some things must happen regardless.
+ */
+
if (rx_status->flag & RX_FLAG_DECRYPTED &&
ieee80211_mgd_ssid_mismatch(sdata, elems)) {
sdata_info(sdata, "SSID mismatch for AP %pM, disconnect\n",
@@ -7825,6 +7830,11 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
}
}
+ /*
+ * P2P will almost certainly not have MBSSID, but this just
+ * assumes that it would at least always inherit NoA anyway
+ * since it's absent from the channel.
+ */
if (sdata->vif.p2p ||
sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) {
struct ieee80211_p2p_noa_attr noa = {};
@@ -7882,23 +7892,17 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
ieee80211_rx_bss_info(link, mgmt, len, rx_status);
+ /*
+ * This assumes that all members of a multiple BSS set must be
+ * switching together, so we can parse channel switch elements
+ * from the transmitted BSS even if our non-transmitted one is
+ * not present in this beacon (due to EMA.)
+ */
ieee80211_sta_process_chanswitch(link, rx_status->mactime,
rx_status->device_timestamp,
elems, elems,
IEEE80211_CSA_SOURCE_BEACON);
- /* note that after this elems->ml_basic can no longer be used fully */
- ieee80211_mgd_check_cross_link_csa(sdata, rx_status->link_id, elems);
-
- ieee80211_mgd_update_bss_param_ch_cnt(sdata, bss_conf, elems);
-
- if (!sdata->u.mgd.epcs.enabled &&
- !link->u.mgd.disable_wmm_tracking &&
- ieee80211_sta_wmm_params(local, link, elems->wmm_param,
- elems->wmm_param_len,
- elems->mu_edca_param_set))
- changed |= BSS_CHANGED_QOS;
-
/*
* If we haven't had a beacon before, tell the driver about the
* DTIM period (and beacon timing if desired) now.
@@ -7915,17 +7919,53 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
ieee80211_recalc_ps_vif(sdata);
}
- if (elems->erp_info) {
- erp_valid = true;
- erp_value = elems->erp_info[0];
- } else {
- erp_valid = false;
- }
+ /* RNR isn't inside an MBSSID profile */
+ ieee80211_mgd_update_bss_param_ch_cnt(sdata, bss_conf, elems);
+
+ /* assume ERP would be inherited anyway */
+ if (!ieee80211_is_s1g_beacon(hdr->frame_control)) {
+ u8 erp_value = 0;
+ bool erp_valid;
+
+ if (elems->erp_info) {
+ erp_valid = true;
+ erp_value = elems->erp_info[0];
+ } else {
+ erp_valid = false;
+ }
- if (!ieee80211_is_s1g_beacon(hdr->frame_control))
changed |= ieee80211_handle_bss_capability(link,
le16_to_cpu(mgmt->u.beacon.capab_info),
erp_valid, erp_value);
+ }
+
+ /*
+ * There are some other things that we can only do when the
+ * real non-transmitted profile was actually parsed, so exit
+ * here before doing those.
+ */
+ if (elems->mbssid_nontx_profile_missing)
+ goto apply;
+
+ /*
+ * This requires multi-link element, which is from the MBSSID profile.
+ * Note that after this elems->ml_basic can no longer be used fully.
+ *
+ * Note also that currently the parsing is incorrect, so this will
+ * never actually do anything.
+ */
+ ieee80211_mgd_check_cross_link_csa(sdata, rx_status->link_id, elems);
+
+ /*
+ * EDCA parameters should be the same, but perhaps ACM can differ
+ * between BSSes in an MBSSID set.
+ */
+ if (!sdata->u.mgd.epcs.enabled &&
+ !link->u.mgd.disable_wmm_tracking &&
+ ieee80211_sta_wmm_params(local, link, elems->wmm_param,
+ elems->wmm_param_len,
+ elems->mu_edca_param_set))
+ changed |= BSS_CHANGED_QOS;
sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
if (WARN_ON(!sta)) {
@@ -7971,6 +8011,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
ieee80211_process_adv_ttlm(sdata, elems,
le64_to_cpu(mgmt->u.beacon.timestamp));
+apply:
ieee80211_link_info_change_notify(sdata, link, changed);
free:
kfree(elems);
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 4a7b41995f76..aa06c1fdf472 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -1094,6 +1094,10 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
sub.type = params->type;
sub.link_id = params->link_id;
+ /* indicate to consumer whether or not profile was found */
+ if (params->bss->transmitted_bss && !nontx_len)
+ elems->mbssid_nontx_profile_missing = true;
+
/* consume the space used for non-transmitted profile */
elems_parse->scratch_pos += nontx_len;
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 04/16] wifi: mac80211: rename "multi_link_inner" variable
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
This variable name seems a bit misleading now (I added it
myself a year ago or so), it indicates that the parsing is
happening on the inner elements of a multi-link element.
Rename it to "inside_multilink" to clarify.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/parse.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 22365c507b99..4a7b41995f76 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -59,7 +59,7 @@ struct ieee80211_elems_parse {
struct ieee80211_elem_defrag ml_reconf, ml_epcs;
- bool multi_link_inner;
+ bool inside_multilink;
bool skip_vendor;
/*
@@ -167,7 +167,7 @@ ieee80211_parse_extension_element(u32 *crc,
switch (le16_get_bits(mle->control,
IEEE80211_ML_CONTROL_TYPE)) {
case IEEE80211_ML_CONTROL_TYPE_BASIC:
- if (elems_parse->multi_link_inner) {
+ if (elems_parse->inside_multilink) {
elems->parse_error |=
IEEE80211_PARSE_ERR_DUP_NEST_ML_BASIC;
break;
@@ -1046,7 +1046,7 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
const struct element *non_inherit = NULL;
struct ieee802_11_elems *elems;
size_t scratch_len = 3 * params->len;
- bool multi_link_inner = false;
+ bool inside_multilink = false;
BUILD_BUG_ON(sizeof(empty_non_inheritance) != empty_non_inheritance[1] + 2);
BUILD_BUG_ON(offsetof(typeof(*elems_parse), elems) != 0);
@@ -1114,7 +1114,7 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
/* must always parse to get elems_parse->ml_basic_elem */
non_inherit = ieee80211_prep_mle_link_parse(elems_parse, params,
&sub);
- multi_link_inner = true;
+ inside_multilink = true;
}
elems_parse->skip_vendor =
@@ -1125,7 +1125,7 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
/* Override with nontransmitted/per-STA profile if found */
if (sub.len) {
- elems_parse->multi_link_inner = multi_link_inner;
+ elems_parse->inside_multilink = inside_multilink;
elems_parse->skip_vendor = false;
_ieee802_11_parse_elems_full(&sub, elems_parse, NULL);
}
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 03/16] wifi: mac80211: clean up return in ieee802_11_find_bssid_profile()
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There's no need to define 'profile_len' at the outer scope
and initialize it, move it where needed and just return 0
if nothing can be found.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/parse.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 8b30e361b622..22365c507b99 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -813,10 +813,9 @@ static size_t ieee802_11_find_bssid_profile(const u8 *start, size_t len,
u8 *nontransmitted_profile)
{
const struct element *elem, *sub;
- size_t profile_len = 0;
if (!bss || !bss->transmitted_bss)
- return profile_len;
+ return 0;
for_each_element_id(elem, WLAN_EID_MULTIPLE_BSSID, start, len) {
if (elem->datalen < 2)
@@ -826,6 +825,7 @@ static size_t ieee802_11_find_bssid_profile(const u8 *start, size_t len,
for_each_element(sub, elem->data + 1, elem->datalen - 1) {
u8 new_bssid[ETH_ALEN];
+ size_t profile_len;
const u8 *index;
if (sub->id != 0 || sub->datalen < 4) {
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 02/16] wifi: mac80211: unify link STA removal in vif link removal
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
There are multiple cases where interface links are removed
and the station links need to be removed with them, e.g.
in mlme.c we have both received and transmitted multi-link
reconfiguration, doing the two things in different order,
the former deleting STA links when the vif link change may
still fail.
It's also not clear that userspace (hostapd) couldn't, at
least in theory, remove a link from an interface without
removing the station links first, or even leave stations
that aren't MLO-capable, using that link.
Unify this code into ieee80211_vif_update_links() so that
it always happens, always happens in the right order and
is transactional (i.e. failures are handled correctly.)
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/link.c | 30 ++++++++++++++++++++++++++++++
net/mac80211/mlme.c | 19 -------------------
2 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/net/mac80211/link.c b/net/mac80211/link.c
index e81dd02de12e..d0535268962c 100644
--- a/net/mac80211/link.c
+++ b/net/mac80211/link.c
@@ -292,6 +292,7 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata,
u16 old_active = sdata->vif.active_links;
unsigned long add = new_links & ~old_links;
unsigned long rem = old_links & ~new_links;
+ unsigned long sta_rem = rem;
unsigned int link_id;
int ret;
struct link_container *links[IEEE80211_MLD_MAX_NUM_LINKS] = {}, *link;
@@ -299,6 +300,7 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *old_data[IEEE80211_MLD_MAX_NUM_LINKS];
bool use_deflink = old_links == 0; /* set for error case */
bool non_sta = sdata->vif.type != NL80211_IFTYPE_STATION;
+ struct sta_info *sta;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -402,6 +404,34 @@ static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata,
goto free;
}
+ /* try to remove links that are now invalid from (MLO) stations */
+ list_for_each_entry(sta, &sdata->local->sta_list, list) {
+ unsigned long rem_links = sta->sta.valid_links & sta_rem;
+
+ if (sta->sdata != sdata)
+ continue;
+
+ /*
+ * skip stations that would have no links left,
+ * those will be removed completely later
+ */
+ if (sta->sta.valid_links == rem_links)
+ continue;
+
+ for_each_set_bit(link_id, &rem_links,
+ IEEE80211_MLD_MAX_NUM_LINKS)
+ ieee80211_sta_remove_link(sta, link_id);
+ }
+
+ /*
+ * Remove stations using any removed links. Note that due
+ * to the above station link removal, this only removes
+ * stations that were skipped above because they'd have no
+ * links left after link removal.
+ */
+ for_each_set_bit(link_id, &sta_rem, IEEE80211_MLD_MAX_NUM_LINKS)
+ sta_info_flush(sdata, link_id);
+
/* use deflink/bss_conf again if and only if there are no more links */
use_deflink = new_links == 0;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index e8d6f6a95c0a..c2ef6c66acde 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -7171,7 +7171,6 @@ static void ieee80211_ml_reconf_work(struct wiphy *wiphy,
container_of(work, struct ieee80211_sub_if_data,
u.mgd.ml_reconf_work.work);
u16 new_valid_links, new_active_links, new_dormant_links;
- struct sta_info *sta;
int ret;
if (!sdata->u.mgd.removed_links)
@@ -7207,16 +7206,6 @@ static void ieee80211_ml_reconf_work(struct wiphy *wiphy,
}
}
- sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
- if (sta) {
- unsigned long removed_links = sdata->u.mgd.removed_links;
- unsigned int link_id;
-
- for_each_set_bit(link_id, &removed_links,
- IEEE80211_MLD_MAX_NUM_LINKS)
- ieee80211_sta_remove_link(sta, link_id);
- }
-
new_dormant_links = sdata->vif.dormant_links & ~sdata->u.mgd.removed_links;
ret = ieee80211_vif_set_links(sdata, new_valid_links,
@@ -11073,14 +11062,6 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
goto err_free;
}
- for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS;
- link_id++) {
- if (!(req->rem_links & BIT(link_id)))
- continue;
-
- ieee80211_sta_remove_link(sta, link_id);
- }
-
/* notify the driver and upper layers */
ieee80211_vif_cfg_change_notify(sdata,
BSS_CHANGED_MLD_VALID_LINKS);
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 01/16] wifi: ieee80211: define some UHR link reconfiguration frame types
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529082644.106145-18-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Define some values needed for UHR link reconfiguration frames,
in particular to prepare for UHR mode change request/handling.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-uhr.h | 51 +++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 17 ++++++++++++
2 files changed, 68 insertions(+)
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
index f4f4bd8256df..71faf4a6825e 100644
--- a/include/linux/ieee80211-uhr.h
+++ b/include/linux/ieee80211-uhr.h
@@ -8,6 +8,7 @@
#define LINUX_IEEE80211_UHR_H
#include <linux/types.h>
+#include <linux/bitfield.h>
#include <linux/if_ether.h>
#define IEEE80211_UHR_OPER_PARAMS_DPS_ENA 0x0001
@@ -463,4 +464,54 @@ struct ieee80211_smd_info {
__le16 timeout;
} __packed;
+enum ieee80211_protected_uhr_action {
+ IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST = 0,
+ IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_RESPONSE = 1,
+ IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY = 2,
+};
+
+enum ieee80211_uhr_link_reconfig_request_type {
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_ST_PREP = 0,
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_ST_EXEC = 1,
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST = 3,
+};
+
+enum ieee80211_uhr_link_reconfig_response_type {
+ IEEE80211_UHR_LINK_RECONFIG_RESPONSE_ST_PREP = 0,
+ IEEE80211_UHR_LINK_RECONFIG_RESPONSE_ST_EXEC = 1,
+};
+
+enum ieee80211_uhr_link_reconfig_notify_type {
+ IEEE80211_UHR_LINK_RECONFIG_NOTIFY_DL_DRAINED = 2,
+ IEEE80211_UHR_LINK_RECONFIG_NOTIFY_OMP_RESPONSE = 3,
+};
+
+enum ieee80211_uhr_mode_change_control {
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID = 0x003f,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE = 0x0040,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_UPDATE = 0x0080,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_LENGTH = 0x0f00,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_SPECIFIC = 0xf000,
+};
+
+enum ieee80211_uhr_mode_change_mode_id {
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_DPS = 0,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_NPCA = 1,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_DUO = 2,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_DSO = 3,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_P_EDCA = 4,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_ELR_RX = 5,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_AOM = 6,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_LLI = 7,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_CO_BF = 8,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_CO_SR = 9,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_EMLSR = 10,
+ IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE = 11,
+};
+
+struct ieee80211_uhr_mode_change_tuple {
+ __le16 control;
+ u8 variable[];
+} __packed;
+
#endif /* LINUX_IEEE80211_UHR_H */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index 11106589acc6..d40484451e9a 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1174,6 +1174,22 @@ struct ieee80211_mgmt {
u8 control;
u8 variable[];
} __packed eml_omn;
+ struct {
+ u8 dialog_token;
+ u8 type;
+ u8 variable[];
+ } __packed uhr_link_reconf_req;
+ struct {
+ u8 dialog_token;
+ u8 type;
+ u8 count;
+ u8 variable[];
+ } __packed uhr_link_reconf_resp;
+ struct {
+ u8 dialog_token;
+ u8 type;
+ u8 variable[];
+ } __packed uhr_link_reconf_notif;
};
} __packed action;
DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */
@@ -1837,6 +1853,7 @@ enum ieee80211_category {
WLAN_CATEGORY_VHT = 21,
WLAN_CATEGORY_S1G = 22,
WLAN_CATEGORY_PROTECTED_EHT = 37,
+ WLAN_CATEGORY_PROTECTED_UHR = 43,
WLAN_CATEGORY_VENDOR_SPECIFIC_PROTECTED = 126,
WLAN_CATEGORY_VENDOR_SPECIFIC = 127,
};
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next 00/16] more UHR work to support DBE
From: Johannes Berg @ 2026-05-29 8:24 UTC (permalink / raw)
To: linux-wireless
Since I had already posted the tests for this as part of the hostap
patch series for UHR, I really should post the kernel code too.
Some of the initial patches are more preparation and/or refactoring
for critical update as well, but then I have DBE. I didn't want to
pull this all apart, it'd likely just result in conflicts.
Note that the patches 13-15 have had less internal review so far.
johannes
^ permalink raw reply
* [PATCH v3 rtw-next 2/2] wifi: rtw89: usb: add serial_number and uuid sysfs attributes for 0x28de:0x2432
From: Ping-Ke Shih @ 2026-05-29 7:50 UTC (permalink / raw)
To: linux-wireless
Cc: driver-core, johannes, mh_chen, wenjie.tsai, charlesl, sabae
In-Reply-To: <20260529075032.16807-1-pkshih@realtek.com>
From: Johnson Tsai <wenjie.tsai@realtek.com>
Expose the device's Serial Number (SN) and UUID from EFUSE via two
read-only sysfs attributes, `serial_number` and `uuid`, on the ieee80211
phy device under the `rtw89_usb` attribute group.
This hardware identification information is essential for user-space
applications to uniquely identify, track, and manage specific Wi-Fi
adapters. For example, in automated factory provisioning or device
management systems, user-space tools rely on the EFUSE serial number and
UUID to bind configurations to specific physical adapters. Currently,
standard wireless APIs do not expose this low-level hardware
information, making these sysfs nodes the only viable solution for
user space to extract this data.
The attributes are gated behind a new RTW89_QUIRK_HW_INFO_SYSFS quirk,
enabled only for the VID 0x28de / PID 0x2432 device via the
dev_id_quirks field in rtw89_driver_info.
Example usage from user-space:
$ cat /sys/class/ieee80211/phy0/rtw89_usb/serial_number
3642000123
$ cat /sys/class/ieee80211/phy0/rtw89_usb/uuid
aaec2b7c-0a55-4727-8de0-b30febccbbaa
Cc: Elliot Saba <sabae@valvesoftware.com>
Cc: Charles Lohr <charlesl@valvesoftware.com>
Signed-off-by: Johnson Tsai <wenjie.tsai@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
v3:
- use dev default group (dev->group) instead
v2:
- add sysfs entries in /sys/class/ieee80211/phy0/rtw89_usb/
- add ABI Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
v1: RFC
---
.../ABI/testing/sysfs-class-ieee80211-rtw89 | 24 +++++++++
drivers/net/wireless/realtek/rtw89/core.h | 6 +++
drivers/net/wireless/realtek/rtw89/rtw8852c.c | 10 ++++
drivers/net/wireless/realtek/rtw89/rtw8852c.h | 6 ++-
.../net/wireless/realtek/rtw89/rtw8852cu.c | 12 ++++-
drivers/net/wireless/realtek/rtw89/usb.c | 52 +++++++++++++++++++
6 files changed, 108 insertions(+), 2 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
diff --git a/Documentation/ABI/testing/sysfs-class-ieee80211-rtw89 b/Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
new file mode 100644
index 000000000000..7dfdce08a42f
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
@@ -0,0 +1,24 @@
+What: /sys/class/ieee80211/phyX/rtw89_usb/serial_number
+Date: May 2026
+Contact: Johnson Tsai <wenjie.tsai@realtek.com>, linux-wireless@vger.kernel.org
+Description: (Read) Serial number burned into EFUSE of the RTL8852CU-based
+ USB Wi-Fi adapter. Only present on devices that set the
+ RTW89_QUIRK_HW_INFO_SYSFS quirk (currently VID 0x28de /
+ PID 0x2432).
+
+ Format: %10phN (5 raw bytes printed as 10 lowercase hex
+ digits, no separators).
+
+ Example: 3642000123
+
+What: /sys/class/ieee80211/phyX/rtw89_usb/uuid
+Date: May 2026
+Contact: Johnson Tsai <wenjie.tsai@realtek.com>, linux-wireless@vger.kernel.org
+Description: (Read) UUID burned into EFUSE of the RTL8852CU-based USB Wi-Fi
+ adapter. Only present on devices that set the
+ RTW89_QUIRK_HW_INFO_SYSFS quirk (currently VID 0x28de /
+ PID 0x2432).
+
+ Format: %pUb (RFC 4122 UUID in lowercase with hyphens).
+
+ Example: aaec2b7c-0a55-4727-8de0-b30febccbbaa
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 4c638c2bdc4f..5547888d7e67 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -3618,6 +3618,9 @@ struct rtw89_sta_link {
u32 data_tx_cnt_lmt:6;
};
+#define RTW89_EFUSE_SN_LEN 5
+#define RTW89_EFUSE_UUID_LEN 16
+
struct rtw89_efuse {
bool valid;
bool power_k_valid;
@@ -3628,6 +3631,8 @@ struct rtw89_efuse {
u8 adc_td;
u8 bt_setting_2;
u8 bt_setting_3;
+ u8 sn[RTW89_EFUSE_SN_LEN];
+ u8 uuid[RTW89_EFUSE_UUID_LEN];
};
struct rtw89_phy_rate_pattern {
@@ -5376,6 +5381,7 @@ enum rtw89_quirks {
RTW89_QUIRK_PCI_BER,
RTW89_QUIRK_THERMAL_PROT_120C,
RTW89_QUIRK_THERMAL_PROT_110C,
+ RTW89_QUIRK_HW_INFO_SYSFS,
NUM_OF_RTW89_QUIRKS,
};
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c
index 7bb1264bcaef..3861cce42b1b 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c
@@ -621,6 +621,15 @@ static void rtw8852c_efuse_parsing_gain_offset(struct rtw89_dev *rtwdev,
gain->offset_valid = valid;
}
+static void rtw8852c_efuse_copy_sn_uuid_usb(struct rtw89_dev *rtwdev,
+ const struct rtw8852c_efuse *map)
+{
+ struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+ memcpy(efuse->sn, map->u.sn, sizeof(efuse->sn));
+ memcpy(efuse->uuid, map->u.uuid, sizeof(efuse->uuid));
+}
+
static int rtw8852c_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map,
enum rtw89_efuse_block block)
{
@@ -640,6 +649,7 @@ static int rtw8852c_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map,
break;
case RTW89_HCI_TYPE_USB:
ether_addr_copy(efuse->addr, map->u.mac_addr);
+ rtw8852c_efuse_copy_sn_uuid_usb(rtwdev, map);
break;
default:
return -ENOTSUPP;
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.h b/drivers/net/wireless/realtek/rtw89/rtw8852c.h
index 8585921ac6c4..b1d7c354c18e 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852c.h
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.h
@@ -13,7 +13,11 @@
struct rtw8852c_u_efuse {
u8 rsvd[0x88];
u8 mac_addr[ETH_ALEN];
-};
+ u8 rsvd1[8];
+ u8 sn[RTW89_EFUSE_SN_LEN];
+ u8 rsvd2[29];
+ u8 uuid[RTW89_EFUSE_UUID_LEN];
+} __packed;
struct rtw8852c_e_efuse {
u8 mac_addr[ETH_ALEN];
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
index 8f89f9a31455..81ee96b0a048 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
@@ -39,6 +39,16 @@ static const struct rtw89_driver_info rtw89_8852cu_info = {
},
};
+static const struct rtw89_driver_info rtw89_8852cu_valve_info = {
+ .chip = &rtw8852c_chip_info,
+ .variant = NULL,
+ .quirks = NULL,
+ .dev_id_quirks = BIT(RTW89_QUIRK_HW_INFO_SYSFS),
+ .bus = {
+ .usb = &rtw8852c_usb_info,
+ },
+};
+
static const struct usb_device_id rtw_8852cu_id_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(0x0411, 0x03a6, 0xff, 0xff, 0xff),
.driver_info = (kernel_ulong_t)&rtw89_8852cu_info },
@@ -53,7 +63,7 @@ static const struct usb_device_id rtw_8852cu_id_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(0x0db0, 0x991d, 0xff, 0xff, 0xff),
.driver_info = (kernel_ulong_t)&rtw89_8852cu_info },
{ USB_DEVICE_AND_INTERFACE_INFO(0x28de, 0x2432, 0xff, 0xff, 0xff),
- .driver_info = (kernel_ulong_t)&rtw89_8852cu_info },
+ .driver_info = (kernel_ulong_t)&rtw89_8852cu_valve_info },
{ USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x8206, 0xff, 0xff, 0xff),
.driver_info = (kernel_ulong_t)&rtw89_8852cu_info },
{ USB_DEVICE_AND_INTERFACE_INFO(0x35b2, 0x0502, 0xff, 0xff, 0xff),
diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c
index 67ebf2d9bb7d..aafa8b9e93df 100644
--- a/drivers/net/wireless/realtek/rtw89/usb.c
+++ b/drivers/net/wireless/realtek/rtw89/usb.c
@@ -1150,6 +1150,56 @@ static int rtw89_usb_switch_mode(struct rtw89_dev *rtwdev)
return rtw89_usb_switch_mode_be(rtwdev);
}
+static ssize_t serial_number_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wiphy *wiphy = container_of(dev, struct wiphy, dev);
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct rtw89_dev *rtwdev = hw->priv;
+ struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+ return sysfs_emit(buf, "%*phN\n",
+ (int)sizeof(efuse->sn), efuse->sn);
+}
+static DEVICE_ATTR_RO(serial_number);
+
+static ssize_t uuid_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wiphy *wiphy = container_of(dev, struct wiphy, dev);
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct rtw89_dev *rtwdev = hw->priv;
+ struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+ return sysfs_emit(buf, "%pUb\n", efuse->uuid);
+}
+static DEVICE_ATTR_RO(uuid);
+
+static struct attribute *rtw89_usb_attrs[] = {
+ &dev_attr_serial_number.attr,
+ &dev_attr_uuid.attr,
+ NULL,
+};
+
+static bool rtw89_usb_group_visible(struct kobject *kobj)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct wiphy *wiphy = container_of(dev, struct wiphy, dev);
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct rtw89_dev *rtwdev = hw->priv;
+
+ return test_bit(RTW89_QUIRK_HW_INFO_SYSFS, rtwdev->quirks);
+}
+
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(rtw89_usb);
+
+static const struct attribute_group rtw89_usb_group = {
+ .name = "rtw89_usb",
+ .attrs = rtw89_usb_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(rtw89_usb),
+};
+__ATTRIBUTE_GROUPS(rtw89_usb);
+
int rtw89_usb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
@@ -1171,6 +1221,8 @@ int rtw89_usb_probe(struct usb_interface *intf,
rtwusb->rtwdev = rtwdev;
rtwusb->info = info->bus.usb;
+ rtwdev->hw->wiphy->dev.groups = rtw89_usb_groups;
+
rtwdev->hci.ops = &rtw89_usb_ops;
rtwdev->hci.type = RTW89_HCI_TYPE_USB;
rtwdev->hci.tx_rpt_enabled = true;
--
2.25.1
^ permalink raw reply related
* [PATCH v3 rtw-next 1/2] wifi: rtw89: add dev_id_quirks to driver_info for per-device quirk control
From: Ping-Ke Shih @ 2026-05-29 7:50 UTC (permalink / raw)
To: linux-wireless
Cc: driver-core, johannes, mh_chen, wenjie.tsai, charlesl, sabae
In-Reply-To: <20260529075032.16807-1-pkshih@realtek.com>
From: Johnson Tsai <wenjie.tsai@realtek.com>
Add a dev_id_quirks field to rtw89_driver_info so that per-device
(VID/PID) quirks can be expressed independently of chip-level
default_quirks. Apply the bitmap in rtw89_alloc_ieee80211_hw() so
both USB and PCI probes benefit automatically.
All existing driver_info structs initialize dev_id_quirks to 0;
no behavior change.
Signed-off-by: Johnson Tsai <wenjie.tsai@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
v3: no change; To easier maintenance driver, keep = 0 for all chips.
v2: add by v2
---
drivers/net/wireless/realtek/rtw89/core.c | 9 +++++++--
drivers/net/wireless/realtek/rtw89/core.h | 4 ++--
drivers/net/wireless/realtek/rtw89/pci.c | 3 +--
drivers/net/wireless/realtek/rtw89/rtw8851be.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8851bu.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852ae.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852au.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852be.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852bte.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852bu.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852ce.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852cu.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8922ae.c | 2 ++
drivers/net/wireless/realtek/rtw89/rtw8922au.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8922de.c | 2 ++
drivers/net/wireless/realtek/rtw89/usb.c | 3 +--
16 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index c1df3e2ba11e..68dad6090f87 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -7532,9 +7532,11 @@ EXPORT_SYMBOL(rtw89_core_unregister);
struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device,
u32 bus_data_size,
- const struct rtw89_chip_info *chip,
- const struct rtw89_chip_variant *variant)
+ const struct rtw89_driver_info *info)
{
+ const unsigned long *dev_id_quirks = &info->dev_id_quirks;
+ const struct rtw89_chip_variant *variant = info->variant;
+ const struct rtw89_chip_info *chip = info->chip;
struct rtw89_fw_info early_fw = {};
const struct firmware *firmware;
struct ieee80211_hw *hw;
@@ -7600,6 +7602,9 @@ struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device,
rtwdev->fw.fw_format = fw_format;
rtwdev->support_mlo = support_mlo;
+ bitmap_or(rtwdev->quirks, rtwdev->quirks, dev_id_quirks,
+ NUM_OF_RTW89_QUIRKS);
+
rtw89_debug(rtwdev, RTW89_DBG_CHAN, "probe driver %s chanctx\n",
no_chanctx ? "without" : "with");
rtw89_debug(rtwdev, RTW89_DBG_CHAN, "probe driver %s MLO cap\n",
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index fb32d2bd9cb0..4c638c2bdc4f 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -4803,6 +4803,7 @@ struct rtw89_driver_info {
const struct rtw89_chip_info *chip;
const struct rtw89_chip_variant *variant;
const struct dmi_system_id *quirks;
+ unsigned long dev_id_quirks; /* bitmap of rtw89_quirks */
union rtw89_bus_info bus;
};
@@ -8129,8 +8130,7 @@ int rtw89_core_register(struct rtw89_dev *rtwdev);
void rtw89_core_unregister(struct rtw89_dev *rtwdev);
struct rtw89_dev *rtw89_alloc_ieee80211_hw(struct device *device,
u32 bus_data_size,
- const struct rtw89_chip_info *chip,
- const struct rtw89_chip_variant *variant);
+ const struct rtw89_driver_info *info);
void rtw89_free_ieee80211_hw(struct rtw89_dev *rtwdev);
u8 rtw89_acquire_mac_id(struct rtw89_dev *rtwdev);
void rtw89_release_mac_id(struct rtw89_dev *rtwdev, u8 mac_id);
diff --git a/drivers/net/wireless/realtek/rtw89/pci.c b/drivers/net/wireless/realtek/rtw89/pci.c
index fe1152c560bd..102bae488180 100644
--- a/drivers/net/wireless/realtek/rtw89/pci.c
+++ b/drivers/net/wireless/realtek/rtw89/pci.c
@@ -4772,8 +4772,7 @@ int rtw89_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
info = (const struct rtw89_driver_info *)id->driver_data;
rtwdev = rtw89_alloc_ieee80211_hw(&pdev->dev,
- sizeof(struct rtw89_pci),
- info->chip, info->variant);
+ sizeof(struct rtw89_pci), info);
if (!rtwdev) {
dev_err(&pdev->dev, "failed to allocate hw\n");
return -ENOMEM;
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851be.c b/drivers/net/wireless/realtek/rtw89/rtw8851be.c
index ce59ac9f56ba..640672eb0d26 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8851be.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8851be.c
@@ -73,6 +73,7 @@ static const struct rtw89_driver_info rtw89_8851be_info = {
.chip = &rtw8851b_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8851b_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
index 6a8d31544314..e7933174398e 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
@@ -31,6 +31,7 @@ static const struct rtw89_driver_info rtw89_8851bu_info = {
.chip = &rtw8851b_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.usb = &rtw8851b_usb_info,
}
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c
index 9e05e831569d..64306cdc1ee4 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852ae.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852ae.c
@@ -71,6 +71,7 @@ static const struct rtw89_driver_info rtw89_8852ae_info = {
.chip = &rtw8852a_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8852a_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852au.c b/drivers/net/wireless/realtek/rtw89/rtw8852au.c
index 4cced4619b7d..29b7f7769370 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852au.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852au.c
@@ -33,6 +33,7 @@ static const struct rtw89_driver_info rtw89_8852au_info = {
.chip = &rtw8852a_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.usb = &rtw8852a_usb_info,
}
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852be.c b/drivers/net/wireless/realtek/rtw89/rtw8852be.c
index 12db0d0be547..5bc0a6a99d1d 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852be.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852be.c
@@ -73,6 +73,7 @@ static const struct rtw89_driver_info rtw89_8852be_info = {
.chip = &rtw8852b_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8852b_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c
index 8c995aa95325..49a72ca835ac 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852bte.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852bte.c
@@ -79,6 +79,7 @@ static const struct rtw89_driver_info rtw89_8852bte_info = {
.chip = &rtw8852bt_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8852bt_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
index 37111fed276f..308d3d570ff3 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
@@ -31,6 +31,7 @@ static const struct rtw89_driver_info rtw89_8852bu_info = {
.chip = &rtw8852b_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.usb = &rtw8852b_usb_info,
}
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c
index 150fed189414..3c64c0539205 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852ce.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852ce.c
@@ -102,6 +102,7 @@ static const struct rtw89_driver_info rtw89_8852ce_info = {
.chip = &rtw8852c_chip_info,
.variant = NULL,
.quirks = rtw8852c_pci_quirks,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8852c_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
index 790fd1dec66d..8f89f9a31455 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
@@ -33,6 +33,7 @@ static const struct rtw89_driver_info rtw89_8852cu_info = {
.chip = &rtw8852c_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.usb = &rtw8852c_usb_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c
index 90c62b757c57..5527a8db393b 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922ae.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922ae.c
@@ -77,6 +77,7 @@ static const struct rtw89_driver_info rtw89_8922ae_info = {
.chip = &rtw8922a_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8922a_pci_info,
},
@@ -86,6 +87,7 @@ static const struct rtw89_driver_info rtw89_8922ae_vs_info = {
.chip = &rtw8922a_chip_info,
.variant = &rtw8922ae_vs_variant,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8922a_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922au.c b/drivers/net/wireless/realtek/rtw89/rtw8922au.c
index 347bde171391..2b81de501d62 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922au.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922au.c
@@ -32,6 +32,7 @@ static const struct rtw89_driver_info rtw89_8922au_info = {
.chip = &rtw8922a_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.usb = &rtw8922a_usb_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922de.c b/drivers/net/wireless/realtek/rtw89/rtw8922de.c
index f144e7fc76de..a1a81c338be3 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922de.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922de.c
@@ -73,6 +73,7 @@ static const struct rtw89_driver_info rtw89_8922de_vs_info = {
.chip = &rtw8922d_chip_info,
.variant = &rtw8922de_vs_variant,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8922d_pci_info,
},
@@ -82,6 +83,7 @@ static const struct rtw89_driver_info rtw89_8922de_info = {
.chip = &rtw8922d_chip_info,
.variant = NULL,
.quirks = NULL,
+ .dev_id_quirks = 0,
.bus = {
.pci = &rtw8922d_pci_info,
},
diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c
index 198378018062..67ebf2d9bb7d 100644
--- a/drivers/net/wireless/realtek/rtw89/usb.c
+++ b/drivers/net/wireless/realtek/rtw89/usb.c
@@ -1161,8 +1161,7 @@ int rtw89_usb_probe(struct usb_interface *intf,
info = (const struct rtw89_driver_info *)id->driver_info;
rtwdev = rtw89_alloc_ieee80211_hw(&intf->dev,
- sizeof(struct rtw89_usb),
- info->chip, info->variant);
+ sizeof(struct rtw89_usb), info);
if (!rtwdev) {
dev_err(&intf->dev, "failed to allocate hw\n");
return -ENOMEM;
--
2.25.1
^ permalink raw reply related
* [PATCH v3 rtw-next 0/2] wifi: rtw89: usb: read serial_number and uuid via sysfs
From: Ping-Ke Shih @ 2026-05-29 7:50 UTC (permalink / raw)
To: linux-wireless
Cc: driver-core, johannes, mh_chen, wenjie.tsai, charlesl, sabae
As what we learned from RFC v1 and v2, add sysfs entries via dev default
group (dev->groups)
- /sys/class/ieee80211/phy0/rtw89_usb/serial_number
- /sys/class/ieee80211/phy0/rtw89_usb/uuid
These entries only present if specific USB device is in use. We add a
quirk as a flag for this.
Above info is fully documented in
Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
v3:
- create sysfs by dev default group (dev->groups) instead of
sysfs_create_groups() to avoid racing with user space
v2:
- add specific sysfs entries in /sys/class/ieee80211/phy0/rtw89_usb/
* serial_number and uuid
- add Documentation/ABI/testing/sysfs-class-ieee80211-rtw89 to
describe ABI
- add sysfs entries by quirk according to specific device ID
- include patch 1/2 which does quirk infrastructure by device ID
- https://lore.kernel.org/linux-wireless/20260525085148.35180-1-pkshih@realtek.com/
v1 (RFC):
- https://lore.kernel.org/linux-wireless/20260519072415.25746-1-pkshih@realtek.com/
Johnson Tsai (2):
wifi: rtw89: add dev_id_quirks to driver_info for per-device quirk
control
wifi: rtw89: usb: add serial_number and uuid sysfs attributes for
0x28de:0x2432
.../ABI/testing/sysfs-class-ieee80211-rtw89 | 24 ++++++++
drivers/net/wireless/realtek/rtw89/core.c | 9 ++-
drivers/net/wireless/realtek/rtw89/core.h | 10 +++-
drivers/net/wireless/realtek/rtw89/pci.c | 3 +-
.../net/wireless/realtek/rtw89/rtw8851be.c | 1 +
.../net/wireless/realtek/rtw89/rtw8851bu.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852ae.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852au.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852be.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852bte.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852bu.c | 1 +
drivers/net/wireless/realtek/rtw89/rtw8852c.c | 10 ++++
drivers/net/wireless/realtek/rtw89/rtw8852c.h | 6 +-
.../net/wireless/realtek/rtw89/rtw8852ce.c | 1 +
.../net/wireless/realtek/rtw89/rtw8852cu.c | 13 ++++-
.../net/wireless/realtek/rtw89/rtw8922ae.c | 2 +
.../net/wireless/realtek/rtw89/rtw8922au.c | 1 +
.../net/wireless/realtek/rtw89/rtw8922de.c | 2 +
drivers/net/wireless/realtek/rtw89/usb.c | 55 ++++++++++++++++++-
19 files changed, 133 insertions(+), 10 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-ieee80211-rtw89
base-commit: 8368970b62404ce2ce70d04c1cfff62700d7d8d5
--
2.25.1
^ permalink raw reply
* [wireless-next:main 10/20] htmldocs: Documentation/networking/checksum-offloads:157: ./include/linux/skbuff.h:181: WARNING: Failed to create a cross reference. A title or caption not found: 'crc' [ref.ref]
From: kernel test robot @ 2026-05-29 7:30 UTC (permalink / raw)
To: Jakub Kicinski
Cc: oe-kbuild-all, Johannes Berg, linux-wireless, Willem de Bruijn,
linux-doc
tree: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
head: e7d6bd24e883bf7c328d73c99bf6bcde19bf5e61
commit: 25bfb3a8edcbadf4d9d27450c547a631e7513f14 [10/20] docs: net: render the checksum comment in checksum-offloads.rst
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260529/202605290940.E4u11iiQ-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605290940.E4u11iiQ-lkp@intel.com/
All warnings (new ones prefixed by >>):
Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:45: ERROR: Unknown target name: "network flags". [docutils]
Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:50: ERROR: Unknown target name: "scope flags". [docutils]
Documentation/userspace-api/landlock:550: ./include/uapi/linux/landlock.h:24: ERROR: Unknown target name: "filesystem flags". [docutils]
Documentation/userspace-api/landlock:559: ./include/uapi/linux/landlock.h:168: ERROR: Unknown target name: "filesystem flags". [docutils]
Documentation/userspace-api/landlock:559: ./include/uapi/linux/landlock.h:191: ERROR: Unknown target name: "network flags". [docutils]
>> Documentation/networking/checksum-offloads:157: ./include/linux/skbuff.h:181: WARNING: Failed to create a cross reference. A title or caption not found: 'crc' [ref.ref]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH wireless-next 2/2] wifi: mac80211: remove 5/10 MHz channel code
From: Johannes Berg @ 2026-05-29 6:40 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260529064502.37422-4-johannes@sipsolutions.net>
From: Johannes Berg <johannes.berg@intel.com>
Now that cfg80211 refuses all attempts to use 5/10 MHz
channels, all of this code is unreachable; remove it.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 2 --
net/mac80211/chan.c | 4 +---
net/mac80211/ibss.c | 31 ++-----------------------------
net/mac80211/mesh.c | 36 +++++++++---------------------------
net/mac80211/mesh_plink.c | 2 --
net/mac80211/mlme.c | 6 ++----
net/mac80211/offchannel.c | 4 +---
net/mac80211/rate.c | 8 +-------
net/mac80211/spectmgmt.c | 10 ----------
net/mac80211/util.c | 7 -------
10 files changed, 16 insertions(+), 94 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index fb4c1c298159..1554e31f0029 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -4591,8 +4591,6 @@ static int ieee80211_set_csa_beacon(struct ieee80211_link_data *link_data,
cfg80211_get_chandef_type(&sdata->u.ibss.chandef))
return -EINVAL;
break;
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
case NL80211_CHAN_WIDTH_20_NOHT:
case NL80211_CHAN_WIDTH_20:
break;
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 23d46cd57137..5152b84a3357 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -642,9 +642,7 @@ __ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
lockdep_assert_wiphy(local->hw.wiphy);
/* don't optimize non-20MHz based and radar_enabled confs */
- if (ctx->conf.def.width == NL80211_CHAN_WIDTH_5 ||
- ctx->conf.def.width == NL80211_CHAN_WIDTH_10 ||
- ctx->conf.def.width == NL80211_CHAN_WIDTH_1 ||
+ if (ctx->conf.def.width == NL80211_CHAN_WIDTH_1 ||
ctx->conf.def.width == NL80211_CHAN_WIDTH_2 ||
ctx->conf.def.width == NL80211_CHAN_WIDTH_4 ||
ctx->conf.def.width == NL80211_CHAN_WIDTH_8 ||
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 08690342cfaa..d0fd6054f182 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -167,8 +167,6 @@ ieee80211_ibss_build_presp(struct ieee80211_sub_if_data *sdata,
/* add HT capability and information IEs */
if (chandef->width != NL80211_CHAN_WIDTH_20_NOHT &&
- chandef->width != NL80211_CHAN_WIDTH_5 &&
- chandef->width != NL80211_CHAN_WIDTH_10 &&
sband->ht_cap.ht_supported) {
struct ieee80211_sta_ht_cap ht_cap;
@@ -259,9 +257,7 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata,
chan = chanreq.oper.chan;
if (!cfg80211_reg_can_beacon(local->hw.wiphy, &chanreq.oper,
NL80211_IFTYPE_ADHOC)) {
- if (chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- chanreq.oper.width == NL80211_CHAN_WIDTH_10 ||
- chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
+ if (chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
chanreq.oper.width == NL80211_CHAN_WIDTH_20) {
sdata_info(sdata,
"Failed to join IBSS, beacons forbidden\n");
@@ -405,12 +401,6 @@ static void ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata,
chan_type = cfg80211_get_chandef_type(&sdata->u.ibss.chandef);
cfg80211_chandef_create(&chandef, cbss->channel, chan_type);
break;
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
- cfg80211_chandef_create(&chandef, cbss->channel,
- NL80211_CHAN_NO_HT);
- chandef.width = sdata->u.ibss.chandef.width;
- break;
case NL80211_CHAN_WIDTH_80:
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_160:
@@ -762,8 +752,6 @@ ieee80211_ibss_process_chanswitch(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(sdata->local->hw.wiphy);
switch (ifibss->chandef.width) {
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
case NL80211_CHAN_WIDTH_20_NOHT:
conn.mode = IEEE80211_CONN_MODE_LEGACY;
fallthrough;
@@ -811,19 +799,6 @@ ieee80211_ibss_process_chanswitch(struct ieee80211_sub_if_data *sdata,
cfg80211_chandef_create(¶ms.chandef, params.chandef.chan,
ch_type);
break;
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
- if (params.chandef.width != ifibss->chandef.width) {
- sdata_info(sdata,
- "IBSS %pM received channel switch from incompatible channel width (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n",
- ifibss->bssid,
- params.chandef.chan->center_freq,
- params.chandef.width,
- params.chandef.center_freq1,
- params.chandef.center_freq2);
- goto disconnect;
- }
- break;
default:
/* should not happen, conn_flags should prevent VHT modes. */
WARN_ON(1);
@@ -1005,9 +980,7 @@ static void ieee80211_update_sta_info(struct ieee80211_sub_if_data *sdata,
}
if (sta && elems->ht_operation && elems->ht_cap_elem &&
- sdata->u.ibss.chandef.width != NL80211_CHAN_WIDTH_20_NOHT &&
- sdata->u.ibss.chandef.width != NL80211_CHAN_WIDTH_5 &&
- sdata->u.ibss.chandef.width != NL80211_CHAN_WIDTH_10) {
+ sdata->u.ibss.chandef.width != NL80211_CHAN_WIDTH_20_NOHT) {
/* we both use HT */
struct ieee80211_ht_cap htcap_ie;
struct cfg80211_chan_def chandef;
diff --git a/net/mac80211/mesh.c b/net/mac80211/mesh.c
index 04578447df9b..d4507e4e6ec1 100644
--- a/net/mac80211/mesh.c
+++ b/net/mac80211/mesh.c
@@ -439,9 +439,7 @@ int mesh_add_ht_cap_ie(struct ieee80211_sub_if_data *sdata,
return 0;
if (!sband->ht_cap.ht_supported ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
if (skb_tailroom(skb) < 2 + sizeof(struct ieee80211_ht_cap))
@@ -480,9 +478,7 @@ int mesh_add_ht_oper_ie(struct ieee80211_sub_if_data *sdata,
return 0;
if (!ht_cap->ht_supported ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
if (skb_tailroom(skb) < 2 + sizeof(struct ieee80211_ht_operation))
@@ -511,9 +507,7 @@ int mesh_add_vht_cap_ie(struct ieee80211_sub_if_data *sdata,
return 0;
if (!sband->vht_cap.vht_supported ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
if (skb_tailroom(skb) < 2 + sizeof(struct ieee80211_vht_cap))
@@ -552,9 +546,7 @@ int mesh_add_vht_oper_ie(struct ieee80211_sub_if_data *sdata,
return 0;
if (!vht_cap->vht_supported ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
if (skb_tailroom(skb) < 2 + sizeof(struct ieee80211_vht_operation))
@@ -576,9 +568,7 @@ int mesh_add_he_cap_ie(struct ieee80211_sub_if_data *sdata,
if (!sband)
return -EINVAL;
- if (sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ if (sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
return ieee80211_put_he_cap(skb, sdata, sband, NULL);
@@ -598,9 +588,7 @@ int mesh_add_he_oper_ie(struct ieee80211_sub_if_data *sdata,
he_cap = ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_MESH_POINT);
if (!he_cap ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
len = 2 + 1 + sizeof(struct ieee80211_he_operation);
@@ -648,9 +636,7 @@ int mesh_add_eht_cap_ie(struct ieee80211_sub_if_data *sdata,
if (!sband)
return -EINVAL;
- if (sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ if (sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
return ieee80211_put_eht_cap(skb, sdata, sband, NULL);
@@ -669,9 +655,7 @@ int mesh_add_eht_oper_ie(struct ieee80211_sub_if_data *sdata, struct sk_buff *sk
eht_cap = ieee80211_get_eht_iftype_cap(sband, NL80211_IFTYPE_MESH_POINT);
if (!eht_cap ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return 0;
len = 2 + 1 + offsetof(struct ieee80211_eht_operation, optional) +
@@ -729,9 +713,7 @@ ieee80211_mesh_update_bss_params(struct ieee80211_sub_if_data *sdata,
return;
if (!ieee80211_get_he_iftype_cap(sband, NL80211_IFTYPE_MESH_POINT) ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_10)
+ sdata->vif.bss_conf.chanreq.oper.width == NL80211_CHAN_WIDTH_20_NOHT)
return;
sdata->vif.bss_conf.he_support = true;
diff --git a/net/mac80211/mesh_plink.c b/net/mac80211/mesh_plink.c
index 37adb053213e..268857467f29 100644
--- a/net/mac80211/mesh_plink.c
+++ b/net/mac80211/mesh_plink.c
@@ -165,8 +165,6 @@ static u64 mesh_set_ht_prot_mode(struct ieee80211_sub_if_data *sdata)
switch (sdata->vif.bss_conf.chanreq.oper.width) {
case NL80211_CHAN_WIDTH_20_NOHT:
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
return 0;
default:
break;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index e8d6f6a95c0a..cfb5bc4eac69 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -6376,10 +6376,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
ret = ieee80211_link_use_channel(link, &chanreq,
IEEE80211_CHANCTX_SHARED);
- /* don't downgrade for 5/10/S1G MHz channels, though. */
- if (chanreq.oper.width == NL80211_CHAN_WIDTH_5 ||
- chanreq.oper.width == NL80211_CHAN_WIDTH_10 ||
- cfg80211_chandef_is_s1g(&chanreq.oper))
+ /* don't downgrade for S1G channels, though. */
+ if (cfg80211_chandef_is_s1g(&chanreq.oper))
return ret;
while (ret && chanreq.oper.width != NL80211_CHAN_WIDTH_20_NOHT) {
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c
index 8bec39b099a0..2bceb73717c6 100644
--- a/net/mac80211/offchannel.c
+++ b/net/mac80211/offchannel.c
@@ -355,9 +355,7 @@ static void _ieee80211_start_next_roc(struct ieee80211_local *local)
* Note: scan can't run, tmp_channel is what we use, so this
* must be the currently active channel.
*/
- roc->on_channel = roc->chan == local->hw.conf.chandef.chan &&
- local->hw.conf.chandef.width != NL80211_CHAN_WIDTH_5 &&
- local->hw.conf.chandef.width != NL80211_CHAN_WIDTH_10;
+ roc->on_channel = roc->chan == local->hw.conf.chandef.chan;
/* start this ROC */
ieee80211_recalc_idle(local);
diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c
index ba1a3aa3f5d4..64768abb0a5f 100644
--- a/net/mac80211/rate.c
+++ b/net/mac80211/rate.c
@@ -601,14 +601,8 @@ static void rate_idx_match_mask(s8 *rate_idx, u16 *rate_flags,
return;
/* if HT BSS, and we handle a data frame, also try HT rates */
- switch (chan_width) {
- case NL80211_CHAN_WIDTH_20_NOHT:
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
+ if (chan_width == NL80211_CHAN_WIDTH_20_NOHT)
return;
- default:
- break;
- }
*rate_idx = 0;
/* keep protection flags */
diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c
index e2eaf8d8d7ff..ec622750e1c9 100644
--- a/net/mac80211/spectmgmt.c
+++ b/net/mac80211/spectmgmt.c
@@ -329,16 +329,6 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
case -1:
cfg80211_chandef_create(&csa_ie->chanreq.oper, new_chan,
NL80211_CHAN_NO_HT);
- /* keep width for 5/10 MHz channels */
- switch (sdata->vif.bss_conf.chanreq.oper.width) {
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
- csa_ie->chanreq.oper.width =
- sdata->vif.bss_conf.chanreq.oper.width;
- break;
- default:
- break;
- }
break;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 2a7ab269687a..f6d4ae4127c8 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3816,13 +3816,6 @@ void ieee80211_chandef_downgrade(struct cfg80211_chan_def *c,
conn->mode = IEEE80211_CONN_MODE_S1G;
conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
break;
- case NL80211_CHAN_WIDTH_5:
- case NL80211_CHAN_WIDTH_10:
- WARN_ON_ONCE(1);
- /* keep c->width */
- conn->mode = IEEE80211_CONN_MODE_LEGACY;
- conn->bw_limit = IEEE80211_CONN_BW_LIMIT_20;
- break;
}
if (new_primary_width != NL80211_CHAN_WIDTH_20_NOHT) {
--
2.53.0
^ permalink raw reply related
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