* [PATCH wireless-next] wifi: mac80211: correct ieee80211-{s1g/eht}.h include guard comments
From: Lachlan Hodges @ 2026-01-30 0:53 UTC (permalink / raw)
To: johannes; +Cc: linux-wireless, arien.judge, Lachlan Hodges
After the split of ieee80211.h some include guard comments weren't
updated, update them to their new file names.
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
include/linux/ieee80211-eht.h | 2 +-
include/linux/ieee80211-s1g.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index f9782e46c5e5..620dad3995cc 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -1179,4 +1179,4 @@ static inline u32 ieee80211_eml_trans_timeout_in_us(u16 eml_cap)
_data + ieee80211_mle_common_size(_data),\
_len - ieee80211_mle_common_size(_data))
-#endif /* LINUX_IEEE80211_H */
+#endif /* LINUX_IEEE80211_EHT_H */
diff --git a/include/linux/ieee80211-s1g.h b/include/linux/ieee80211-s1g.h
index 5b9ed2dcc00e..22dde4cbc1b0 100644
--- a/include/linux/ieee80211-s1g.h
+++ b/include/linux/ieee80211-s1g.h
@@ -572,4 +572,4 @@ static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim,
}
}
-#endif /* LINUX_IEEE80211_H */
+#endif /* LINUX_IEEE80211_S1G_H */
--
2.43.0
^ permalink raw reply related
* RE: Problems building rtw89
From: Ping-Ke Shih @ 2026-01-30 0:36 UTC (permalink / raw)
To: Iohann Tachy, linux-wireless@vger.kernel.org
In-Reply-To: <CAPVS0eKn3zEyRekrQP_K3W5n5iVZ44nXkUAAE2=KkNcpq+LeYg@mail.gmail.com>
Iohann Tachy <iohann.tachy@gmail.com> wrote:
> Good morning,
>
> Currently there's an issue where it's not possible to build the rtw89 driver.
> The RTL8852BE card has a compatibility problem with Intel X99 chipset
> and a hacked rtw89 driver is needed to make it work.
>
> I kindly request to read the Github issue:
> https://github.com/lwfinger/rtw89/issues/396
A fix in Github is to disable 36-bit DMA [1]. I'd like to collect platform
info to add a quirk. Please share outputs of below commands
- dmidecode
- lspci -vt
- lspci -x
[1] https://github.com/a5a5aa555oo/rtw89-1/commit/629756ee0232089d298bb398790d4a745b950058
^ permalink raw reply
* [BUG] MT7996 does not send TXFREE for certain sent frames
From: Rory Little @ 2026-01-30 0:17 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee; +Cc: linux-wireless, Ben Greear
Hello all,
When transmitting to a specific AP, I have noticed that traffic on all
but one station stalls after a short period. After some poking at the
problem, I determined the issue to be that TXFREE messages were not
reporting freed MSDUs for the 'stalled' stations until every other MSDU
had been handled. These unfreed tokens seem to consistently be from WCID
!= 1. If there is a consistent Tx stream from WCID 1, this can cause
those MSDUs to stay unfreed indefinitely. I am not observing any
reported retries or Tx failures for these blocked MSDUs, and they appear
to be transmitted soon after the initial DMA mapping.
We are using the below diff to mitigate the issue, by blocking transmit
when specific txwi's are held by the NIC for too long. However, this
solution reduces the throughput of the interfaces, and probably isn't
viable for the main driver.
One last piece of information, for what its worth, is that this issue
was not triggering in older (pre-6.12) kernels due to an issue in
calculating EHT airtime in mac80211. It seems that TXQs regularly
failing the airtime fairness check helped to mitigate this issue.
Whether it was due to the reduced throughput, or due to preventing frame
aggregation, I can't say.
Please let me know if you would like any further information regarding
this problem.
- Rory
--
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c
b/drivers/net/wireless/mediatek/mt76/mac80211.c
index d0c522909e98..486145e51064 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -724,6 +724,8 @@ mt76_alloc_device(struct device *pdev, unsigned int
size,
INIT_LIST_HEAD(&dev->txwi_cache);
INIT_LIST_HEAD(&dev->rxwi_cache);
+ INIT_LIST_HEAD(&dev->token_queue);
+ dev->token_queue_tail = &dev->token_queue;
dev->token_size = dev->drv->token_size;
INIT_DELAYED_WORK(&dev->scan_work, mt76_scan_work);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h
b/drivers/net/wireless/mediatek/mt76/mt76.h
index 5e68efc367fc..c57827fc51fa 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -449,6 +449,8 @@ struct mt76_txwi_cache {
void *ptr;
};
+ unsigned long jiffies;
+
u8 qid;
};
@@ -959,6 +961,8 @@ struct mt76_dev {
spinlock_t token_lock;
struct idr token;
+ struct list_head token_queue;
+ struct list_head *token_queue_tail;
u16 wed_token_count;
u16 token_count;
u16 token_start;
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c
b/drivers/net/wireless/mediatek/mt76/tx.c
index 9ec6d0b53a84..495a5c4f931e 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -5,6 +5,11 @@
#include "mt76.h"
+static unsigned long tx_wait_thresh_ms = 100;
+module_param_named(tx_wait_thresh_ms, tx_wait_thresh_ms, ulong, 0644);
+MODULE_PARM_DESC(tx_wait_thresh_ms, "Time to wait for TXFREE before
flushing the tx queue.\n"
+ "0 to disable this behavior.");
+
static int
mt76_txq_get_qid(struct ieee80211_txq *txq)
{
@@ -850,9 +855,15 @@ int mt76_token_consume(struct mt76_dev *dev, struct
mt76_txwi_cache **ptxwi)
token = idr_alloc(&dev->token, *ptxwi, dev->token_start,
dev->token_start + dev->token_size,
GFP_ATOMIC);
- if (token >= dev->token_start)
+ if (token >= dev->token_start) {
dev->token_count++;
+ list_add(&(*ptxwi)->list, dev->token_queue_tail);
+ dev->token_queue_tail = &(*ptxwi)->list;
+
+ (*ptxwi)->jiffies = jiffies;
+ }
+
#ifdef CONFIG_NET_MEDIATEK_SOC_WED
if (mtk_wed_device_active(&dev->mmio.wed) &&
token >= dev->mmio.wed.wlan.token_start)
@@ -889,7 +900,7 @@ EXPORT_SYMBOL_GPL(mt76_rx_token_consume);
struct mt76_txwi_cache *
mt76_token_release(struct mt76_dev *dev, int token, bool *wake)
{
- struct mt76_txwi_cache *txwi;
+ struct mt76_txwi_cache *txwi, *oldest_txwi;
spin_lock_bh(&dev->token_lock);
@@ -897,6 +908,10 @@ mt76_token_release(struct mt76_dev *dev, int token,
bool *wake)
if (txwi) {
dev->token_count--;
+ if (dev->token_queue_tail == &txwi->list)
+ dev->token_queue_tail = txwi->list.prev;
+ list_del(&txwi->list);
+
#ifdef CONFIG_NET_MEDIATEK_SOC_WED
if (mtk_wed_device_active(&dev->mmio.wed) &&
token >= dev->mmio.wed.wlan.token_start &&
@@ -905,8 +920,13 @@ mt76_token_release(struct mt76_dev *dev, int token,
bool *wake)
#endif
}
- if (dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR &&
- dev->phy.q_tx[0]->blocked)
+ oldest_txwi = list_first_entry_or_null(&dev->token_queue, struct
mt76_txwi_cache, list);
+
+ if (tx_wait_thresh_ms && oldest_txwi &&
+ time_is_before_jiffies(oldest_txwi->jiffies + (HZ *
tx_wait_thresh_ms / 1000)))
+ __mt76_set_tx_blocked(dev, true);
+ else if (dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR &&
+ dev->phy.q_tx[0]->blocked)
*wake = true;
spin_unlock_bh(&dev->token_lock);
^ permalink raw reply related
* [PATCH mt76] wifi: mt76: don't return TXQ when exceeding max non-AQL packets
From: David Bauer @ 2026-01-29 23:23 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno
Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek
mt76_txq_send_burst does check if the number of non-AQL frames exceeds
the maximum. In this case the queue is returned to ieee80211_return_txq
when iterating over the scheduled TXQs in mt76_txq_schedule_list.
This has the effect of inserting said TXQ at the head of the list. This
means the loop will get the same TXQ again, which will terminate the
scheduling round. TXQs following in the list thus never get scheduled
for transmission.
This can manifest in high latency low throughput or broken connections
for said STAs.
Check if the non-AQL packet count exceeds the limit and not return the
TXQ in this case.
Schedule all TXQs for the STA in case the non-AQL limit can be satisfied
again.
Signed-off-by: David Bauer <mail@david-bauer.net>
---
drivers/net/wireless/mediatek/mt76/tx.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 9ec6d0b53a84a..0753acf2eccb8 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -227,7 +227,9 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid,
struct sk_buff *skb)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+ struct ieee80211_sta *sta;
int pending;
+ int i;
if (!wcid || info->tx_time_est)
return;
@@ -235,6 +237,17 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid,
pending = atomic_dec_return(&wcid->non_aql_packets);
if (pending < 0)
atomic_cmpxchg(&wcid->non_aql_packets, pending, 0);
+
+ sta = wcid_to_sta(wcid);
+ if (!sta || pending != MT_MAX_NON_AQL_PKT - 1)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+ if (!sta->txq[i])
+ continue;
+
+ ieee80211_schedule_txq(dev->hw, sta->txq[i]);
+ }
}
void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *skb,
@@ -542,6 +555,9 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid)
if (!wcid || test_bit(MT_WCID_FLAG_PS, &wcid->flags))
continue;
+ if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT)
+ continue;
+
phy = mt76_dev_phy(dev, wcid->phy_idx);
if (test_bit(MT76_RESET, &phy->state) || phy->offchannel)
continue;
--
2.51.0
^ permalink raw reply related
* [PATCH iwlwifi-next 2/2] wifi: iwlwifi: mvm: pause TCM on fast resume
From: Miri Korenblit @ 2026-01-29 19:27 UTC (permalink / raw)
To: linux-wireless; +Cc: Chris Bainbridge, Johannes Berg
In-Reply-To: <20260129212650.a36482a60719.I5bf64a108ca39dacb5ca0dcd8b7258a3ce8db74c@changeid>
Not pausing it means that we can have the TCM work queued into a
non-freezable workqueue, which, in resume, is re-activated before the
driver's resume is called.
The TCM work might send commands to the FW before we resumed the device,
leading to an assert.
Closes: https://lore.kernel.org/linux-wireless/aTDoDiD55qlUZ0pn@debian.local/
Tested-by: Chris Bainbridge <chris.bainbridge@gmail.com>
Fixes: e8bb19c1d590 ("wifi: iwlwifi: support fast resume")
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/intel/iwlwifi/mvm/d3.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
index 07f1a84c274e..af1a45845999 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/d3.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2016-2017 Intel Deutschland GmbH
*/
@@ -3239,6 +3239,8 @@ void iwl_mvm_fast_suspend(struct iwl_mvm *mvm)
IWL_DEBUG_WOWLAN(mvm, "Starting fast suspend flow\n");
+ iwl_mvm_pause_tcm(mvm, true);
+
mvm->fast_resume = true;
set_bit(IWL_MVM_STATUS_IN_D3, &mvm->status);
@@ -3295,6 +3297,8 @@ int iwl_mvm_fast_resume(struct iwl_mvm *mvm)
mvm->trans->state = IWL_TRANS_NO_FW;
}
+ iwl_mvm_resume_tcm(mvm);
+
out:
clear_bit(IWL_MVM_STATUS_IN_D3, &mvm->status);
mvm->fast_resume = false;
--
2.34.1
^ permalink raw reply related
* [PATCH iwlwifi-next 1/2] wifi: iwlwifi: mld: cancel mlo_scan_start_wk
From: Miri Korenblit @ 2026-01-29 19:27 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
mlo_scan_start_wk is not canceled on disconnection. In fact, it is not
canceled anywhere except in the restart cleanup, where we don't really
have to.
This can cause an init-after-queue issue: if, for example, the work was
queued and then drv_change_interface got executed.
This can also cause use-after-free: if the work is executed after the
vif is freed.
Fixes: 9748ad82a9d9 ("wifi: iwlwifi: defer MLO scan after link activation")
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/intel/iwlwifi/mld/iface.c | 2 --
drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 2 ++
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/iface.c b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
index a5ececfc13e4..f15d1f5d1bf5 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/iface.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/iface.c
@@ -55,8 +55,6 @@ void iwl_mld_cleanup_vif(void *data, u8 *mac, struct ieee80211_vif *vif)
ieee80211_iter_keys(mld->hw, vif, iwl_mld_cleanup_keys_iter, NULL);
- wiphy_delayed_work_cancel(mld->wiphy, &mld_vif->mlo_scan_start_wk);
-
CLEANUP_STRUCT(mld_vif);
}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
index 55b484c16280..cd0dce8de856 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
@@ -1759,6 +1759,8 @@ static int iwl_mld_move_sta_state_down(struct iwl_mld *mld,
wiphy_work_cancel(mld->wiphy, &mld_vif->emlsr.unblock_tpt_wk);
wiphy_delayed_work_cancel(mld->wiphy,
&mld_vif->emlsr.check_tpt_wk);
+ wiphy_delayed_work_cancel(mld->wiphy,
+ &mld_vif->mlo_scan_start_wk);
iwl_mld_reset_cca_40mhz_workaround(mld, vif);
iwl_mld_smps_workaround(mld, vif, true);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH wireless-next v5 3/3] wifi: mac80211: add initial UHR support
From: Karthikeyan Kathirvel @ 2026-01-29 17:20 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
In-Reply-To: <0d07eb7e74de33ff36fa11efde1323c3f81fb0b3.camel@sipsolutions.net>
On 1/29/2026 10:15 PM, Johannes Berg wrote:
> On Thu, 2026-01-29 at 22:13 +0530, Karthikeyan Kathirvel wrote:
>>
>>> + if (ieee80211_uhr_capa_size_ok(data, len, true)) {
>>
>> here it should be false isn't it ? since here UHR cap processed for STA
>
> I _think_ I've been consistent in that 'ap=true' means it's processed
> *from* the AP?
>
> johannes
Got it, can you please modify it to from_ap in ieee80211_uhr_capa_size_ok()
/KK
^ permalink raw reply
* Re: [PATCH wireless-next v6 1/3] wifi: ieee80211: add some initial UHR definitions
From: Karthikeyan Kathirvel @ 2026-01-29 16:56 UTC (permalink / raw)
To: Johannes Berg, Pablo MARTIN-GOMEZ, linux-wireless
In-Reply-To: <587d823b0a900d02f78a552d9cb350ef4fc0766c.camel@sipsolutions.net>
On 1/29/2026 9:40 PM, Johannes Berg wrote:
> On Thu, 2026-01-29 at 17:03 +0100, Pablo MARTIN-GOMEZ wrote:
>>> +struct ieee80211_uhr_oper {
>>> + __le16 params;
>>> + u8 basic_mcs_nss_set[4];
>>> + u8 variable[];
>>> +} __packed;
>>> +
>>
>> I've just noticed that all the other PHYs have a type name
>> `ieee80211_*PHY*_operation`, it would make sense to keep the same
>> template for UHR.
>
> Meh. "_operation" makes those names get really long, and everyone
> probably already has a bunch of code depending on this, so I don't think
> I want to change it now.
>
> As for the _cap vs. _capa, maybe that's a better argument, but it's also
> not _quite_ the same between all the others (e.g. "...vht_cap" but
> "...he_cap_elem"), so... I don't think changing it now is worth the
> cost.
>
> Qualcomm folks, what do you think?
>
> johannes
>
Either way works for us, but I’d lean toward maintaining consistency
with the existing naming pattern. Developers often search for
ieee80211_*PHY*_operation when debugging or exploring supported PHY
types, and using a shortened _oper might make those searches less effective.
^ permalink raw reply
* Re: [PATCH wireless-next v5 3/3] wifi: mac80211: add initial UHR support
From: Johannes Berg @ 2026-01-29 16:45 UTC (permalink / raw)
To: Karthikeyan Kathirvel, linux-wireless
In-Reply-To: <104f3cd9-581c-402e-b4c3-898f1deaef5b@oss.qualcomm.com>
On Thu, 2026-01-29 at 22:13 +0530, Karthikeyan Kathirvel wrote:
>
> > + if (ieee80211_uhr_capa_size_ok(data, len, true)) {
>
> here it should be false isn't it ? since here UHR cap processed for STA
I _think_ I've been consistent in that 'ap=true' means it's processed
*from* the AP?
johannes
^ permalink raw reply
* Re: [PATCH wireless-next v5 3/3] wifi: mac80211: add initial UHR support
From: Karthikeyan Kathirvel @ 2026-01-29 16:43 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260129092039.09be5776db3d.Ieec940b58dbf8115dab7e1e24cb5513f52c8cb2f@changeid>
On 1/29/2026 1:50 PM, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
>
> Add support for making UHR connections and accepting AP
> stations with UHR support.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> v5:
> - parse UHR capa as from AP
> v4:
> - fix NPCA validation
> v3:
> - use uhr_oper instead of removed uhr_capa
> - fix indentation (Jeff Johnson)
> ---
> include/net/mac80211.h | 35 ++++++++++-
> net/mac80211/Makefile | 2 +-
> net/mac80211/cfg.c | 16 ++++-
> net/mac80211/ieee80211_i.h | 19 +++++-
> net/mac80211/main.c | 15 ++++-
> net/mac80211/mlme.c | 117 ++++++++++++++++++++++++++++++++++---
> net/mac80211/parse.c | 22 ++++++-
> net/mac80211/rx.c | 26 +++++++++
> net/mac80211/sta_info.c | 13 ++++-
> net/mac80211/sta_info.h | 80 ++++++++++++++++++-------
> net/mac80211/uhr.c | 29 +++++++++
> net/mac80211/util.c | 36 +++++++++++-
> 12 files changed, 370 insertions(+), 40 deletions(-)
> create mode 100644 net/mac80211/uhr.c
>
> diff --git a/include/net/mac80211.h b/include/net/mac80211.h
> index 36ae7fe9ddf3..7a55762f9af8 100644
> --- a/include/net/mac80211.h
> +++ b/include/net/mac80211.h
> @@ -7,7 +7,7 @@
> * Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2015 - 2017 Intel Deutschland GmbH
> - * Copyright (C) 2018 - 2025 Intel Corporation
> + * Copyright (C) 2018 - 2026 Intel Corporation
> */
>
> #ifndef MAC80211_H
> @@ -706,6 +706,7 @@ struct ieee80211_parsed_tpe {
> * @pwr_reduction: power constraint of BSS.
> * @eht_support: does this BSS support EHT
> * @epcs_support: does this BSS support EPCS
> + * @uhr_support: does this BSS support UHR
> * @csa_active: marks whether a channel switch is going on.
> * @mu_mimo_owner: indicates interface owns MU-MIMO capability
> * @chanctx_conf: The channel context this interface is assigned to, or %NULL
> @@ -832,6 +833,8 @@ struct ieee80211_bss_conf {
> u8 pwr_reduction;
> bool eht_support;
> bool epcs_support;
> + bool uhr_support;
> +
> bool csa_active;
>
> bool mu_mimo_owner;
> @@ -1598,6 +1601,7 @@ enum mac80211_rx_encoding {
> RX_ENC_VHT,
> RX_ENC_HE,
> RX_ENC_EHT,
> + RX_ENC_UHR,
> };
>
> /**
> @@ -1631,7 +1635,7 @@ enum mac80211_rx_encoding {
> * @antenna: antenna used
> * @rate_idx: index of data rate into band's supported rates or MCS index if
> * HT or VHT is used (%RX_FLAG_HT/%RX_FLAG_VHT)
> - * @nss: number of streams (VHT, HE and EHT only)
> + * @nss: number of streams (VHT, HE, EHT and UHR only)
> * @flag: %RX_FLAG_\*
> * @encoding: &enum mac80211_rx_encoding
> * @bw: &enum rate_info_bw
> @@ -1642,6 +1646,11 @@ enum mac80211_rx_encoding {
> * @eht: EHT specific rate information
> * @eht.ru: EHT RU, from &enum nl80211_eht_ru_alloc
> * @eht.gi: EHT GI, from &enum nl80211_eht_gi
> + * @uhr: UHR specific rate information
> + * @uhr.ru: UHR RU, from &enum nl80211_eht_ru_alloc
> + * @uhr.gi: UHR GI, from &enum nl80211_eht_gi
> + * @uhr.elr: UHR ELR MCS was used
> + * @uhr.im: UHR interference mitigation was used
> * @rx_flags: internal RX flags for mac80211
> * @ampdu_reference: A-MPDU reference number, must be a different value for
> * each A-MPDU but the same for each subframe within one A-MPDU
> @@ -1673,6 +1682,12 @@ struct ieee80211_rx_status {
> u8 ru:4;
> u8 gi:2;
> } eht;
> + struct {
> + u8 ru:4;
> + u8 gi:2;
> + u8 elr:1;
> + u8 im:1;
> + } uhr;
> };
> u8 rate_idx;
> u8 nss;
> @@ -2434,6 +2449,7 @@ struct ieee80211_sta_aggregates {
> * @he_cap: HE capabilities of this STA
> * @he_6ghz_capa: on 6 GHz, holds the HE 6 GHz band capabilities
> * @eht_cap: EHT capabilities of this STA
> + * @uhr_cap: UHR capabilities of this STA
> * @s1g_cap: S1G capabilities of this STA
> * @agg: per-link data for multi-link aggregation
> * @bandwidth: current bandwidth the station can receive with
> @@ -2457,6 +2473,7 @@ struct ieee80211_link_sta {
> struct ieee80211_sta_he_cap he_cap;
> struct ieee80211_he_6ghz_capa he_6ghz_capa;
> struct ieee80211_sta_eht_cap eht_cap;
> + struct ieee80211_sta_uhr_cap uhr_cap;
> struct ieee80211_sta_s1g_cap s1g_cap;
>
> struct ieee80211_sta_aggregates agg;
> @@ -7289,6 +7306,20 @@ ieee80211_get_eht_iftype_cap_vif(const struct ieee80211_supported_band *sband,
> return ieee80211_get_eht_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
> }
>
> +/**
> + * ieee80211_get_uhr_iftype_cap_vif - return UHR capabilities for sband/vif
> + * @sband: the sband to search for the iftype on
> + * @vif: the vif to get the iftype from
> + *
> + * Return: pointer to the struct ieee80211_sta_uhr_cap, or %NULL is none found
> + */
> +static inline const struct ieee80211_sta_uhr_cap *
> +ieee80211_get_uhr_iftype_cap_vif(const struct ieee80211_supported_band *sband,
> + struct ieee80211_vif *vif)
> +{
> + return ieee80211_get_uhr_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
> +}
> +
> /**
> * ieee80211_update_mu_groups - set the VHT MU-MIMO groud data
> *
> diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
> index a33884967f21..b0e392eb7753 100644
> --- a/net/mac80211/Makefile
> +++ b/net/mac80211/Makefile
> @@ -36,7 +36,7 @@ mac80211-y := \
> tdls.o \
> ocb.o \
> airtime.o \
> - eht.o
> + eht.o uhr.o
>
> mac80211-$(CONFIG_MAC80211_LEDS) += led.o
> mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
> diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
> index 964f440e31cd..f83dda0755a7 100644
> --- a/net/mac80211/cfg.c
> +++ b/net/mac80211/cfg.c
> @@ -5,7 +5,7 @@
> * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
> * Copyright 2013-2015 Intel Mobile Communications GmbH
> * Copyright (C) 2015-2017 Intel Deutschland GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> */
>
> #include <linux/ieee80211.h>
> @@ -1608,6 +1608,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
> link_conf->eht_mu_beamformer = false;
> }
>
> + if (params->uhr_oper) {
> + if (!link_conf->eht_support)
> + return -EOPNOTSUPP;
> +
> + link_conf->uhr_support = true;
> + }
> +
> if (sdata->vif.type == NL80211_IFTYPE_AP &&
> params->mbssid_config.tx_wdev) {
> err = ieee80211_set_ap_mbssid_options(sdata,
> @@ -2085,6 +2092,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
> params->vht_capa ||
> params->he_capa ||
> params->eht_capa ||
> + params->uhr_capa ||
> params->s1g_capa ||
> params->opmode_notif_used;
>
> @@ -2163,6 +2171,12 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
> params->eht_capa_len,
> link_sta);
>
> + if (params->uhr_capa)
> + ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
> + params->uhr_capa,
> + params->uhr_capa_len,
> + link_sta);
> +
> if (params->s1g_capa)
> ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
> link_sta);
> diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
> index dc757cb32974..b5e774ea6b07 100644
> --- a/net/mac80211/ieee80211_i.h
> +++ b/net/mac80211/ieee80211_i.h
> @@ -5,7 +5,7 @@
> * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
> * Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
> * Copyright 2013-2015 Intel Mobile Communications GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> */
>
> #ifndef IEEE80211_I_H
> @@ -394,9 +394,10 @@ enum ieee80211_conn_mode {
> IEEE80211_CONN_MODE_VHT,
> IEEE80211_CONN_MODE_HE,
> IEEE80211_CONN_MODE_EHT,
> + IEEE80211_CONN_MODE_UHR,
> };
>
> -#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_EHT
> +#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_UHR
>
> enum ieee80211_conn_bw_limit {
> IEEE80211_CONN_BW_LIMIT_20,
> @@ -1826,6 +1827,8 @@ struct ieee802_11_elems {
> const struct ieee80211_multi_link_elem *ml_epcs;
> const struct ieee80211_bandwidth_indication *bandwidth_indication;
> const struct ieee80211_ttlm_elem *ttlm[IEEE80211_TTLM_MAX_CNT];
> + const struct ieee80211_uhr_capa *uhr_capa;
> + const struct ieee80211_uhr_oper *uhr_oper;
>
> /* not the order in the psd values is per element, not per chandef */
> struct ieee80211_parsed_tpe tpe;
> @@ -1850,6 +1853,8 @@ struct ieee802_11_elems {
> u8 country_elem_len;
> u8 bssid_index_len;
> u8 eht_cap_len;
> + u8 uhr_capa_len;
> + u8 uhr_oper_len;
>
> /* mult-link element can be de-fragmented and thus u8 is not sufficient */
> size_t ml_basic_len;
> @@ -2693,6 +2698,9 @@ int ieee80211_put_eht_cap(struct sk_buff *skb,
> struct ieee80211_sub_if_data *sdata,
> const struct ieee80211_supported_band *sband,
> const struct ieee80211_conn_settings *conn);
> +int ieee80211_put_uhr_cap(struct sk_buff *skb,
> + struct ieee80211_sub_if_data *sdata,
> + const struct ieee80211_supported_band *sband);
> int ieee80211_put_reg_conn(struct sk_buff *skb,
> enum ieee80211_channel_flags flags);
>
> @@ -2868,6 +2876,13 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
> struct ieee80211_mgmt *mgmt, size_t len);
> void ieee80211_stop_mbssid(struct ieee80211_sub_if_data *sdata);
>
> +void
> +ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
> + struct ieee80211_supported_band *sband,
> + const struct ieee80211_uhr_capa *uhr_capa,
> + u8 uhr_capa_len,
> + struct link_sta_info *link_sta);
> +
> #if IS_ENABLED(CONFIG_MAC80211_KUNIT_TEST)
> #define EXPORT_SYMBOL_IF_MAC80211_KUNIT(sym) EXPORT_SYMBOL_IF_KUNIT(sym)
> #define VISIBLE_IF_MAC80211_KUNIT
> diff --git a/net/mac80211/main.c b/net/mac80211/main.c
> index b05e313c7f17..486a0bf3c0de 100644
> --- a/net/mac80211/main.c
> +++ b/net/mac80211/main.c
> @@ -5,7 +5,7 @@
> * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2017 Intel Deutschland GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> */
>
> #include <net/mac80211.h>
> @@ -1123,7 +1123,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
> int result, i;
> enum nl80211_band band;
> int channels, max_bitrates;
> - bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g;
> + bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g, supp_uhr;
> struct cfg80211_chan_def dflt_chandef = {};
>
> if (ieee80211_hw_check(hw, QUEUE_CONTROL) &&
> @@ -1237,6 +1237,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
> supp_he = false;
> supp_eht = false;
> supp_s1g = false;
> + supp_uhr = false;
> for (band = 0; band < NUM_NL80211_BANDS; band++) {
> const struct ieee80211_sband_iftype_data *iftd;
> struct ieee80211_supported_band *sband;
> @@ -1293,6 +1294,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
>
> supp_he = supp_he || iftd->he_cap.has_he;
> supp_eht = supp_eht || iftd->eht_cap.has_eht;
> + supp_uhr = supp_uhr || iftd->uhr_cap.has_uhr;
>
> if (band == NL80211_BAND_2GHZ)
> he_40_mhz_cap =
> @@ -1325,6 +1327,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
> if (WARN_ON(supp_eht && !supp_he))
> return -EINVAL;
>
> + /* UHR requires EHT support */
> + if (WARN_ON(supp_uhr && !supp_eht))
> + return -EINVAL;
> +
> if (!sband->ht_cap.ht_supported)
> continue;
>
> @@ -1437,6 +1443,11 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
> IEEE80211_EHT_PPE_THRES_MAX_LEN;
> }
>
> + if (supp_uhr)
> + local->scan_ies_len +=
> + 3 + sizeof(struct ieee80211_uhr_capa) +
> + sizeof(struct ieee80211_uhr_capa_phy);
> +
> if (!local->ops->hw_scan) {
> /* For hw_scan, driver needs to set these up. */
> local->hw.wiphy->max_scan_ssids = 4;
> diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
> index 6e468c4fcda2..df170556825f 100644
> --- a/net/mac80211/mlme.c
> +++ b/net/mac80211/mlme.c
> @@ -8,7 +8,7 @@
> * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2015 - 2017 Intel Deutschland GmbH
> - * Copyright (C) 2018 - 2025 Intel Corporation
> + * Copyright (C) 2018 - 2026 Intel Corporation
> */
>
> #include <linux/delay.h>
> @@ -162,6 +162,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
> const struct ieee80211_vht_operation *vht_oper = elems->vht_operation;
> const struct ieee80211_he_operation *he_oper = elems->he_operation;
> const struct ieee80211_eht_operation *eht_oper = elems->eht_operation;
> + const struct ieee80211_uhr_oper *uhr_oper = elems->uhr_oper;
> struct ieee80211_supported_band *sband =
> sdata->local->hw.wiphy->bands[channel->band];
> struct cfg80211_chan_def vht_chandef;
> @@ -192,7 +193,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
>
> /* get special 6 GHz case out of the way */
> if (sband->band == NL80211_BAND_6GHZ) {
> - enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_EHT;
> + enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_HIGHEST;
>
> /* this is an error */
> if (conn->mode < IEEE80211_CONN_MODE_HE)
> @@ -215,7 +216,9 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
> return IEEE80211_CONN_MODE_LEGACY;
> }
>
> - return mode;
> + if (mode <= IEEE80211_CONN_MODE_EHT)
> + return mode;
> + goto check_uhr;
> }
>
> /* now we have the progression HT, VHT, ... */
> @@ -340,7 +343,63 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
> *chandef = eht_chandef;
> }
>
> - return IEEE80211_CONN_MODE_EHT;
> +check_uhr:
> + if (conn->mode < IEEE80211_CONN_MODE_UHR || !uhr_oper)
> + return IEEE80211_CONN_MODE_EHT;
> +
> + /*
> + * In beacons we don't have all the data - but we know the size was OK,
> + * so if the size is valid as a non-beacon case, we have more data and
> + * can validate the NPCA parameters.
> + */
> + if (ieee80211_uhr_oper_size_ok((const void *)uhr_oper,
> + elems->uhr_oper_len,
> + false)) {
> + struct cfg80211_chan_def npca_chandef = *chandef;
> + const struct ieee80211_uhr_npca_info *npca;
> + const __le16 *dis_subch_bmap;
> + u16 punct = chandef->punctured, npca_punct;
> +
> + npca = ieee80211_uhr_npca_info(uhr_oper);
> + if (npca) {
> + int width = cfg80211_chandef_get_width(chandef);
> + u8 offs = le32_get_bits(npca->params,
> + IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS);
> + u32 cf1 = chandef->center_freq1;
> + bool pri_upper, npca_upper;
> +
> + pri_upper = chandef->chan->center_freq > cf1;
> + npca_upper = 20 * offs >= width / 2;
> +
> + if (20 * offs >= cfg80211_chandef_get_width(chandef) ||
> + pri_upper == npca_upper) {
> + sdata_info(sdata,
> + "AP UHR NPCA primary channel invalid, disabling UHR\n");
> + return IEEE80211_CONN_MODE_EHT;
> + }
> + }
> +
> + dis_subch_bmap = ieee80211_uhr_npca_dis_subch_bitmap(uhr_oper);
> +
> + if (dis_subch_bmap) {
> + npca_punct = get_unaligned_le16(dis_subch_bmap);
> + npca_chandef.punctured = npca_punct;
> + }
> +
> + /*
> + * must be a valid puncturing pattern for this channel as
> + * well as puncturing all subchannels that are already in
> + * the disabled subchannel bitmap on the primary channel
> + */
> + if (!cfg80211_chandef_valid(&npca_chandef) ||
> + ((punct & npca_punct) != punct)) {
> + sdata_info(sdata,
> + "AP UHR NPCA disabled subchannel bitmap invalid, disabling UHR\n");
> + return IEEE80211_CONN_MODE_EHT;
> + }
> + }
> +
> + return IEEE80211_CONN_MODE_UHR;
> }
>
> static bool
> @@ -1091,6 +1150,7 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
> IEEE80211_CONN_BW_LIMIT_160);
> break;
> case IEEE80211_CONN_MODE_EHT:
> + case IEEE80211_CONN_MODE_UHR:
> conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
> conn->bw_limit,
> IEEE80211_CONN_BW_LIMIT_320);
> @@ -1108,6 +1168,8 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
> set_bit(BSS_MEMBERSHIP_SELECTOR_HE_PHY, sta_selectors);
> if (conn->mode >= IEEE80211_CONN_MODE_EHT)
> set_bit(BSS_MEMBERSHIP_SELECTOR_EHT_PHY, sta_selectors);
> + if (conn->mode >= IEEE80211_CONN_MODE_UHR)
> + set_bit(BSS_MEMBERSHIP_SELECTOR_UHR_PHY, sta_selectors);
>
> /*
> * We do not support EPD or GLK so never add them.
> @@ -1155,6 +1217,11 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
> IEEE80211_CONN_BW_LIMIT_160);
> }
>
> + if (conn->mode >= IEEE80211_CONN_MODE_UHR &&
> + !cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
> + IEEE80211_CHAN_NO_UHR))
> + conn->mode = IEEE80211_CONN_MODE_EHT;
> +
> if (chanreq->oper.width != ap_chandef->width || ap_mode != conn->mode)
> link_id_info(sdata, link_id,
> "regulatory prevented using AP config, downgraded\n");
> @@ -1884,11 +1951,13 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
>
> /*
> * careful - need to know about all the present elems before
> - * calling ieee80211_assoc_add_ml_elem(), so add this one if
> - * we're going to put it after the ML element
> + * calling ieee80211_assoc_add_ml_elem(), so add these if
> + * we're going to put them after the ML element
> */
> if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
> ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_EHT_CAPABILITY);
> + if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_UHR)
> + ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_UHR_CAPA);
>
> if (link_id == assoc_data->assoc_link_id)
> ieee80211_assoc_add_ml_elem(sdata, skb, orig_capab, ext_capa,
> @@ -1901,6 +1970,9 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
> ieee80211_put_eht_cap(skb, sdata, sband,
> &assoc_data->link[link_id].conn);
>
> + if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_UHR)
> + ieee80211_put_uhr_cap(skb, sdata, sband);
> +
> if (sband->band == NL80211_BAND_S1GHZ) {
> ieee80211_add_aid_request_ie(sdata, skb);
> ieee80211_add_s1g_capab_ie(sdata, &sband->s1g_cap, skb);
> @@ -2135,6 +2207,9 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata,
> sizeof(struct ieee80211_eht_mcs_nss_supp) +
> IEEE80211_EHT_PPE_THRES_MAX_LEN;
>
> + size += 2 + 1 + sizeof(struct ieee80211_uhr_capa) +
> + sizeof(struct ieee80211_uhr_capa_phy);
> +
> return size;
> }
>
> @@ -5531,6 +5606,18 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
> bss_conf->epcs_support = false;
> }
>
> + if (elems->uhr_oper && elems->uhr_capa &&
> + link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) {
> + ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
> + elems->uhr_capa,
> + elems->uhr_capa_len,
> + link_sta);
> +
> + bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr;
> + } else {
> + bss_conf->uhr_support = false;
> + }
> +
> if (elems->s1g_oper &&
> link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G &&
> elems->s1g_capab)
> @@ -5821,6 +5908,7 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
> bool is_6ghz = sband->band == NL80211_BAND_6GHZ;
> const struct ieee80211_sta_he_cap *he_cap;
> const struct ieee80211_sta_eht_cap *eht_cap;
> + const struct ieee80211_sta_uhr_cap *uhr_cap;
> struct ieee80211_sta_vht_cap vht_cap;
>
> if (sband->band == NL80211_BAND_S1GHZ) {
> @@ -5996,9 +6084,6 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
> "no EHT support, limiting to HE\n");
> goto out;
> }
> -
> - /* we have EHT */
> -
> conn->mode = IEEE80211_CONN_MODE_EHT;
>
> /* check bandwidth */
> @@ -6009,6 +6094,20 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
> mlme_link_id_dbg(sdata, link_id,
> "no EHT 320 MHz cap in 6 GHz, limiting to 160 MHz\n");
>
> + if (req && req->flags & ASSOC_REQ_DISABLE_UHR) {
> + mlme_link_id_dbg(sdata, link_id,
> + "UHR disabled by flag, limiting to EHT\n");
> + goto out;
> + }
> +
> + uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif);
> + if (!uhr_cap) {
> + mlme_link_id_dbg(sdata, link_id,
> + "no UHR support, limiting to EHT\n");
> + goto out;
> + }
> + conn->mode = IEEE80211_CONN_MODE_UHR;
> +
> out:
> mlme_link_id_dbg(sdata, link_id,
> "determined local STA to be %s, BW limited to %d MHz\n",
> diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
> index 667021bc60c6..cb238600652a 100644
> --- a/net/mac80211/parse.c
> +++ b/net/mac80211/parse.c
> @@ -6,7 +6,7 @@
> * Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2015-2017 Intel Deutschland GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> *
> * element parsing for mac80211
> */
> @@ -189,6 +189,26 @@ ieee80211_parse_extension_element(u32 *crc,
> elems->ttlm_num++;
> }
> break;
> + case WLAN_EID_EXT_UHR_OPER:
> + if (params->mode < IEEE80211_CONN_MODE_UHR)
> + break;
> + calc_crc = true;
> + if (ieee80211_uhr_oper_size_ok(data, len,
> + params->type == (IEEE80211_FTYPE_MGMT |
> + IEEE80211_STYPE_BEACON))) {
> + elems->uhr_oper = data;
> + elems->uhr_oper_len = len;
> + }
> + break;
> + case WLAN_EID_EXT_UHR_CAPA:
> + if (params->mode < IEEE80211_CONN_MODE_UHR)
> + break;
> + calc_crc = true;
> + if (ieee80211_uhr_capa_size_ok(data, len, true)) {
here it should be false isn't it ? since here UHR cap processed for STA
> + elems->uhr_capa = data;
> + elems->uhr_capa_len = len;
> + }
> + break;
> }
>
> if (crc && calc_crc)
> diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
> index 30b9b4d76357..69034d83a7b6 100644
> --- a/net/mac80211/rx.c
> +++ b/net/mac80211/rx.c
> @@ -5518,6 +5518,32 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
> status->rate_idx, status->nss, status->eht.gi))
> goto drop;
> break;
> + case RX_ENC_UHR:
> + if (WARN_ONCE(!(status->rate_idx <= 15 ||
> + status->rate_idx == 17 ||
> + status->rate_idx == 19 ||
> + status->rate_idx == 20 ||
> + status->rate_idx == 23) ||
> + !status->nss ||
> + status->nss > 8 ||
> + status->uhr.gi > NL80211_RATE_INFO_EHT_GI_3_2,
> + "Rate marked as a UHR rate but data is invalid: MCS:%d, NSS:%d, GI:%d\n",
> + status->rate_idx, status->nss, status->uhr.gi))
> + goto drop;
> + if (WARN_ONCE(status->uhr.elr &&
> + (status->nss != 1 || status->rate_idx > 1 ||
> + status->uhr.gi != NL80211_RATE_INFO_EHT_GI_1_6 ||
> + status->bw != RATE_INFO_BW_20 || status->uhr.im),
> + "bad UHR ELR MCS MCS:%d, NSS:%d, GI:%d, BW:%d, IM:%d\n",
> + status->rate_idx, status->nss, status->uhr.gi,
> + status->bw, status->uhr.im))
> + goto drop;
> + if (WARN_ONCE(status->uhr.im &&
> + (status->nss != 1 || status->rate_idx == 15),
> + "bad UHR IM MCS MCS:%d, NSS:%d\n",
> + status->rate_idx, status->nss))
> + goto drop;
> + break;
> default:
> WARN_ON_ONCE(1);
> fallthrough;
> diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
> index 22e8561ad6fc..a79ebeb43585 100644
> --- a/net/mac80211/sta_info.c
> +++ b/net/mac80211/sta_info.c
> @@ -4,7 +4,7 @@
> * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2015 - 2017 Intel Deutschland GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> */
>
> #include <linux/module.h>
> @@ -2567,6 +2567,17 @@ static void sta_stats_decode_rate(struct ieee80211_local *local, u32 rate,
> rinfo->eht_gi = STA_STATS_GET(EHT_GI, rate);
> rinfo->eht_ru_alloc = STA_STATS_GET(EHT_RU, rate);
> break;
> + case STA_STATS_RATE_TYPE_UHR:
> + rinfo->flags = RATE_INFO_FLAGS_UHR_MCS;
> + rinfo->mcs = STA_STATS_GET(UHR_MCS, rate);
> + rinfo->nss = STA_STATS_GET(UHR_NSS, rate);
> + rinfo->eht_gi = STA_STATS_GET(UHR_GI, rate);
> + rinfo->eht_ru_alloc = STA_STATS_GET(UHR_RU, rate);
> + if (STA_STATS_GET(UHR_ELR, rate))
> + rinfo->flags |= RATE_INFO_FLAGS_UHR_ELR_MCS;
> + if (STA_STATS_GET(UHR_IM, rate))
> + rinfo->flags |= RATE_INFO_FLAGS_UHR_IM;
> + break;
> }
> }
>
> diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
> index b1edf8ed102f..2875ef7d7946 100644
> --- a/net/mac80211/sta_info.h
> +++ b/net/mac80211/sta_info.h
> @@ -3,7 +3,7 @@
> * Copyright 2002-2005, Devicescape Software, Inc.
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright(c) 2015-2017 Intel Deutschland GmbH
> - * Copyright(c) 2020-2024 Intel Corporation
> + * Copyright(c) 2020-2026 Intel Corporation
> */
>
> #ifndef STA_INFO_H
> @@ -1009,25 +1009,49 @@ enum sta_stats_type {
> STA_STATS_RATE_TYPE_HE,
> STA_STATS_RATE_TYPE_S1G,
> STA_STATS_RATE_TYPE_EHT,
> + STA_STATS_RATE_TYPE_UHR,
> };
>
> -#define STA_STATS_FIELD_HT_MCS GENMASK( 7, 0)
> -#define STA_STATS_FIELD_LEGACY_IDX GENMASK( 3, 0)
> -#define STA_STATS_FIELD_LEGACY_BAND GENMASK( 7, 4)
> -#define STA_STATS_FIELD_VHT_MCS GENMASK( 3, 0)
> -#define STA_STATS_FIELD_VHT_NSS GENMASK( 7, 4)
> -#define STA_STATS_FIELD_HE_MCS GENMASK( 3, 0)
> -#define STA_STATS_FIELD_HE_NSS GENMASK( 7, 4)
> -#define STA_STATS_FIELD_EHT_MCS GENMASK( 3, 0)
> -#define STA_STATS_FIELD_EHT_NSS GENMASK( 7, 4)
> -#define STA_STATS_FIELD_BW GENMASK(12, 8)
> -#define STA_STATS_FIELD_SGI GENMASK(13, 13)
> -#define STA_STATS_FIELD_TYPE GENMASK(16, 14)
> -#define STA_STATS_FIELD_HE_RU GENMASK(19, 17)
> -#define STA_STATS_FIELD_HE_GI GENMASK(21, 20)
> -#define STA_STATS_FIELD_HE_DCM GENMASK(22, 22)
> -#define STA_STATS_FIELD_EHT_RU GENMASK(20, 17)
> -#define STA_STATS_FIELD_EHT_GI GENMASK(22, 21)
> +/* common */
> +#define STA_STATS_FIELD_TYPE 0x0000000F
> +#define STA_STATS_FIELD_BW 0x000001F0
> +#define STA_STATS_FIELD_RESERVED 0x00000E00
> +
> +/* STA_STATS_RATE_TYPE_LEGACY */
> +#define STA_STATS_FIELD_LEGACY_IDX 0x0000F000
> +#define STA_STATS_FIELD_LEGACY_BAND 0x000F0000
> +
> +/* STA_STATS_RATE_TYPE_HT */
> +#define STA_STATS_FIELD_HT_MCS 0x000FF000
> +
> +/* STA_STATS_RATE_TYPE_VHT */
> +#define STA_STATS_FIELD_VHT_MCS 0x0000F000
> +#define STA_STATS_FIELD_VHT_NSS 0x000F0000
> +
> +/* HT & VHT */
> +#define STA_STATS_FIELD_SGI 0x00100000
> +
> +/* STA_STATS_RATE_TYPE_HE */
> +#define STA_STATS_FIELD_HE_MCS 0x0000F000
> +#define STA_STATS_FIELD_HE_NSS 0x000F0000
> +#define STA_STATS_FIELD_HE_RU 0x00700000
> +#define STA_STATS_FIELD_HE_GI 0x01800000
> +#define STA_STATS_FIELD_HE_DCM 0x02000000
> +
> +/* STA_STATS_RATE_TYPE_EHT */
> +#define STA_STATS_FIELD_EHT_MCS 0x0000F000
> +#define STA_STATS_FIELD_EHT_NSS 0x000F0000
> +#define STA_STATS_FIELD_EHT_RU 0x00F00000
> +#define STA_STATS_FIELD_EHT_GI 0x03000000
> +
> +/* STA_STATS_RATE_TYPE_UHR */
> +#define STA_STATS_FIELD_UHR_MCS 0x0001F000
> +#define STA_STATS_FIELD_UHR_NSS 0x001E0000
> +#define STA_STATS_FIELD_UHR_RU 0x01E00000
> +#define STA_STATS_FIELD_UHR_GI 0x06000000
> +#define STA_STATS_FIELD_UHR_ELR 0x08000000
> +#define STA_STATS_FIELD_UHR_IM 0x10000000
> +
>
> #define STA_STATS_FIELD(_n, _v) FIELD_PREP(STA_STATS_FIELD_ ## _n, _v)
> #define STA_STATS_GET(_n, _v) FIELD_GET(STA_STATS_FIELD_ ## _n, _v)
> @@ -1040,8 +1064,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
>
> r = STA_STATS_FIELD(BW, s->bw);
>
> - if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
> - r |= STA_STATS_FIELD(SGI, 1);
> + switch (s->encoding) {
> + case RX_ENC_HT:
> + case RX_ENC_VHT:
> + if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
> + r |= STA_STATS_FIELD(SGI, 1);
> + break;
> + default:
> + break;
> + }
>
> switch (s->encoding) {
> case RX_ENC_VHT:
> @@ -1073,6 +1104,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
> r |= STA_STATS_FIELD(EHT_GI, s->eht.gi);
> r |= STA_STATS_FIELD(EHT_RU, s->eht.ru);
> break;
> + case RX_ENC_UHR:
> + r |= STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_UHR);
> + r |= STA_STATS_FIELD(UHR_NSS, s->nss);
> + r |= STA_STATS_FIELD(UHR_MCS, s->rate_idx);
> + r |= STA_STATS_FIELD(UHR_GI, s->uhr.gi);
> + r |= STA_STATS_FIELD(UHR_RU, s->uhr.ru);
> + r |= STA_STATS_FIELD(UHR_ELR, s->uhr.elr);
> + r |= STA_STATS_FIELD(UHR_IM, s->uhr.im);
> + break;
> default:
> WARN_ON(1);
> return STA_STATS_RATE_INVALID;
> diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c
> new file mode 100644
> index 000000000000..377218987632
> --- /dev/null
> +++ b/net/mac80211/uhr.c
> @@ -0,0 +1,29 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * UHR handling
> + *
> + * Copyright(c) 2025-2026 Intel Corporation
> + */
> +
> +#include "ieee80211_i.h"
> +
> +void
> +ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
> + struct ieee80211_supported_band *sband,
> + const struct ieee80211_uhr_capa *uhr_capa,
> + u8 uhr_capa_len,
> + struct link_sta_info *link_sta)
> +{
> + struct ieee80211_sta_uhr_cap *uhr_cap = &link_sta->pub->uhr_cap;
> +
> + memset(uhr_cap, 0, sizeof(*uhr_cap));
> +
> + if (!uhr_capa ||
> + !ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif))
> + return;
> +
> + uhr_cap->has_uhr = true;
> +
> + uhr_cap->mac = uhr_capa->mac;
> + uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_capa);
> +}
> diff --git a/net/mac80211/util.c b/net/mac80211/util.c
> index 4d5680da7aa0..97433347c26d 100644
> --- a/net/mac80211/util.c
> +++ b/net/mac80211/util.c
> @@ -6,7 +6,7 @@
> * Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
> * Copyright 2013-2014 Intel Mobile Communications GmbH
> * Copyright (C) 2015-2017 Intel Deutschland GmbH
> - * Copyright (C) 2018-2025 Intel Corporation
> + * Copyright (C) 2018-2026 Intel Corporation
> *
> * utilities for mac80211
> */
> @@ -1421,6 +1421,13 @@ static int ieee80211_put_preq_ies_band(struct sk_buff *skb,
> if (err)
> return err;
>
> + if (cfg80211_any_usable_channels(local->hw.wiphy, BIT(sband->band),
> + IEEE80211_CHAN_NO_UHR)) {
> + err = ieee80211_put_uhr_cap(skb, sdata, sband);
> + if (err)
> + return err;
> + }
> +
> /*
> * If adding more here, adjust code in main.c
> * that calculates local->scan_ies_len.
> @@ -4527,6 +4534,32 @@ int ieee80211_put_eht_cap(struct sk_buff *skb,
> return 0;
> }
>
> +int ieee80211_put_uhr_cap(struct sk_buff *skb,
> + struct ieee80211_sub_if_data *sdata,
> + const struct ieee80211_supported_band *sband)
> +{
> + const struct ieee80211_sta_uhr_cap *uhr_cap =
> + ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif);
> + int len;
> +
> + if (!uhr_cap)
> + return 0;
> +
> + len = 2 + 1 + sizeof(struct ieee80211_uhr_capa) +
> + sizeof(struct ieee80211_uhr_capa_phy);
> +
> + if (skb_tailroom(skb) < len)
> + return -ENOBUFS;
> +
> + skb_put_u8(skb, WLAN_EID_EXTENSION);
> + skb_put_u8(skb, len - 2);
> + skb_put_u8(skb, WLAN_EID_EXT_UHR_CAPA);
> + skb_put_data(skb, &uhr_cap->mac, sizeof(uhr_cap->mac));
> + skb_put_data(skb, &uhr_cap->phy, sizeof(uhr_cap->phy));
> +
> + return 0;
> +}
> +
> const char *ieee80211_conn_mode_str(enum ieee80211_conn_mode mode)
> {
> static const char * const modes[] = {
> @@ -4536,6 +4569,7 @@ const char *ieee80211_conn_mode_str(enum ieee80211_conn_mode mode)
> [IEEE80211_CONN_MODE_VHT] = "VHT",
> [IEEE80211_CONN_MODE_HE] = "HE",
> [IEEE80211_CONN_MODE_EHT] = "EHT",
> + [IEEE80211_CONN_MODE_UHR] = "UHR",
> };
>
> if (WARN_ON(mode >= ARRAY_SIZE(modes)))
^ permalink raw reply
* Re: [GIT PULL] wireless-next-2026-01-29
From: Jakub Kicinski @ 2026-01-29 16:28 UTC (permalink / raw)
To: Paolo Abeni; +Cc: Johannes Berg, netdev, linux-wireless
In-Reply-To: <b0d6887f-7946-46b6-986a-bf410b832d66@redhat.com>
On Thu, 29 Jan 2026 15:08:46 +0100 Paolo Abeni wrote:
> https://patchwork.kernel.org/project/netdevbpf/patch/20260129110136.176980-39-johannes@sipsolutions.net/
>
> so it could be a CI flake, but could you please have a look?
flake, fwiw
^ permalink raw reply
* Re: [PATCH wireless-next v6 1/3] wifi: ieee80211: add some initial UHR definitions
From: Johannes Berg @ 2026-01-29 16:10 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless
In-Reply-To: <2144630f-6abd-4a97-821a-51cada015867@freebox.fr>
On Thu, 2026-01-29 at 17:03 +0100, Pablo MARTIN-GOMEZ wrote:
> > +struct ieee80211_uhr_oper {
> > + __le16 params;
> > + u8 basic_mcs_nss_set[4];
> > + u8 variable[];
> > +} __packed;
> > +
>
> I've just noticed that all the other PHYs have a type name
> `ieee80211_*PHY*_operation`, it would make sense to keep the same
> template for UHR.
Meh. "_operation" makes those names get really long, and everyone
probably already has a bunch of code depending on this, so I don't think
I want to change it now.
As for the _cap vs. _capa, maybe that's a better argument, but it's also
not _quite_ the same between all the others (e.g. "...vht_cap" but
"...he_cap_elem"), so... I don't think changing it now is worth the
cost.
Qualcomm folks, what do you think?
johannes
^ permalink raw reply
* Re: [PATCH wireless-next v6 1/3] wifi: ieee80211: add some initial UHR definitions
From: Pablo MARTIN-GOMEZ @ 2026-01-29 16:03 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260129134944.a84420ec58d6.I5b11fb0345a933bf497fd802aecc72932d58dd68@changeid>
On 29/01/2026 13:49, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
>
> This is based on Draft P802.11bn_D1.2, but that's still very
> incomplete, so don't handle a number of things and make some
> local decisions such as using 40 bits for MAC capabilities
> and 8 bits for PHY capabilities.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> v6:
> - add 'ap' argument to ieee80211_uhr_phy_cap()
> v5:
> - use correct EHT MCS len (24 bits, not 24 bytes)
> - handle DBE AP/non-AP in ieee80211_uhr_capa_size_ok()
> v4:
> - update to D1.2, including DBE in UHR capabilities
> - fold in suggestions from Pablo
> ---
> include/linux/ieee80211-uhr.h | 219 ++++++++++++++++++++++++++++++++++
> include/linux/ieee80211.h | 33 ++++-
> 2 files changed, 250 insertions(+), 2 deletions(-)
> create mode 100644 include/linux/ieee80211-uhr.h
[...]
> +struct ieee80211_uhr_oper {
> + __le16 params;
> + u8 basic_mcs_nss_set[4];
> + u8 variable[];
> +} __packed;
> +
I've just noticed that all the other PHYs have a type name
`ieee80211_*PHY*_operation`, it would make sense to keep the same
template for UHR.
[...]
> +
> +struct ieee80211_uhr_capa_mac {
> + u8 mac_cap[5];
> +} __packed;
> +
> +struct ieee80211_uhr_capa {
> + struct ieee80211_uhr_capa_mac mac;
> + /* DBE, PHY capabilities */
> + u8 variable[];
> +} __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
> +
> +struct ieee80211_uhr_capa_phy {
> + u8 cap;
> +} __packed;
> +
>
> +}
Same for the capabilities. For the other PHYs, we use *PHY*_cap_[...],
so I would stick with that.
Pablo
^ permalink raw reply
* Re: [PATCH v2] wifi: ath11k: fix memory leaks in beacon template setup
From: Jeff Johnson @ 2026-01-29 15:36 UTC (permalink / raw)
To: Zilin Guan
Cc: ath11k, baochen.qiang, jianhao.xu, jjohnson, linux-kernel,
linux-wireless
In-Reply-To: <20260129061330.796429-1-zilin@seu.edu.cn>
On 1/28/2026 10:13 PM, Zilin Guan wrote:
> On Wed, Jan 28, 2026 at 08:30:22AM -0800, Jeff Johnson wrote:
>> On 1/19/2026 10:37 PM, Zilin Guan wrote:
>>> The functions ath11k_mac_setup_bcn_tmpl_ema() and
>>> ath11k_mac_setup_bcn_tmpl_mbssid() allocate memory for beacon templates
>>> but fail to free it when parameter setup returns an error.
>>>
>>> Since beacon templates must be released during normal execution, they
>>> must also be released in the error handling paths to prevent memory
>>> leaks.
>>>
>>> Fix this by adding the missing deallocation calls in the respective
>>> error paths.
>>>
>>> Compile tested only. Issue found using a prototype static analysis tool
>>> and code review.
>>>
>>> Fixes: 3a415daa3e8b ("wifi: ath11k: add P2P IE in beacon template")
>>> Fixes: 335a92765d30 ("wifi: ath11k: MBSSID beacon support")
>>> Suggested-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>>> Signed-off-by: Zilin Guan <zilin@seu.edu.cn>
>>> ---
>>> Changes in v2:
>>> - Use unified exit paths for cleanup.
>>>
>>> drivers/net/wireless/ath/ath11k/mac.c | 25 +++++++++++++++----------
>>> 1 file changed, 15 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
>>> index 4dfd08b58416..42edcc5e9e49 100644
>>> --- a/drivers/net/wireless/ath/ath11k/mac.c
>>> +++ b/drivers/net/wireless/ath/ath11k/mac.c
>>> @@ -1561,8 +1561,10 @@ static int ath11k_mac_setup_bcn_tmpl_ema(struct ath11k_vif *arvif,
>>
>> while looking to apply this patch I noticed the following logic earlier in the
>> function:
>>
>> beacons = ieee80211_beacon_get_template_ema_list(tx_arvif->ar->hw,
>> tx_arvif->vif, 0);
>> if (!beacons || !beacons->cnt) {
>> ath11k_warn(arvif->ar->ab,
>> "failed to get ema beacon templates from mac80211\n");
>> return -EPERM;
>> }
>>
>> I did not look at ieee80211_beacon_get_template_ema_list()
>> But if it is possible that this can return a valid beacons pointer with
>> beacons->cnt == 0, then won't this also leak the beacons allocation?
>>
>> Given that ieee80211_beacon_free_ema_list(beacons) can handle a NULL
>> beacons pointer, perhaps this should also goto free?
>
> Hi Jeff,
>
> Thanks for pointing that out.
>
> I looked into the allocation chain for
> ieee80211_beacon_get_template_ema_list():
>
> ieee80211_beacon_get_template_ema_list()
> |__ __ieee80211_beacon_get()
> |__ ieee80211_beacon_get_ap_ema_list()
>
> It seems that ieee80211_beacon_get_ap_ema_list() only returns a valid
> pointer when ema->cnt is non-zero. Therefore, a valid beacons pointer with
> beacons->cnt == 0 is likely unreachable under the current mac80211
> implementation, making the existing check more of a defensive programming
> measure.
>
> However, for the sake of strict logical consistency, it would make sense
> to use the goto path there as well.
>
> Do you think it's worth updating this in a v3, or is the current v2
> sufficient given the current call logic?
I prefer strict logical consistency so I prefer either adding the goto or
removing the beacons->cnt check.
Or a completely different approach would be to use cleanup.h functionality and
annotate beacons with __free(ieee80211_beacon_free_ema_list) so that no
explicit calls to that function are required. If you try this approach then
beacons must be defined at the point of allocation:
struct ieee80211_ema_beacons *beacons __free(ieee80211_beacon_free_ema_list) =
ieee80211_beacon_get_template_ema_list(tx_arvif->ar->hw,
tx_arvif->vif, 0);
Note that I have not tried this approach with allocations other than from the
kmalloc() family with __free(kfree), but in theory this should work.
/jeff
^ permalink raw reply
* Re: [GIT PULL] wireless-next-2026-01-29
From: Johannes Berg @ 2026-01-29 14:20 UTC (permalink / raw)
To: Paolo Abeni, netdev; +Cc: linux-wireless
In-Reply-To: <b0d6887f-7946-46b6-986a-bf410b832d66@redhat.com>
On Thu, 2026-01-29 at 15:08 +0100, Paolo Abeni wrote:
> On 1/29/26 11:58 AM, Johannes Berg wrote:
> > Here's another set of changes for net-next. Two things to note:
> >
> > 1) This introduces a couple of new sparse warnings, because it
> > cannot deal with guard(spinlock_bh)() which the drivers are
> > now using. I previously fixed sparse for that for RCU, but
> > given the context tracking work will remove this validation
> > from sparse entirely, I haven't bothered trying to keep up.
> The CI also report a 32bit build failure, but with a dangling link to
> the actual build results:
>
> https://patchwork.kernel.org/project/netdevbpf/patch/20260129110136.176980-39-johannes@sipsolutions.net/
>
> so it could be a CI flake, but could you please have a look?
Hmm. My instance didn't report anything at all [1], not even the sparse
warnings (oddly enough), although it had reported the new sparse
warnings due to guard() for the ath PR [2], but not on 32-bit.
[1] https://patchwork.kernel.org/project/linux-wireless/patch/20260129110136.176980-39-johannes@sipsolutions.net/
[2] https://patchwork.kernel.org/project/linux-wireless/patch/65b400ca-8526-4184-ae0b-5e24e41dab9c@oss.qualcomm.com/
I just did a 32-bit build manually, and only see some warnings like
this:
drivers/net/wireless/realtek/rtw89/util.c:43:45: warning: decimal
constant 2238721139 is between LONG_MAX and ULONG_MAX. For C99 that
means long long, C90 compilers are very likely to produce unsigned long
(and a warning) here
but that's old, this file hasn't been changed in ~11 months. We can get
that fixed, but it's not something the CI should have flagged as "new
warnings".
Sorry, not sure what's going on.
johannes
^ permalink raw reply
* Re: [GIT PULL] wireless-next-2026-01-29
From: Paolo Abeni @ 2026-01-29 14:08 UTC (permalink / raw)
To: Johannes Berg, netdev; +Cc: linux-wireless
In-Reply-To: <20260129110136.176980-39-johannes@sipsolutions.net>
On 1/29/26 11:58 AM, Johannes Berg wrote:
> Here's another set of changes for net-next. Two things to note:
>
> 1) This introduces a couple of new sparse warnings, because it
> cannot deal with guard(spinlock_bh)() which the drivers are
> now using. I previously fixed sparse for that for RCU, but
> given the context tracking work will remove this validation
> from sparse entirely, I haven't bothered trying to keep up.
>
> 2) There's a core SDIO patch in here, but that's intentional
> and we agreed with the maintainer to merge it through this
> tree since only two wireless changes depend on it.
>
> Please pull and let us know if there's any problem.
The CI also report a 32bit build failure, but with a dangling link to
the actual build results:
https://patchwork.kernel.org/project/netdevbpf/patch/20260129110136.176980-39-johannes@sipsolutions.net/
so it could be a CI flake, but could you please have a look?
/P
^ permalink raw reply
* Re: [PATCH v2 2/2] wifi: ath12k: Fix firmware stats leak when pdev list is empty
From: Saikiran B @ 2026-01-29 14:06 UTC (permalink / raw)
To: Baochen Qiang; +Cc: ath12k, linux-wireless, kvalo
In-Reply-To: <7fb6b431-58ea-47bc-b251-5144575db17d@oss.qualcomm.com>
On Thu, Jan 29, 2026 at 7:57 AM Baochen Qiang
<baochen.qiang@oss.qualcomm.com> wrote:
>
>
>
> On 1/27/2026 12:40 PM, Saikiran B wrote:
> > I have analyzed the logs and code flow in depth to provide more
> > definitive answers for your questions.
> >
> > The log entries showing the failure are:
> > [ 563.574076] ath12k_pci 0004:01:00.0: failed to pull fw stats: -71
> > [ 564.575896] ath12k_pci 0004:01:00.0: time out while waiting for get fw stats
> >
> > 1. Why are other stats populated?
> > The "failed to pull fw stats: -71" error is not the initial failure
> > but a symptom that appears after repeated operations. The leak happens
> > during *successful* calls prior to this error.
> >
> > Code flow proving the leak:
> > - ath12k_mac_get_fw_stats() sends WMI_REQUEST_PDEV_STAT.
> > - Firmware responds. ath12k_update_stats_event() parses the response.
> > - ath12k_wmi_fw_stats_process() is called, which splices 'vdevs' and
> > 'beacon' stats into ar->fw_stats.vdevs/bcn.
> > - ath12k_mac_get_fw_stats() returns 0 (Success).
> > - In ath12k_mac_op_get_txpower(), the check `if (!pdev)` fails if the
> > pdev-specific list is empty (but vdev list is NOT empty).
> > - The function exits via `err_fallback` WITHOUT calling ath12k_fw_stats_reset().
> > - Result: The 'vdev' and 'beacon' stats that were spliced into
> > ar->fw_stats remain there, leaking memory and accumulating with every
> > call.
> >
> > 2. Exact place where -71 is printed:
> > The error "failed to pull fw stats: -71" is printed in
> > [ath12k_update_stats_event()](drivers/net/wireless/ath/ath12k/wmi.c).
> > It corresponds to "ret = ath12k_wmi_pull_fw_stats()" returning -EPROTO.
> > This propagates from
> > [ath12k_wmi_tlv_fw_stats_data_parse()](drivers/net/wireless/ath/ath12k/wmi.c),
> > when buffer validation checks (like `len < sizeof(*src)`) fail.
> >
> > Conclusion:
> > The fix in my patch (resetting stats when `!pdev`) is critical because
> > it ensures that the accumulated 'vdev' and 'beacon' stats are freed
> > even when the 'pdev' list ends up empty.
> >
> > Let me know if you need anything else.
>
> can you please try below to see if it can fix your issue?
>
> https://lore.kernel.org/r/20260129-ath12k-fw-stats-fixes-v1-0-55d66064f4d5@oss.qualcomm.com
>
> >
> > Thanks & Regards,
> > Saikiran
> >
> > On Tue, Jan 27, 2026 at 9:47 AM Saikiran B <bjsaikiran@gmail.com> wrote:
> >>
> >> Hi Baochen,
> >>
> >> Regarding your questions:
> >>
> >> "Are other stats populated?"
> >>
> >> Yes. When ath12k_mac_get_fw_stats() returns success (0), it means the
> >> firmware response was received and valid WMI events were processed.
> >> The firmware response to WMI_REQUEST_PDEV_STAT typically includes
> >> multiple stats TLVs (vdev stats, beacon stats, etc.). Even if the
> >> "pdev stats" list ends up empty (e.g., due to specific filtering or
> >> availability), the firmware should have populated other lists (like
> >> vdevs or beacons) in the ar->fw_stats structure. If we don't reset,
> >> these valid entries leak and accumulate.
> >>
> >> "Where exactly is -71 (EPROTO) printed?"
> >>
> >> The log "failed to pull fw stats: -71" is printed in
> >> ath12k_update_stats_event() (wmi.c line 8500 in my tree). This error
> >> code (-EPROTO) propagates from ath12k_wmi_tlv_fw_stats_data_parse(),
> >> where it is returned when buffer validation checks fail (e.g., if (len
> >> < sizeof(*src))). This failure suggests that the accumulated state or
> >> memory corruption from the leak eventually causes the parser to fail
> >> on subsequent events.
> >>
> >> So, fixing the leak is necessary for correctness regardless of the
> >> specific side-effect error code.
> >>
> >> Thanks & Regards,
> >> Saikiran
> >>
> >> On Tue, Jan 27, 2026 at 8:57 AM Baochen Qiang
> >> <baochen.qiang@oss.qualcomm.com> wrote:
> >>>
> >>>
> >>>
> >>> On 1/26/2026 5:52 PM, Saikiran wrote:
> >>>> The commits bd6ec8111e65 and 2977567b244f changed firmware stats handling
> >>>> to be caller-driven, requiring explicit ath12k_fw_stats_reset() calls
> >>>> after using ath12k_mac_get_fw_stats().
> >>>>
> >>>> In ath12k_mac_op_get_txpower(), when ath12k_mac_get_fw_stats() succeeds
> >>>> but the pdev stats list is empty, the function exits without calling
> >>>> ath12k_fw_stats_reset(). Even though the pdev list is empty, the firmware
> >>>> may have populated other stats lists (vdevs, beacons, etc.) in the
> >>>
> >>> 'may' is not enough, we need to be 100% sure whether other stats are populated. This is
> >>> critical for us to find the root cause.
> >>>
> >>>> ar->fw_stats structure.
> >>>>
> >>>> Without resetting the stats buffer, this data accumulates across multiple
> >>>> calls, eventually causing the stats buffer to overflow and leading to
> >>>> firmware communication failures (error -71/EPROTO) during subsequent
> >>>> operations.
> >>>>
> >>>> The issue manifests during 5GHz scanning which triggers multiple TX power
> >>>> queries. Symptoms include:
> >>>> - "failed to pull fw stats: -71" errors in dmesg
> >>>
> >>> still, can you please check the logs to see at which exact place is this printed?
> >>>
> >>>> - 5GHz networks not detected despite hardware support
> >>>> - 2.4GHz networks work normally
> >>>>
> >>>> Fix by calling ath12k_fw_stats_reset() when the pdev list is empty,
> >>>> ensuring the stats buffer is properly cleaned up even when only partial
> >>>> stats data is received from firmware.
> >>>>
> >>>> Fixes: bd6ec8111e65 ("wifi: ath12k: Make firmware stats reset caller-driven")
> >>>> Link: https://bugs.launchpad.net/ubuntu-concept/+bug/2138308
> >>>> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302 (Lenovo Yoga Slim 7x)
> >>>> Signed-off-by: Saikiran <bjsaikiran@gmail.com>
> >>>> ---
> >>>> drivers/net/wireless/ath/ath12k/mac.c | 1 +
> >>>> 1 file changed, 1 insertion(+)
> >>>>
> >>>> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> >>>> index e0e49f782bf8..6e35c3ee9864 100644
> >>>> --- a/drivers/net/wireless/ath/ath12k/mac.c
> >>>> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> >>>> @@ -5169,6 +5169,7 @@ static int ath12k_mac_op_get_txpower(struct ieee80211_hw *hw,
> >>>> struct ath12k_fw_stats_pdev, list);
> >>>> if (!pdev) {
> >>>> spin_unlock_bh(&ar->data_lock);
> >>>> + ath12k_fw_stats_reset(ar);
> >>>> goto err_fallback;
> >>>> }
> >>>>
> >>>
>
Hi Baochen,
I tried applying your patches on top of v6.19-rc7 (which is the latest
mainline release candidate I'm testing on), but I ran into build
issues because some of the dependencies seem missing.
Specifically:
Patch 2 ("wifi: ath12k: fix station lookup failure when disconnecting
from AP") uses `ath12k_link_sta_find_by_addr()`, which does not exist
in my tree. It seems your patches are based on a different tree
(ath-next?) that has newer changes not yet in the mainline.
Could you please point me to the specific git repo/branch you are
using? I can try to build and test on that baseline to be sure.
Regarding the firmware stats issue:
I verified the firmware files match the latest available (MD5 sums
matched), yet the "-71" errors and memory leak persist on my device
without fixes.
I successfully applied the logic from your Patch 1 manually (since
[ath12k_mac_get_target_pdev_id](cci:1://file:///home/saikiran/linux/kernel/x1e/x1e/drivers/net/wireless/ath/ath12k/mac.c:989:0-1008:1)
exists), but I haven't fully validated if it alone resolves the leak
in all scenarios.
However, the fix I proposed in my v2 patch (resetting stats when pdev
list is empty) definitely stops the leak mechanism by ensuring cleanup
happens even when the firmware returns partial stats (which seems to
be the trigger condition).
I'll wait for your pointer to the base tree to do a proper test of your series.
Thanks & Regards,
Saikiran
^ permalink raw reply
* [PATCH wireless-next v4] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
From: Lorenzo Bianconi @ 2026-01-29 13:15 UTC (permalink / raw)
To: Johannes Berg, Ryder Lee, Sean Wang, Matthias Brugger,
AngeloGioacchino Del Regno
Cc: linux-wireless, Felix Fietkau, Shayne Chen, Lorenzo Bianconi,
Christian Marangi, linux-mediatek, linux-arm-kernel
Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR/eMLMR info.
Tested-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
Introduce support in AP mode for parsing of the Operating Mode Notification
frame sent by the client to enable/disable MLO eMLSR or eMLMR if supported
by both the AP and the client.
Add drv_set_eml_op_mode mac80211 callback in order to configure underlay
driver with eMLSR info (control and bitmap).
Implement drv_set_eml_op_mode callback for MT7996 driver.
---
Changes in v4:
- Fix link_bitmap sanity checks
- Link to v3: https://lore.kernel.org/r/20260128-mac80211-emlsr-v3-1-473b0c1d9cc4@kernel.org
Changes in v3:
- Get rid of ieee80211_get_emlsr_pad_delay_update and
ieee80211_get_emlsr_trans_delay_update routines and add sanity checks
for pad_delay/trans_delay directly in ieee80211_rx_eml_op_mode_notif()
- Add mcs_map sanity checks.
- Add ieee80211_eml_params struct kernel-doc.
- Get rid of emlsr_pad_delay/emlsr_trans_delay in ieee80211_eml_params
struct and rely on ieee80211_sta fields.
- Improve sanity checks.
- Remove mt7996 patch from the series.
- Link to v2: https://lore.kernel.org/r/20260125-mac80211-emlsr-v2-0-466329d61c88@kernel.org
Changes in v2:
- Improve sanity check against device EML capabilities
- Squash patch 1/2 and 2/2
- Validate link_bitmap with vif->active_links
- Introduce ieee80211_eml_params struct as containe for EML info to pass
to the underlay driver.
- Pass padding_delay and transition_delay to the underlay driver.
- Implement drv_set_eml_op_mode callback for MT7996 driver.
- Link to v2: https://lore.kernel.org/r/20260122-mac80211-emlsr-v1-0-f0e43bb6d95a@kernel.org
---
include/linux/ieee80211-eht.h | 11 +++
include/linux/ieee80211.h | 6 ++
include/net/mac80211.h | 32 ++++++++
net/mac80211/driver-ops.h | 21 +++++
net/mac80211/eht.c | 175 ++++++++++++++++++++++++++++++++++++++++++
net/mac80211/ieee80211_i.h | 2 +
net/mac80211/iface.c | 10 ++-
net/mac80211/rx.c | 8 ++
net/mac80211/trace.h | 32 ++++++++
9 files changed, 296 insertions(+), 1 deletion(-)
diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h
index f9782e46c5e5241bccc84c3bbc18b1cc9ec1879c..11105528a2c60d8351a5c9be04fcb714878601fe 100644
--- a/include/linux/ieee80211-eht.h
+++ b/include/linux/ieee80211-eht.h
@@ -558,6 +558,17 @@ struct ieee80211_mle_tdls_common_info {
#define IEEE80211_MLC_PRIO_ACCESS_PRES_AP_MLD_MAC_ADDR 0x0010
+#define IEEE80211_EML_CTRL_EMLSR_MODE BIT(0)
+#define IEEE80211_EML_CTRL_EMLMR_MODE BIT(1)
+#define IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE BIT(2)
+#define IEEE80211_EML_CTRL_INDEV_COEX_ACT BIT(3)
+
+#define IEEE80211_EML_EMLSR_PAD_DELAY 0x07
+#define IEEE80211_EML_EMLSR_TRANS_DELAY 0x38
+
+#define IEEE80211_EML_EMLMR_RX_MCS_MAP 0xf0
+#define IEEE80211_EML_EMLMR_TX_MCS_MAP 0x0f
+
/* no fixed fields in PRIO_ACCESS */
/**
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index fbde215c25aa79efd339aa530896a29dbb1a8ff8..f2c6f34f39f24ce59cbb2650f70e898d24d12901 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1186,6 +1186,12 @@ struct ieee80211_mgmt {
u8 action_code;
u8 variable[];
} __packed epcs;
+ struct {
+ u8 action_code;
+ u8 dialog_token;
+ u8 control;
+ u8 variable[];
+ } __packed eml_omn;
} u;
} __packed action;
DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf35190921f4fee0fe3294418007a56..388076b1b17c2560756deb2966903e21049205ac 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1902,6 +1902,31 @@ enum ieee80211_offload_flags {
IEEE80211_OFFLOAD_DECAP_ENABLED = BIT(2),
};
+/**
+ * struct ieee80211_eml_params - EHT Operating mode notification parameters
+ *
+ * EML Operating mode notification parameters received in the Operating mode
+ * notification frame. This struct is used as a container to pass the info to
+ * the underlay driver.
+ *
+ * @link_id: the link ID where the Operating mode notification frame has been
+ * received.
+ * @control: EML control field defined in P802.11be section 9.4.1.76.
+ * @link_bitmap: eMLSR/eMLMR enabled links defined in P802.11be
+ * section 9.4.1.76.
+ * @emlmr_mcs_map_count: eMLMR number of valid mcs_map_bw fields according to
+ * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set).
+ * @emlmr_mcs_map_bw: eMLMR supported MCS and NSS set subfileds defined in
+ * P802.11be section 9.4.1.76 (valid if eMLMR mode control bit is set).
+ */
+struct ieee80211_eml_params {
+ u8 link_id;
+ u8 control;
+ u16 link_bitmap;
+ u8 emlmr_mcs_map_count;
+ u8 emlmr_mcs_map_bw[9];
+};
+
/**
* struct ieee80211_vif_cfg - interface configuration
* @assoc: association status
@@ -4513,6 +4538,9 @@ struct ieee80211_prep_tx_info {
* interface with the specified type would be added, and thus drivers that
* implement this callback need to handle such cases. The type is the full
* &enum nl80211_iftype.
+ * @set_eml_op_mode: Configure eMLSR/eMLMR operation mode in the underlay
+ * driver according to the parameter received in the EML Operating mode
+ * notification frame.
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
@@ -4908,6 +4936,10 @@ struct ieee80211_ops {
struct ieee80211_neg_ttlm *ttlm);
void (*prep_add_interface)(struct ieee80211_hw *hw,
enum nl80211_iftype type);
+ int (*set_eml_op_mode)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_eml_params *eml_params);
};
/**
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 55105d238d6bc5963eb2863575805bee72c42399..51bf3c7822a76007e6ec59fb70e97fa50d43632b 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1772,4 +1772,25 @@ drv_prep_add_interface(struct ieee80211_local *local,
trace_drv_return_void(local);
}
+static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta,
+ struct ieee80211_eml_params *eml_params)
+{
+ struct ieee80211_local *local = sdata->local;
+ int ret = -EOPNOTSUPP;
+
+ might_sleep();
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ trace_drv_set_eml_op_mode(local, sdata, sta, eml_params->link_id,
+ eml_params->control,
+ eml_params->link_bitmap);
+ if (local->ops->set_eml_op_mode)
+ ret = local->ops->set_eml_op_mode(&local->hw, &sdata->vif,
+ sta, eml_params);
+ trace_drv_return_int(local, ret);
+
+ return ret;
+}
+
#endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index fd41046e3b681b753e6cc7ddf82046e4bc5df9b3..75096b2195d24358da4ae3abc595d0e349d17037 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -5,6 +5,7 @@
* Copyright(c) 2021-2025 Intel Corporation
*/
+#include "driver-ops.h"
#include "ieee80211_i.h"
void
@@ -102,3 +103,177 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
}
+
+static void
+ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *req, int opt_len)
+{
+ int len = offsetofend(struct ieee80211_mgmt, u.action.u.eml_omn);
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+
+ len += opt_len; /* optional len */
+ skb = dev_alloc_skb(local->tx_headroom + len);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, local->tx_headroom);
+ mgmt = skb_put_zero(skb, len);
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, req->sa, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_EHT;
+ mgmt->u.action.u.eml_omn.action_code =
+ WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF;
+ mgmt->u.action.u.eml_omn.dialog_token =
+ req->u.action.u.eml_omn.dialog_token;
+ mgmt->u.action.u.eml_omn.control = req->u.action.u.eml_omn.control &
+ ~(IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE |
+ IEEE80211_EML_CTRL_INDEV_COEX_ACT);
+ /* Copy optional fields from the received notification frame */
+ memcpy(mgmt->u.action.u.eml_omn.variable,
+ req->u.action.u.eml_omn.variable, opt_len);
+
+ ieee80211_tx_skb(sdata, skb);
+}
+
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ int len = offsetofend(struct ieee80211_mgmt, u.action.u.eml_omn);
+ enum nl80211_iftype type = ieee80211_vif_type_p2p(&sdata->vif);
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ const struct wiphy_iftype_ext_capab *ift_ext_capa;
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ struct ieee80211_local *local = sdata->local;
+ u8 control = mgmt->u.action.u.eml_omn.control;
+ u8 *ptr = mgmt->u.action.u.eml_omn.variable;
+ struct ieee80211_eml_params eml_params = {
+ .link_id = status->link_id,
+ };
+ struct sta_info *sta;
+ int opt_len = 0;
+
+ if (!ieee80211_vif_is_mld(&sdata->vif))
+ return;
+
+ /* eMLSR and eMLMR can't be enabled at the same time */
+ if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) &&
+ (control & IEEE80211_EML_CTRL_EMLMR_MODE))
+ return;
+
+ if ((control & IEEE80211_EML_CTRL_EMLMR_MODE) &&
+ (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE))
+ return;
+
+ ift_ext_capa = cfg80211_get_iftype_ext_capa(local->hw.wiphy, type);
+ if (!ift_ext_capa)
+ return;
+
+ if (!status->link_valid)
+ return;
+
+ sta = sta_info_get_bss(sdata, mgmt->sa);
+ if (!sta)
+ return;
+
+ if (control & IEEE80211_EML_CTRL_EMLSR_MODE) {
+ u8 emlsr_param_update_len;
+
+ if (!(ift_ext_capa->eml_capabilities &
+ IEEE80211_EML_CAP_EMLSR_SUPP))
+ return;
+
+ opt_len += sizeof(__le16); /* eMLSR link_bitmap */
+ /* eMLSR param update field is not part of Notfication frame
+ * sent by the AP to client so account it separately.
+ */
+ emlsr_param_update_len =
+ !!(control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE);
+
+ if (skb->len < len + opt_len + emlsr_param_update_len)
+ return;
+
+ if (control & IEEE80211_EML_CTRL_EMLSR_PARAM_UPDATE) {
+ u8 pad_delay, trans_delay;
+
+ pad_delay = u8_get_bits(ptr[2],
+ IEEE80211_EML_EMLSR_PAD_DELAY);
+ if (pad_delay >
+ IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
+ return;
+
+ trans_delay = u8_get_bits(ptr[2],
+ IEEE80211_EML_EMLSR_TRANS_DELAY);
+ if (trans_delay >
+ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US)
+ return;
+
+ /* Update sta padding and transition delay */
+ sta->sta.eml_cap =
+ u8_replace_bits(sta->sta.eml_cap,
+ pad_delay,
+ IEEE80211_EML_CAP_EMLSR_PADDING_DELAY);
+ sta->sta.eml_cap =
+ u8_replace_bits(sta->sta.eml_cap,
+ trans_delay,
+ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
+ }
+ }
+
+ if (control & IEEE80211_EML_CTRL_EMLMR_MODE) {
+ u8 mcs_map_size;
+ int i;
+
+ if (!(ift_ext_capa->eml_capabilities &
+ IEEE80211_EML_CAP_EMLMR_SUPPORT))
+ return;
+
+ opt_len += sizeof(__le16); /* eMLMR link_bitmap */
+ opt_len++; /* eMLMR mcs_map_count */
+ if (skb->len < len + opt_len)
+ return;
+
+ eml_params.emlmr_mcs_map_count = ptr[2];
+ if (eml_params.emlmr_mcs_map_count > 2)
+ return;
+
+ mcs_map_size = 3 * (1 + eml_params.emlmr_mcs_map_count);
+ opt_len += mcs_map_size;
+ if (skb->len < len + opt_len)
+ return;
+
+ for (i = 0; i < mcs_map_size; i++) {
+ u8 rx_mcs, tx_mcs;
+
+ rx_mcs = u8_get_bits(ptr[3 + i],
+ IEEE80211_EML_EMLMR_RX_MCS_MAP);
+ if (rx_mcs > 8)
+ return;
+
+ tx_mcs = u8_get_bits(ptr[3 + i],
+ IEEE80211_EML_EMLMR_TX_MCS_MAP);
+ if (tx_mcs > 8)
+ return;
+ }
+
+ memcpy(eml_params.emlmr_mcs_map_bw, &ptr[3], mcs_map_size);
+ }
+
+ if ((control & IEEE80211_EML_CTRL_EMLSR_MODE) ||
+ (control & IEEE80211_EML_CTRL_EMLMR_MODE)) {
+ eml_params.link_bitmap = get_unaligned_le16(ptr);
+ if ((eml_params.link_bitmap & sdata->vif.active_links) !=
+ eml_params.link_bitmap)
+ return;
+ }
+
+ if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params))
+ return;
+
+ ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
+}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index dc757cb329740d621f6a9deb4e9ffe258e1b7d67..3ef93cd1fb33e2614c3e06c51d6b1ebcafa4824c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2837,6 +2837,8 @@ void ieee80211_destroy_frag_cache(struct ieee80211_fragment_cache *cache);
u8 ieee80211_ie_len_eht_cap(struct ieee80211_sub_if_data *sdata);
+void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb);
void
ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
struct ieee80211_supported_band *sband,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 3ce94b95decd64eea4ea063ae98c111bdfd57e9c..17476fd9259b68b3d9c649eabad10efa42d56cd7 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1664,7 +1664,15 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
}
} else if (ieee80211_is_action(mgmt->frame_control) &&
mgmt->u.action.category == WLAN_CATEGORY_PROTECTED_EHT) {
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ if (sdata->vif.type == NL80211_IFTYPE_AP) {
+ switch (mgmt->u.action.u.eml_omn.action_code) {
+ case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+ ieee80211_rx_eml_op_mode_notif(sdata, skb);
+ break;
+ default:
+ break;
+ }
+ } else if (sdata->vif.type == NL80211_IFTYPE_STATION) {
switch (mgmt->u.action.u.ttlm_req.action_code) {
case WLAN_PROTECTED_EHT_ACTION_TTLM_REQ:
ieee80211_process_neg_ttlm_req(sdata, mgmt,
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 30b9b4d763578feea179ad33be5174fc3fc19209..6a13460b8b6a9176f50ca9ec7fb365befc9b317c 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3928,6 +3928,14 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
u.action.u.epcs))
goto invalid;
goto queue;
+ case WLAN_PROTECTED_EHT_ACTION_EML_OP_MODE_NOTIF:
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ break;
+
+ if (len < offsetofend(typeof(*mgmt),
+ u.action.u.eml_omn))
+ goto invalid;
+ goto queue;
default:
break;
}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 0bfbce1574862b5a6a2ca39794abea7fe9a3f34a..c04d4547e8f4655ad77b83bed6e5a832d959e61b 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -3353,6 +3353,38 @@ TRACE_EVENT(drv_prep_add_interface,
)
);
+TRACE_EVENT(drv_set_eml_op_mode,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta,
+ unsigned int link_id,
+ u8 control, u16 link_bitmap),
+
+ TP_ARGS(local, sdata, sta, link_id, control, link_bitmap),
+
+ TP_STRUCT__entry(LOCAL_ENTRY
+ VIF_ENTRY
+ STA_ENTRY
+ __field(u32, link_id)
+ __field(u8, control)
+ __field(u16, link_bitmap)),
+
+ TP_fast_assign(LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_NAMED_ASSIGN(sta);
+ __entry->link_id = link_id;
+ __entry->control = control;
+ __entry->link_bitmap = link_bitmap;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT
+ " (link:%d control:%02x link_bitmap:%04x)",
+ LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG, __entry->link_id,
+ __entry->control, __entry->link_bitmap
+ )
+);
+
#endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
---
base-commit: c30e188bd2a886258be5facb970a804d8ef549b5
change-id: 20260121-mac80211-emlsr-5774082ff8cc
Best regards,
--
Lorenzo Bianconi <lorenzo@kernel.org>
^ permalink raw reply related
* RE: [PATCH] wifi: rtw89: usb: fix TX flow control by tracking in-flight URBs
From: Mh_chen @ 2026-01-29 13:12 UTC (permalink / raw)
To: Lucid Duck, Bitterblue Smith, Isaiah
Cc: linux-wireless@vger.kernel.org, Ping-Ke Shih
In-Reply-To: <202601291256.60TCusZS3018440@rtits1.realtek.com.tw>
+Isaiah,
-----Original Message-----
From: Lucid Duck <lucid_duck@justthetip.ca>
Sent: Wednesday, January 28, 2026 4:01 AM
To: Bitterblue Smith <rtl8821cerfe2@gmail.com>
Cc: linux-wireless@vger.kernel.org; Ping-Ke Shih <pkshih@realtek.com>; Mh_chen <mh_chen@realtek.com>
Subject: Re: [PATCH] wifi: rtw89: usb: fix TX flow control by tracking in-flight URBs
External mail : This email originated from outside the organization. Do not reply, click links, or open attachments unless you recognize the sender and know the content is safe.
On Mon, 26 Jan 2026, Bitterblue Smith wrote:
> The CH12 checks in the completion handler are unnecessary since there
> is one in the resource checking function.
You're right. I'll remove the redundant check in v2.
> Is there a reason to add a new counter instead of just using the
> length of each tx_queue?
Good question. The tx_queue length tracks SKBs queued for submission, but doesn't account for URBs that have been submitted to USB core and are in-flight (between submit and completion callback). An SKB is dequeued before usb_submit_urb(), so skb_queue_len() would undercount actual resource usage.
The atomic counter tracks the full lifecycle: incremented at submit, decremented at completion. This gives mac80211 accurate backpressure even when URBs are pending in the USB subsystem.
Happy to hear if I'm missing something that would make skb_queue_len() work here.
--
Lucid Duck
^ permalink raw reply
* [PATCH wireless-next v6 3/3] wifi: mac80211: add initial UHR support
From: Johannes Berg @ 2026-01-29 12:49 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260129134944.a84420ec58d6.I5b11fb0345a933bf497fd802aecc72932d58dd68@changeid>
From: Johannes Berg <johannes.berg@intel.com>
Add support for making UHR connections and accepting AP
stations with UHR support.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
v6:
- fix ieee80211_uhr_phy_cap() usage
v5:
- parse UHR capa as from AP
v4:
- fix NPCA validation
v3:
- use uhr_oper instead of removed uhr_capa
- fix indentation (Jeff Johnson)
---
include/net/mac80211.h | 35 ++++++++++-
net/mac80211/Makefile | 2 +-
net/mac80211/cfg.c | 16 ++++-
net/mac80211/ieee80211_i.h | 19 +++++-
net/mac80211/main.c | 15 ++++-
net/mac80211/mlme.c | 117 ++++++++++++++++++++++++++++++++++---
net/mac80211/parse.c | 22 ++++++-
net/mac80211/rx.c | 26 +++++++++
net/mac80211/sta_info.c | 13 ++++-
net/mac80211/sta_info.h | 80 ++++++++++++++++++-------
net/mac80211/uhr.c | 31 ++++++++++
net/mac80211/util.c | 36 +++++++++++-
12 files changed, 372 insertions(+), 40 deletions(-)
create mode 100644 net/mac80211/uhr.c
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf3..7a55762f9af8 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7,7 +7,7 @@
* Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*/
#ifndef MAC80211_H
@@ -706,6 +706,7 @@ struct ieee80211_parsed_tpe {
* @pwr_reduction: power constraint of BSS.
* @eht_support: does this BSS support EHT
* @epcs_support: does this BSS support EPCS
+ * @uhr_support: does this BSS support UHR
* @csa_active: marks whether a channel switch is going on.
* @mu_mimo_owner: indicates interface owns MU-MIMO capability
* @chanctx_conf: The channel context this interface is assigned to, or %NULL
@@ -832,6 +833,8 @@ struct ieee80211_bss_conf {
u8 pwr_reduction;
bool eht_support;
bool epcs_support;
+ bool uhr_support;
+
bool csa_active;
bool mu_mimo_owner;
@@ -1598,6 +1601,7 @@ enum mac80211_rx_encoding {
RX_ENC_VHT,
RX_ENC_HE,
RX_ENC_EHT,
+ RX_ENC_UHR,
};
/**
@@ -1631,7 +1635,7 @@ enum mac80211_rx_encoding {
* @antenna: antenna used
* @rate_idx: index of data rate into band's supported rates or MCS index if
* HT or VHT is used (%RX_FLAG_HT/%RX_FLAG_VHT)
- * @nss: number of streams (VHT, HE and EHT only)
+ * @nss: number of streams (VHT, HE, EHT and UHR only)
* @flag: %RX_FLAG_\*
* @encoding: &enum mac80211_rx_encoding
* @bw: &enum rate_info_bw
@@ -1642,6 +1646,11 @@ enum mac80211_rx_encoding {
* @eht: EHT specific rate information
* @eht.ru: EHT RU, from &enum nl80211_eht_ru_alloc
* @eht.gi: EHT GI, from &enum nl80211_eht_gi
+ * @uhr: UHR specific rate information
+ * @uhr.ru: UHR RU, from &enum nl80211_eht_ru_alloc
+ * @uhr.gi: UHR GI, from &enum nl80211_eht_gi
+ * @uhr.elr: UHR ELR MCS was used
+ * @uhr.im: UHR interference mitigation was used
* @rx_flags: internal RX flags for mac80211
* @ampdu_reference: A-MPDU reference number, must be a different value for
* each A-MPDU but the same for each subframe within one A-MPDU
@@ -1673,6 +1682,12 @@ struct ieee80211_rx_status {
u8 ru:4;
u8 gi:2;
} eht;
+ struct {
+ u8 ru:4;
+ u8 gi:2;
+ u8 elr:1;
+ u8 im:1;
+ } uhr;
};
u8 rate_idx;
u8 nss;
@@ -2434,6 +2449,7 @@ struct ieee80211_sta_aggregates {
* @he_cap: HE capabilities of this STA
* @he_6ghz_capa: on 6 GHz, holds the HE 6 GHz band capabilities
* @eht_cap: EHT capabilities of this STA
+ * @uhr_cap: UHR capabilities of this STA
* @s1g_cap: S1G capabilities of this STA
* @agg: per-link data for multi-link aggregation
* @bandwidth: current bandwidth the station can receive with
@@ -2457,6 +2473,7 @@ struct ieee80211_link_sta {
struct ieee80211_sta_he_cap he_cap;
struct ieee80211_he_6ghz_capa he_6ghz_capa;
struct ieee80211_sta_eht_cap eht_cap;
+ struct ieee80211_sta_uhr_cap uhr_cap;
struct ieee80211_sta_s1g_cap s1g_cap;
struct ieee80211_sta_aggregates agg;
@@ -7289,6 +7306,20 @@ ieee80211_get_eht_iftype_cap_vif(const struct ieee80211_supported_band *sband,
return ieee80211_get_eht_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
}
+/**
+ * ieee80211_get_uhr_iftype_cap_vif - return UHR capabilities for sband/vif
+ * @sband: the sband to search for the iftype on
+ * @vif: the vif to get the iftype from
+ *
+ * Return: pointer to the struct ieee80211_sta_uhr_cap, or %NULL is none found
+ */
+static inline const struct ieee80211_sta_uhr_cap *
+ieee80211_get_uhr_iftype_cap_vif(const struct ieee80211_supported_band *sband,
+ struct ieee80211_vif *vif)
+{
+ return ieee80211_get_uhr_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
+}
+
/**
* ieee80211_update_mu_groups - set the VHT MU-MIMO groud data
*
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index a33884967f21..b0e392eb7753 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -36,7 +36,7 @@ mac80211-y := \
tdls.o \
ocb.o \
airtime.o \
- eht.o
+ eht.o uhr.o
mac80211-$(CONFIG_MAC80211_LEDS) += led.o
mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 964f440e31cd..f83dda0755a7 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5,7 +5,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/ieee80211.h>
@@ -1608,6 +1608,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
link_conf->eht_mu_beamformer = false;
}
+ if (params->uhr_oper) {
+ if (!link_conf->eht_support)
+ return -EOPNOTSUPP;
+
+ link_conf->uhr_support = true;
+ }
+
if (sdata->vif.type == NL80211_IFTYPE_AP &&
params->mbssid_config.tx_wdev) {
err = ieee80211_set_ap_mbssid_options(sdata,
@@ -2085,6 +2092,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
params->vht_capa ||
params->he_capa ||
params->eht_capa ||
+ params->uhr_capa ||
params->s1g_capa ||
params->opmode_notif_used;
@@ -2163,6 +2171,12 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
params->eht_capa_len,
link_sta);
+ if (params->uhr_capa)
+ ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
+ params->uhr_capa,
+ params->uhr_capa_len,
+ link_sta);
+
if (params->s1g_capa)
ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
link_sta);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index dc757cb32974..b5e774ea6b07 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -5,7 +5,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#ifndef IEEE80211_I_H
@@ -394,9 +394,10 @@ enum ieee80211_conn_mode {
IEEE80211_CONN_MODE_VHT,
IEEE80211_CONN_MODE_HE,
IEEE80211_CONN_MODE_EHT,
+ IEEE80211_CONN_MODE_UHR,
};
-#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_EHT
+#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_UHR
enum ieee80211_conn_bw_limit {
IEEE80211_CONN_BW_LIMIT_20,
@@ -1826,6 +1827,8 @@ struct ieee802_11_elems {
const struct ieee80211_multi_link_elem *ml_epcs;
const struct ieee80211_bandwidth_indication *bandwidth_indication;
const struct ieee80211_ttlm_elem *ttlm[IEEE80211_TTLM_MAX_CNT];
+ const struct ieee80211_uhr_capa *uhr_capa;
+ const struct ieee80211_uhr_oper *uhr_oper;
/* not the order in the psd values is per element, not per chandef */
struct ieee80211_parsed_tpe tpe;
@@ -1850,6 +1853,8 @@ struct ieee802_11_elems {
u8 country_elem_len;
u8 bssid_index_len;
u8 eht_cap_len;
+ u8 uhr_capa_len;
+ u8 uhr_oper_len;
/* mult-link element can be de-fragmented and thus u8 is not sufficient */
size_t ml_basic_len;
@@ -2693,6 +2698,9 @@ int ieee80211_put_eht_cap(struct sk_buff *skb,
struct ieee80211_sub_if_data *sdata,
const struct ieee80211_supported_band *sband,
const struct ieee80211_conn_settings *conn);
+int ieee80211_put_uhr_cap(struct sk_buff *skb,
+ struct ieee80211_sub_if_data *sdata,
+ const struct ieee80211_supported_band *sband);
int ieee80211_put_reg_conn(struct sk_buff *skb,
enum ieee80211_channel_flags flags);
@@ -2868,6 +2876,13 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgmt *mgmt, size_t len);
void ieee80211_stop_mbssid(struct ieee80211_sub_if_data *sdata);
+void
+ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_supported_band *sband,
+ const struct ieee80211_uhr_capa *uhr_capa,
+ u8 uhr_capa_len,
+ struct link_sta_info *link_sta);
+
#if IS_ENABLED(CONFIG_MAC80211_KUNIT_TEST)
#define EXPORT_SYMBOL_IF_MAC80211_KUNIT(sym) EXPORT_SYMBOL_IF_KUNIT(sym)
#define VISIBLE_IF_MAC80211_KUNIT
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index b05e313c7f17..486a0bf3c0de 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -5,7 +5,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <net/mac80211.h>
@@ -1123,7 +1123,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
int result, i;
enum nl80211_band band;
int channels, max_bitrates;
- bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g;
+ bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g, supp_uhr;
struct cfg80211_chan_def dflt_chandef = {};
if (ieee80211_hw_check(hw, QUEUE_CONTROL) &&
@@ -1237,6 +1237,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
supp_he = false;
supp_eht = false;
supp_s1g = false;
+ supp_uhr = false;
for (band = 0; band < NUM_NL80211_BANDS; band++) {
const struct ieee80211_sband_iftype_data *iftd;
struct ieee80211_supported_band *sband;
@@ -1293,6 +1294,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
supp_he = supp_he || iftd->he_cap.has_he;
supp_eht = supp_eht || iftd->eht_cap.has_eht;
+ supp_uhr = supp_uhr || iftd->uhr_cap.has_uhr;
if (band == NL80211_BAND_2GHZ)
he_40_mhz_cap =
@@ -1325,6 +1327,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (WARN_ON(supp_eht && !supp_he))
return -EINVAL;
+ /* UHR requires EHT support */
+ if (WARN_ON(supp_uhr && !supp_eht))
+ return -EINVAL;
+
if (!sband->ht_cap.ht_supported)
continue;
@@ -1437,6 +1443,11 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
IEEE80211_EHT_PPE_THRES_MAX_LEN;
}
+ if (supp_uhr)
+ local->scan_ies_len +=
+ 3 + sizeof(struct ieee80211_uhr_capa) +
+ sizeof(struct ieee80211_uhr_capa_phy);
+
if (!local->ops->hw_scan) {
/* For hw_scan, driver needs to set these up. */
local->hw.wiphy->max_scan_ssids = 4;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 6e468c4fcda2..df170556825f 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -8,7 +8,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*/
#include <linux/delay.h>
@@ -162,6 +162,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_vht_operation *vht_oper = elems->vht_operation;
const struct ieee80211_he_operation *he_oper = elems->he_operation;
const struct ieee80211_eht_operation *eht_oper = elems->eht_operation;
+ const struct ieee80211_uhr_oper *uhr_oper = elems->uhr_oper;
struct ieee80211_supported_band *sband =
sdata->local->hw.wiphy->bands[channel->band];
struct cfg80211_chan_def vht_chandef;
@@ -192,7 +193,7 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
/* get special 6 GHz case out of the way */
if (sband->band == NL80211_BAND_6GHZ) {
- enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_EHT;
+ enum ieee80211_conn_mode mode = IEEE80211_CONN_MODE_HIGHEST;
/* this is an error */
if (conn->mode < IEEE80211_CONN_MODE_HE)
@@ -215,7 +216,9 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
return IEEE80211_CONN_MODE_LEGACY;
}
- return mode;
+ if (mode <= IEEE80211_CONN_MODE_EHT)
+ return mode;
+ goto check_uhr;
}
/* now we have the progression HT, VHT, ... */
@@ -340,7 +343,63 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
*chandef = eht_chandef;
}
- return IEEE80211_CONN_MODE_EHT;
+check_uhr:
+ if (conn->mode < IEEE80211_CONN_MODE_UHR || !uhr_oper)
+ return IEEE80211_CONN_MODE_EHT;
+
+ /*
+ * In beacons we don't have all the data - but we know the size was OK,
+ * so if the size is valid as a non-beacon case, we have more data and
+ * can validate the NPCA parameters.
+ */
+ if (ieee80211_uhr_oper_size_ok((const void *)uhr_oper,
+ elems->uhr_oper_len,
+ false)) {
+ struct cfg80211_chan_def npca_chandef = *chandef;
+ const struct ieee80211_uhr_npca_info *npca;
+ const __le16 *dis_subch_bmap;
+ u16 punct = chandef->punctured, npca_punct;
+
+ npca = ieee80211_uhr_npca_info(uhr_oper);
+ if (npca) {
+ int width = cfg80211_chandef_get_width(chandef);
+ u8 offs = le32_get_bits(npca->params,
+ IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS);
+ u32 cf1 = chandef->center_freq1;
+ bool pri_upper, npca_upper;
+
+ pri_upper = chandef->chan->center_freq > cf1;
+ npca_upper = 20 * offs >= width / 2;
+
+ if (20 * offs >= cfg80211_chandef_get_width(chandef) ||
+ pri_upper == npca_upper) {
+ sdata_info(sdata,
+ "AP UHR NPCA primary channel invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+ }
+
+ dis_subch_bmap = ieee80211_uhr_npca_dis_subch_bitmap(uhr_oper);
+
+ if (dis_subch_bmap) {
+ npca_punct = get_unaligned_le16(dis_subch_bmap);
+ npca_chandef.punctured = npca_punct;
+ }
+
+ /*
+ * must be a valid puncturing pattern for this channel as
+ * well as puncturing all subchannels that are already in
+ * the disabled subchannel bitmap on the primary channel
+ */
+ if (!cfg80211_chandef_valid(&npca_chandef) ||
+ ((punct & npca_punct) != punct)) {
+ sdata_info(sdata,
+ "AP UHR NPCA disabled subchannel bitmap invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+ }
+
+ return IEEE80211_CONN_MODE_UHR;
}
static bool
@@ -1091,6 +1150,7 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
IEEE80211_CONN_BW_LIMIT_160);
break;
case IEEE80211_CONN_MODE_EHT:
+ case IEEE80211_CONN_MODE_UHR:
conn->bw_limit = min_t(enum ieee80211_conn_bw_limit,
conn->bw_limit,
IEEE80211_CONN_BW_LIMIT_320);
@@ -1108,6 +1168,8 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
set_bit(BSS_MEMBERSHIP_SELECTOR_HE_PHY, sta_selectors);
if (conn->mode >= IEEE80211_CONN_MODE_EHT)
set_bit(BSS_MEMBERSHIP_SELECTOR_EHT_PHY, sta_selectors);
+ if (conn->mode >= IEEE80211_CONN_MODE_UHR)
+ set_bit(BSS_MEMBERSHIP_SELECTOR_UHR_PHY, sta_selectors);
/*
* We do not support EPD or GLK so never add them.
@@ -1155,6 +1217,11 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
IEEE80211_CONN_BW_LIMIT_160);
}
+ if (conn->mode >= IEEE80211_CONN_MODE_UHR &&
+ !cfg80211_chandef_usable(sdata->wdev.wiphy, &chanreq->oper,
+ IEEE80211_CHAN_NO_UHR))
+ conn->mode = IEEE80211_CONN_MODE_EHT;
+
if (chanreq->oper.width != ap_chandef->width || ap_mode != conn->mode)
link_id_info(sdata, link_id,
"regulatory prevented using AP config, downgraded\n");
@@ -1884,11 +1951,13 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
/*
* careful - need to know about all the present elems before
- * calling ieee80211_assoc_add_ml_elem(), so add this one if
- * we're going to put it after the ML element
+ * calling ieee80211_assoc_add_ml_elem(), so add these if
+ * we're going to put them after the ML element
*/
if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_EHT)
ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_EHT_CAPABILITY);
+ if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_UHR)
+ ADD_PRESENT_EXT_ELEM(WLAN_EID_EXT_UHR_CAPA);
if (link_id == assoc_data->assoc_link_id)
ieee80211_assoc_add_ml_elem(sdata, skb, orig_capab, ext_capa,
@@ -1901,6 +1970,9 @@ ieee80211_add_link_elems(struct ieee80211_sub_if_data *sdata,
ieee80211_put_eht_cap(skb, sdata, sband,
&assoc_data->link[link_id].conn);
+ if (assoc_data->link[link_id].conn.mode >= IEEE80211_CONN_MODE_UHR)
+ ieee80211_put_uhr_cap(skb, sdata, sband);
+
if (sband->band == NL80211_BAND_S1GHZ) {
ieee80211_add_aid_request_ie(sdata, skb);
ieee80211_add_s1g_capab_ie(sdata, &sband->s1g_cap, skb);
@@ -2135,6 +2207,9 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata,
sizeof(struct ieee80211_eht_mcs_nss_supp) +
IEEE80211_EHT_PPE_THRES_MAX_LEN;
+ size += 2 + 1 + sizeof(struct ieee80211_uhr_capa) +
+ sizeof(struct ieee80211_uhr_capa_phy);
+
return size;
}
@@ -5531,6 +5606,18 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
bss_conf->epcs_support = false;
}
+ if (elems->uhr_oper && elems->uhr_capa &&
+ link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) {
+ ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
+ elems->uhr_capa,
+ elems->uhr_capa_len,
+ link_sta);
+
+ bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr;
+ } else {
+ bss_conf->uhr_support = false;
+ }
+
if (elems->s1g_oper &&
link->u.mgd.conn.mode == IEEE80211_CONN_MODE_S1G &&
elems->s1g_capab)
@@ -5821,6 +5908,7 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
bool is_6ghz = sband->band == NL80211_BAND_6GHZ;
const struct ieee80211_sta_he_cap *he_cap;
const struct ieee80211_sta_eht_cap *eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap;
struct ieee80211_sta_vht_cap vht_cap;
if (sband->band == NL80211_BAND_S1GHZ) {
@@ -5996,9 +6084,6 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
"no EHT support, limiting to HE\n");
goto out;
}
-
- /* we have EHT */
-
conn->mode = IEEE80211_CONN_MODE_EHT;
/* check bandwidth */
@@ -6009,6 +6094,20 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata,
mlme_link_id_dbg(sdata, link_id,
"no EHT 320 MHz cap in 6 GHz, limiting to 160 MHz\n");
+ if (req && req->flags & ASSOC_REQ_DISABLE_UHR) {
+ mlme_link_id_dbg(sdata, link_id,
+ "UHR disabled by flag, limiting to EHT\n");
+ goto out;
+ }
+
+ uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif);
+ if (!uhr_cap) {
+ mlme_link_id_dbg(sdata, link_id,
+ "no UHR support, limiting to EHT\n");
+ goto out;
+ }
+ conn->mode = IEEE80211_CONN_MODE_UHR;
+
out:
mlme_link_id_dbg(sdata, link_id,
"determined local STA to be %s, BW limited to %d MHz\n",
diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c
index 667021bc60c6..cb238600652a 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -6,7 +6,7 @@
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*
* element parsing for mac80211
*/
@@ -189,6 +189,26 @@ ieee80211_parse_extension_element(u32 *crc,
elems->ttlm_num++;
}
break;
+ case WLAN_EID_EXT_UHR_OPER:
+ if (params->mode < IEEE80211_CONN_MODE_UHR)
+ break;
+ calc_crc = true;
+ if (ieee80211_uhr_oper_size_ok(data, len,
+ params->type == (IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_BEACON))) {
+ elems->uhr_oper = data;
+ elems->uhr_oper_len = len;
+ }
+ break;
+ case WLAN_EID_EXT_UHR_CAPA:
+ if (params->mode < IEEE80211_CONN_MODE_UHR)
+ break;
+ calc_crc = true;
+ if (ieee80211_uhr_capa_size_ok(data, len, true)) {
+ elems->uhr_capa = data;
+ elems->uhr_capa_len = len;
+ }
+ break;
}
if (crc && calc_crc)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 30b9b4d76357..69034d83a7b6 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -5518,6 +5518,32 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
status->rate_idx, status->nss, status->eht.gi))
goto drop;
break;
+ case RX_ENC_UHR:
+ if (WARN_ONCE(!(status->rate_idx <= 15 ||
+ status->rate_idx == 17 ||
+ status->rate_idx == 19 ||
+ status->rate_idx == 20 ||
+ status->rate_idx == 23) ||
+ !status->nss ||
+ status->nss > 8 ||
+ status->uhr.gi > NL80211_RATE_INFO_EHT_GI_3_2,
+ "Rate marked as a UHR rate but data is invalid: MCS:%d, NSS:%d, GI:%d\n",
+ status->rate_idx, status->nss, status->uhr.gi))
+ goto drop;
+ if (WARN_ONCE(status->uhr.elr &&
+ (status->nss != 1 || status->rate_idx > 1 ||
+ status->uhr.gi != NL80211_RATE_INFO_EHT_GI_1_6 ||
+ status->bw != RATE_INFO_BW_20 || status->uhr.im),
+ "bad UHR ELR MCS MCS:%d, NSS:%d, GI:%d, BW:%d, IM:%d\n",
+ status->rate_idx, status->nss, status->uhr.gi,
+ status->bw, status->uhr.im))
+ goto drop;
+ if (WARN_ONCE(status->uhr.im &&
+ (status->nss != 1 || status->rate_idx == 15),
+ "bad UHR IM MCS MCS:%d, NSS:%d\n",
+ status->rate_idx, status->nss))
+ goto drop;
+ break;
default:
WARN_ON_ONCE(1);
fallthrough;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 22e8561ad6fc..a79ebeb43585 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -4,7 +4,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
@@ -2567,6 +2567,17 @@ static void sta_stats_decode_rate(struct ieee80211_local *local, u32 rate,
rinfo->eht_gi = STA_STATS_GET(EHT_GI, rate);
rinfo->eht_ru_alloc = STA_STATS_GET(EHT_RU, rate);
break;
+ case STA_STATS_RATE_TYPE_UHR:
+ rinfo->flags = RATE_INFO_FLAGS_UHR_MCS;
+ rinfo->mcs = STA_STATS_GET(UHR_MCS, rate);
+ rinfo->nss = STA_STATS_GET(UHR_NSS, rate);
+ rinfo->eht_gi = STA_STATS_GET(UHR_GI, rate);
+ rinfo->eht_ru_alloc = STA_STATS_GET(UHR_RU, rate);
+ if (STA_STATS_GET(UHR_ELR, rate))
+ rinfo->flags |= RATE_INFO_FLAGS_UHR_ELR_MCS;
+ if (STA_STATS_GET(UHR_IM, rate))
+ rinfo->flags |= RATE_INFO_FLAGS_UHR_IM;
+ break;
}
}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index b1edf8ed102f..2875ef7d7946 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -3,7 +3,7 @@
* Copyright 2002-2005, Devicescape Software, Inc.
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright(c) 2015-2017 Intel Deutschland GmbH
- * Copyright(c) 2020-2024 Intel Corporation
+ * Copyright(c) 2020-2026 Intel Corporation
*/
#ifndef STA_INFO_H
@@ -1009,25 +1009,49 @@ enum sta_stats_type {
STA_STATS_RATE_TYPE_HE,
STA_STATS_RATE_TYPE_S1G,
STA_STATS_RATE_TYPE_EHT,
+ STA_STATS_RATE_TYPE_UHR,
};
-#define STA_STATS_FIELD_HT_MCS GENMASK( 7, 0)
-#define STA_STATS_FIELD_LEGACY_IDX GENMASK( 3, 0)
-#define STA_STATS_FIELD_LEGACY_BAND GENMASK( 7, 4)
-#define STA_STATS_FIELD_VHT_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_VHT_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_HE_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_HE_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_EHT_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_EHT_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_BW GENMASK(12, 8)
-#define STA_STATS_FIELD_SGI GENMASK(13, 13)
-#define STA_STATS_FIELD_TYPE GENMASK(16, 14)
-#define STA_STATS_FIELD_HE_RU GENMASK(19, 17)
-#define STA_STATS_FIELD_HE_GI GENMASK(21, 20)
-#define STA_STATS_FIELD_HE_DCM GENMASK(22, 22)
-#define STA_STATS_FIELD_EHT_RU GENMASK(20, 17)
-#define STA_STATS_FIELD_EHT_GI GENMASK(22, 21)
+/* common */
+#define STA_STATS_FIELD_TYPE 0x0000000F
+#define STA_STATS_FIELD_BW 0x000001F0
+#define STA_STATS_FIELD_RESERVED 0x00000E00
+
+/* STA_STATS_RATE_TYPE_LEGACY */
+#define STA_STATS_FIELD_LEGACY_IDX 0x0000F000
+#define STA_STATS_FIELD_LEGACY_BAND 0x000F0000
+
+/* STA_STATS_RATE_TYPE_HT */
+#define STA_STATS_FIELD_HT_MCS 0x000FF000
+
+/* STA_STATS_RATE_TYPE_VHT */
+#define STA_STATS_FIELD_VHT_MCS 0x0000F000
+#define STA_STATS_FIELD_VHT_NSS 0x000F0000
+
+/* HT & VHT */
+#define STA_STATS_FIELD_SGI 0x00100000
+
+/* STA_STATS_RATE_TYPE_HE */
+#define STA_STATS_FIELD_HE_MCS 0x0000F000
+#define STA_STATS_FIELD_HE_NSS 0x000F0000
+#define STA_STATS_FIELD_HE_RU 0x00700000
+#define STA_STATS_FIELD_HE_GI 0x01800000
+#define STA_STATS_FIELD_HE_DCM 0x02000000
+
+/* STA_STATS_RATE_TYPE_EHT */
+#define STA_STATS_FIELD_EHT_MCS 0x0000F000
+#define STA_STATS_FIELD_EHT_NSS 0x000F0000
+#define STA_STATS_FIELD_EHT_RU 0x00F00000
+#define STA_STATS_FIELD_EHT_GI 0x03000000
+
+/* STA_STATS_RATE_TYPE_UHR */
+#define STA_STATS_FIELD_UHR_MCS 0x0001F000
+#define STA_STATS_FIELD_UHR_NSS 0x001E0000
+#define STA_STATS_FIELD_UHR_RU 0x01E00000
+#define STA_STATS_FIELD_UHR_GI 0x06000000
+#define STA_STATS_FIELD_UHR_ELR 0x08000000
+#define STA_STATS_FIELD_UHR_IM 0x10000000
+
#define STA_STATS_FIELD(_n, _v) FIELD_PREP(STA_STATS_FIELD_ ## _n, _v)
#define STA_STATS_GET(_n, _v) FIELD_GET(STA_STATS_FIELD_ ## _n, _v)
@@ -1040,8 +1064,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
r = STA_STATS_FIELD(BW, s->bw);
- if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
- r |= STA_STATS_FIELD(SGI, 1);
+ switch (s->encoding) {
+ case RX_ENC_HT:
+ case RX_ENC_VHT:
+ if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
+ r |= STA_STATS_FIELD(SGI, 1);
+ break;
+ default:
+ break;
+ }
switch (s->encoding) {
case RX_ENC_VHT:
@@ -1073,6 +1104,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
r |= STA_STATS_FIELD(EHT_GI, s->eht.gi);
r |= STA_STATS_FIELD(EHT_RU, s->eht.ru);
break;
+ case RX_ENC_UHR:
+ r |= STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_UHR);
+ r |= STA_STATS_FIELD(UHR_NSS, s->nss);
+ r |= STA_STATS_FIELD(UHR_MCS, s->rate_idx);
+ r |= STA_STATS_FIELD(UHR_GI, s->uhr.gi);
+ r |= STA_STATS_FIELD(UHR_RU, s->uhr.ru);
+ r |= STA_STATS_FIELD(UHR_ELR, s->uhr.elr);
+ r |= STA_STATS_FIELD(UHR_IM, s->uhr.im);
+ break;
default:
WARN_ON(1);
return STA_STATS_RATE_INVALID;
diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c
new file mode 100644
index 000000000000..a9970c07b5fc
--- /dev/null
+++ b/net/mac80211/uhr.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UHR handling
+ *
+ * Copyright(c) 2025-2026 Intel Corporation
+ */
+
+#include "ieee80211_i.h"
+
+void
+ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_supported_band *sband,
+ const struct ieee80211_uhr_capa *uhr_capa,
+ u8 uhr_capa_len,
+ struct link_sta_info *link_sta)
+{
+ struct ieee80211_sta_uhr_cap *uhr_cap = &link_sta->pub->uhr_cap;
+ bool ap;
+
+ memset(uhr_cap, 0, sizeof(*uhr_cap));
+
+ if (!uhr_capa ||
+ !ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif))
+ return;
+
+ uhr_cap->has_uhr = true;
+
+ uhr_cap->mac = uhr_capa->mac;
+ ap = sdata->vif.type == NL80211_IFTYPE_STATION;
+ uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_capa, ap);
+}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 4d5680da7aa0..97433347c26d 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -6,7 +6,7 @@
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*
* utilities for mac80211
*/
@@ -1421,6 +1421,13 @@ static int ieee80211_put_preq_ies_band(struct sk_buff *skb,
if (err)
return err;
+ if (cfg80211_any_usable_channels(local->hw.wiphy, BIT(sband->band),
+ IEEE80211_CHAN_NO_UHR)) {
+ err = ieee80211_put_uhr_cap(skb, sdata, sband);
+ if (err)
+ return err;
+ }
+
/*
* If adding more here, adjust code in main.c
* that calculates local->scan_ies_len.
@@ -4527,6 +4534,32 @@ int ieee80211_put_eht_cap(struct sk_buff *skb,
return 0;
}
+int ieee80211_put_uhr_cap(struct sk_buff *skb,
+ struct ieee80211_sub_if_data *sdata,
+ const struct ieee80211_supported_band *sband)
+{
+ const struct ieee80211_sta_uhr_cap *uhr_cap =
+ ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif);
+ int len;
+
+ if (!uhr_cap)
+ return 0;
+
+ len = 2 + 1 + sizeof(struct ieee80211_uhr_capa) +
+ sizeof(struct ieee80211_uhr_capa_phy);
+
+ if (skb_tailroom(skb) < len)
+ return -ENOBUFS;
+
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ skb_put_u8(skb, len - 2);
+ skb_put_u8(skb, WLAN_EID_EXT_UHR_CAPA);
+ skb_put_data(skb, &uhr_cap->mac, sizeof(uhr_cap->mac));
+ skb_put_data(skb, &uhr_cap->phy, sizeof(uhr_cap->phy));
+
+ return 0;
+}
+
const char *ieee80211_conn_mode_str(enum ieee80211_conn_mode mode)
{
static const char * const modes[] = {
@@ -4536,6 +4569,7 @@ const char *ieee80211_conn_mode_str(enum ieee80211_conn_mode mode)
[IEEE80211_CONN_MODE_VHT] = "VHT",
[IEEE80211_CONN_MODE_HE] = "HE",
[IEEE80211_CONN_MODE_EHT] = "EHT",
+ [IEEE80211_CONN_MODE_UHR] = "UHR",
};
if (WARN_ON(mode >= ARRAY_SIZE(modes)))
--
2.52.0
^ permalink raw reply related
* [PATCH wireless-next v6 2/3] wifi: cfg80211: add initial UHR support
From: Johannes Berg @ 2026-01-29 12:49 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260129134944.a84420ec58d6.I5b11fb0345a933bf497fd802aecc72932d58dd68@changeid>
From: Johannes Berg <johannes.berg@intel.com>
Add initial support for making UHR connections (or suppressing
that), adding UHR capable stations on the AP side, encoding
and decoding UHR MCSes (except rate calculation for the new
MCSes 17, 19, 20 and 23) as well as regulatory support.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
v5:
- validate NL80211_ATTR_UHR_CAPABILITY for non-AP (only)
v4:
- check for correct NSS/MCS for interference mitigation
v3:
- remove UHR capa pointer from AP settings, it's not in
the beacon anyway
- fix kernel-doc (Jeff Johnson)
---
include/net/cfg80211.h | 58 ++++++++++++++++++--
include/uapi/linux/nl80211.h | 30 +++++++++++
net/wireless/nl80211.c | 102 +++++++++++++++++++++++++++++++++--
net/wireless/reg.c | 4 +-
net/wireless/util.c | 101 ++++++++++++++++++++++++++--------
5 files changed, 265 insertions(+), 30 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 0ae0aa7594a3..7e7a76b314f9 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -7,7 +7,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/ethtool.h>
@@ -126,6 +126,7 @@ struct wiphy;
* @IEEE80211_CHAN_NO_4MHZ: 4 MHz bandwidth is not permitted on this channel.
* @IEEE80211_CHAN_NO_8MHZ: 8 MHz bandwidth is not permitted on this channel.
* @IEEE80211_CHAN_NO_16MHZ: 16 MHz bandwidth is not permitted on this channel.
+ * @IEEE80211_CHAN_NO_UHR: UHR operation is not permitted on this channel.
*/
enum ieee80211_channel_flags {
IEEE80211_CHAN_DISABLED = BIT(0),
@@ -143,6 +144,7 @@ enum ieee80211_channel_flags {
IEEE80211_CHAN_NO_10MHZ = BIT(12),
IEEE80211_CHAN_NO_HE = BIT(13),
/* can use free bits here */
+ IEEE80211_CHAN_NO_UHR = BIT(18),
IEEE80211_CHAN_NO_320MHZ = BIT(19),
IEEE80211_CHAN_NO_EHT = BIT(20),
IEEE80211_CHAN_DFS_CONCURRENT = BIT(21),
@@ -429,6 +431,18 @@ struct ieee80211_sta_eht_cap {
u8 eht_ppe_thres[IEEE80211_EHT_PPE_THRES_MAX_LEN];
};
+/**
+ * struct ieee80211_sta_uhr_cap - STA's UHR capabilities
+ * @has_uhr: true iff UHR is supported and data is valid
+ * @mac: fixed MAC capabilities
+ * @phy: fixed PHY capabilities
+ */
+struct ieee80211_sta_uhr_cap {
+ bool has_uhr;
+ struct ieee80211_uhr_capa_mac mac;
+ struct ieee80211_uhr_capa_phy phy;
+};
+
/* sparse defines __CHECKER__; see Documentation/dev-tools/sparse.rst */
#ifdef __CHECKER__
/*
@@ -454,6 +468,7 @@ struct ieee80211_sta_eht_cap {
* @he_6ghz_capa: HE 6 GHz capabilities, must be filled in for a
* 6 GHz band channel (and 0 may be valid value).
* @eht_cap: STA's EHT capabilities
+ * @uhr_cap: STA's UHR capabilities
* @vendor_elems: vendor element(s) to advertise
* @vendor_elems.data: vendor element(s) data
* @vendor_elems.len: vendor element(s) length
@@ -463,6 +478,7 @@ struct ieee80211_sband_iftype_data {
struct ieee80211_sta_he_cap he_cap;
struct ieee80211_he_6ghz_capa he_6ghz_capa;
struct ieee80211_sta_eht_cap eht_cap;
+ struct ieee80211_sta_uhr_cap uhr_cap;
struct {
const u8 *data;
unsigned int len;
@@ -704,6 +720,26 @@ ieee80211_get_eht_iftype_cap(const struct ieee80211_supported_band *sband,
return NULL;
}
+/**
+ * ieee80211_get_uhr_iftype_cap - return UHR capabilities for an sband's iftype
+ * @sband: the sband to search for the iftype on
+ * @iftype: enum nl80211_iftype
+ *
+ * Return: pointer to the struct ieee80211_sta_uhr_cap, or NULL is none found
+ */
+static inline const struct ieee80211_sta_uhr_cap *
+ieee80211_get_uhr_iftype_cap(const struct ieee80211_supported_band *sband,
+ enum nl80211_iftype iftype)
+{
+ const struct ieee80211_sband_iftype_data *data =
+ ieee80211_get_sband_iftype_data(sband, iftype);
+
+ if (data && data->uhr_cap.has_uhr)
+ return &data->uhr_cap;
+
+ return NULL;
+}
+
/**
* wiphy_read_of_freq_limits - read frequency limits from device tree
*
@@ -1486,6 +1522,7 @@ struct cfg80211_s1g_short_beacon {
* @he_cap: HE capabilities (or %NULL if HE isn't enabled)
* @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled)
* @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
+ * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
* @ht_required: stations must support HT
* @vht_required: stations must support VHT
* @twt_responder: Enable Target Wait Time
@@ -1525,6 +1562,7 @@ struct cfg80211_ap_settings {
const struct ieee80211_he_operation *he_oper;
const struct ieee80211_eht_cap_elem *eht_cap;
const struct ieee80211_eht_operation *eht_oper;
+ const struct ieee80211_uhr_oper *uhr_oper;
bool ht_required, vht_required, he_required, sae_h2e_required;
bool twt_responder;
u32 flags;
@@ -1698,6 +1736,8 @@ struct sta_txpwr {
* @eht_capa: EHT capabilities of station
* @eht_capa_len: the length of the EHT capabilities
* @s1g_capa: S1G capabilities of station
+ * @uhr_capa: UHR capabilities of the station
+ * @uhr_capa_len: the length of the UHR capabilities
*/
struct link_station_parameters {
const u8 *mld_mac;
@@ -1717,6 +1757,8 @@ struct link_station_parameters {
const struct ieee80211_eht_cap_elem *eht_capa;
u8 eht_capa_len;
const struct ieee80211_s1g_cap *s1g_capa;
+ const struct ieee80211_uhr_capa *uhr_capa;
+ u8 uhr_capa_len;
};
/**
@@ -1898,6 +1940,11 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
* @RATE_INFO_FLAGS_EXTENDED_SC_DMG: 60GHz extended SC MCS
* @RATE_INFO_FLAGS_EHT_MCS: EHT MCS information
* @RATE_INFO_FLAGS_S1G_MCS: MCS field filled with S1G MCS
+ * @RATE_INFO_FLAGS_UHR_MCS: UHR MCS information
+ * @RATE_INFO_FLAGS_UHR_ELR_MCS: UHR ELR MCS was used
+ * (set together with @RATE_INFO_FLAGS_UHR_MCS)
+ * @RATE_INFO_FLAGS_UHR_IM: UHR Interference Mitigation
+ * was used
*/
enum rate_info_flags {
RATE_INFO_FLAGS_MCS = BIT(0),
@@ -1909,6 +1956,9 @@ enum rate_info_flags {
RATE_INFO_FLAGS_EXTENDED_SC_DMG = BIT(6),
RATE_INFO_FLAGS_EHT_MCS = BIT(7),
RATE_INFO_FLAGS_S1G_MCS = BIT(8),
+ RATE_INFO_FLAGS_UHR_MCS = BIT(9),
+ RATE_INFO_FLAGS_UHR_ELR_MCS = BIT(10),
+ RATE_INFO_FLAGS_UHR_IM = BIT(11),
};
/**
@@ -1924,7 +1974,7 @@ enum rate_info_flags {
* @RATE_INFO_BW_160: 160 MHz bandwidth
* @RATE_INFO_BW_HE_RU: bandwidth determined by HE RU allocation
* @RATE_INFO_BW_320: 320 MHz bandwidth
- * @RATE_INFO_BW_EHT_RU: bandwidth determined by EHT RU allocation
+ * @RATE_INFO_BW_EHT_RU: bandwidth determined by EHT/UHR RU allocation
* @RATE_INFO_BW_1: 1 MHz bandwidth
* @RATE_INFO_BW_2: 2 MHz bandwidth
* @RATE_INFO_BW_4: 4 MHz bandwidth
@@ -1955,7 +2005,7 @@ enum rate_info_bw {
*
* @flags: bitflag of flags from &enum rate_info_flags
* @legacy: bitrate in 100kbit/s for 802.11abg
- * @mcs: mcs index if struct describes an HT/VHT/HE/EHT/S1G rate
+ * @mcs: mcs index if struct describes an HT/VHT/HE/EHT/S1G/UHR rate
* @nss: number of streams (VHT & HE only)
* @bw: bandwidth (from &enum rate_info_bw)
* @he_gi: HE guard interval (from &enum nl80211_he_gi)
@@ -3265,6 +3315,7 @@ struct cfg80211_ml_reconf_req {
* Drivers shall disable MLO features for the current association if this
* flag is not set.
* @ASSOC_REQ_SPP_AMSDU: SPP A-MSDUs will be used on this connection (if any)
+ * @ASSOC_REQ_DISABLE_UHR: Disable UHR
*/
enum cfg80211_assoc_req_flags {
ASSOC_REQ_DISABLE_HT = BIT(0),
@@ -3275,6 +3326,7 @@ enum cfg80211_assoc_req_flags {
ASSOC_REQ_DISABLE_EHT = BIT(5),
CONNECT_REQ_MLO_SUPPORT = BIT(6),
ASSOC_REQ_SPP_AMSDU = BIT(7),
+ ASSOC_REQ_DISABLE_UHR = BIT(8),
};
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 54ddbd9a5459..2254a6dead39 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2976,6 +2976,13 @@ enum nl80211_commands {
* @NL80211_ATTR_EPP_PEER: A flag attribute to indicate if the peer is an EPP
* STA. Used with %NL80211_CMD_NEW_STA and %NL80211_CMD_ADD_LINK_STA
*
+ * @NL80211_ATTR_UHR_CAPABILITY: UHR Capability information element (from
+ * association request when used with NL80211_CMD_NEW_STATION). Can be set
+ * only if HE/EHT are also available.
+ * @NL80211_ATTR_DISABLE_UHR: Force UHR capable interfaces to disable
+ * this feature during association. This is a flag attribute.
+ * Currently only supported in mac80211 drivers.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3546,6 +3553,9 @@ enum nl80211_attrs {
NL80211_ATTR_EPP_PEER,
+ NL80211_ATTR_UHR_CAPABILITY,
+ NL80211_ATTR_DISABLE_UHR,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -3898,6 +3908,12 @@ enum nl80211_eht_ru_alloc {
* @NL80211_RATE_INFO_4_MHZ_WIDTH: 4 MHz S1G rate
* @NL80211_RATE_INFO_8_MHZ_WIDTH: 8 MHz S1G rate
* @NL80211_RATE_INFO_16_MHZ_WIDTH: 16 MHz S1G rate
+ * @NL80211_RATE_INFO_UHR_MCS: UHR MCS index (u8, 0-15, 17, 19, 20, 23)
+ * Note that the other EHT attributes (such as @NL80211_RATE_INFO_EHT_NSS)
+ * are used in conjunction with this where applicable
+ * @NL80211_RATE_INFO_UHR_ELR: UHR ELR flag, which restricts NSS to 1,
+ * MCS to 0 or 1, and GI to %NL80211_RATE_INFO_EHT_GI_1_6.
+ * @NL80211_RATE_INFO_UHR_IM: UHR Interference Mitigation flag
* @__NL80211_RATE_INFO_AFTER_LAST: internal use
*/
enum nl80211_rate_info {
@@ -3931,6 +3947,9 @@ enum nl80211_rate_info {
NL80211_RATE_INFO_4_MHZ_WIDTH,
NL80211_RATE_INFO_8_MHZ_WIDTH,
NL80211_RATE_INFO_16_MHZ_WIDTH,
+ NL80211_RATE_INFO_UHR_MCS,
+ NL80211_RATE_INFO_UHR_ELR,
+ NL80211_RATE_INFO_UHR_IM,
/* keep last */
__NL80211_RATE_INFO_AFTER_LAST,
@@ -4253,6 +4272,10 @@ enum nl80211_mpath_info {
* capabilities element
* @NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE: EHT PPE thresholds information as
* defined in EHT capabilities element
+ * @NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC: UHR MAC capabilities as in UHR
+ * capabilities element
+ * @NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY: UHR PHY capabilities as in UHR
+ * capabilities element
* @__NL80211_BAND_IFTYPE_ATTR_AFTER_LAST: internal use
* @NL80211_BAND_IFTYPE_ATTR_MAX: highest band attribute currently defined
*/
@@ -4270,6 +4293,8 @@ enum nl80211_band_iftype_attr {
NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PHY,
NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MCS_SET,
NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE,
+ NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC,
+ NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY,
/* keep last */
__NL80211_BAND_IFTYPE_ATTR_AFTER_LAST,
@@ -4452,6 +4477,8 @@ enum nl80211_wmm_rule {
* @NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY: Channel is not permitted for use
* as a primary channel. Does not prevent the channel from existing
* as a non-primary subchannel. Only applicable to S1G channels.
+ * @NL80211_FREQUENCY_ATTR_NO_UHR: UHR operation is not allowed on this channel
+ * in current regulatory domain.
* @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number
* currently defined
* @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use
@@ -4501,6 +4528,7 @@ enum nl80211_frequency_attr {
NL80211_FREQUENCY_ATTR_NO_8MHZ,
NL80211_FREQUENCY_ATTR_NO_16MHZ,
NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY,
+ NL80211_FREQUENCY_ATTR_NO_UHR,
/* keep last */
__NL80211_FREQUENCY_ATTR_AFTER_LAST,
@@ -4714,6 +4742,7 @@ enum nl80211_sched_scan_match_attr {
* despite NO_IR configuration.
* @NL80211_RRF_ALLOW_20MHZ_ACTIVITY: Allow activity in 20 MHz bandwidth,
* despite NO_IR configuration.
+ * @NL80211_RRF_NO_UHR: UHR operation not allowed
*/
enum nl80211_reg_rule_flags {
NL80211_RRF_NO_OFDM = 1 << 0,
@@ -4740,6 +4769,7 @@ enum nl80211_reg_rule_flags {
NL80211_RRF_NO_6GHZ_AFC_CLIENT = 1 << 23,
NL80211_RRF_ALLOW_6GHZ_VLP_AP = 1 << 24,
NL80211_RRF_ALLOW_20MHZ_ACTIVITY = 1 << 25,
+ NL80211_RRF_NO_UHR = 1 << 26,
};
#define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 85e30fda4c46..181205eae755 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -332,6 +332,15 @@ static int validate_nan_cluster_id(const struct nlattr *attr,
return 0;
}
+static int validate_uhr_capa(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+
+ return ieee80211_uhr_capa_size_ok(data, len, false);
+}
+
/* policy for the attributes */
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR];
@@ -934,6 +943,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_BSS_PARAM] = { .type = NLA_FLAG },
[NL80211_ATTR_S1G_PRIMARY_2MHZ] = { .type = NLA_FLAG },
[NL80211_ATTR_EPP_PEER] = { .type = NLA_FLAG },
+ [NL80211_ATTR_UHR_CAPABILITY] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_capa, 255),
+ [NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -1319,6 +1331,9 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
if ((chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY))
goto nla_put_failure;
+ if ((chan->flags & IEEE80211_CHAN_NO_UHR) &&
+ nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_UHR))
+ goto nla_put_failure;
}
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
@@ -1954,6 +1969,7 @@ nl80211_send_iftype_data(struct sk_buff *msg,
{
const struct ieee80211_sta_he_cap *he_cap = &iftdata->he_cap;
const struct ieee80211_sta_eht_cap *eht_cap = &iftdata->eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap = &iftdata->uhr_cap;
if (nl80211_put_iftypes(msg, NL80211_BAND_IFTYPE_ATTR_IFTYPES,
iftdata->types_mask))
@@ -2005,6 +2021,14 @@ nl80211_send_iftype_data(struct sk_buff *msg,
return -ENOBUFS;
}
+ if (uhr_cap->has_uhr) {
+ if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC,
+ sizeof(uhr_cap->mac), &uhr_cap->mac) ||
+ nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY,
+ sizeof(uhr_cap->phy), &uhr_cap->phy))
+ return -ENOBUFS;
+ }
+
if (sband->band == NL80211_BAND_6GHZ &&
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA,
sizeof(iftdata->he_6ghz_capa),
@@ -6462,6 +6486,17 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
cap->datalen - 1))
return -EINVAL;
}
+
+ cap = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
+ if (cap) {
+ if (!cap->datalen)
+ return -EINVAL;
+ params->uhr_oper = (void *)(cap->data + 1);
+ if (!ieee80211_uhr_oper_size_ok((const u8 *)params->uhr_oper,
+ cap->datalen - 1, true))
+ return -EINVAL;
+ }
+
return 0;
}
@@ -6593,6 +6628,9 @@ static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params
(channel->flags & IEEE80211_CHAN_NO_EHT))
return -EOPNOTSUPP;
+ if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
+ return -EOPNOTSUPP;
+
return 0;
}
@@ -7175,7 +7213,8 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr)
break;
case RATE_INFO_BW_EHT_RU:
rate_flg = 0;
- WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS));
+ WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS) &&
+ !(info->flags & RATE_INFO_FLAGS_UHR_MCS));
break;
}
@@ -7228,6 +7267,23 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr)
nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC,
info->eht_ru_alloc))
return false;
+ } else if (info->flags & RATE_INFO_FLAGS_UHR_MCS) {
+ if (nla_put_u8(msg, NL80211_RATE_INFO_UHR_MCS, info->mcs))
+ return false;
+ if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_NSS, info->nss))
+ return false;
+ if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_GI, info->eht_gi))
+ return false;
+ if (info->bw == RATE_INFO_BW_EHT_RU &&
+ nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC,
+ info->eht_ru_alloc))
+ return false;
+ if (info->flags & RATE_INFO_FLAGS_UHR_ELR_MCS &&
+ nla_put_flag(msg, NL80211_RATE_INFO_UHR_ELR))
+ return false;
+ if (info->flags & RATE_INFO_FLAGS_UHR_IM &&
+ nla_put_flag(msg, NL80211_RATE_INFO_UHR_IM))
+ return false;
}
nla_nest_end(msg, rate);
@@ -8101,7 +8157,8 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
if (params->ext_capab || params->link_sta_params.ht_capa ||
params->link_sta_params.vht_capa ||
params->link_sta_params.he_capa ||
- params->link_sta_params.eht_capa)
+ params->link_sta_params.eht_capa ||
+ params->link_sta_params.uhr_capa)
return -EINVAL;
if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_SPP_AMSDU))
return -EINVAL;
@@ -8321,6 +8378,16 @@ static int nl80211_set_station_tdls(struct genl_info *info,
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params->link_sta_params.eht_capa)
+ return -EINVAL;
+
+ params->link_sta_params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params->link_sta_params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_S1G_CAPABILITY])
params->link_sta_params.s1g_capa =
nla_data(info->attrs[NL80211_ATTR_S1G_CAPABILITY]);
@@ -8641,6 +8708,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params.link_sta_params.eht_capa)
+ return -EINVAL;
+
+ params.link_sta_params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params.link_sta_params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_EML_CAPABILITY]) {
params.eml_cap_present = true;
params.eml_cap =
@@ -8700,10 +8777,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
params.link_sta_params.ht_capa = NULL;
params.link_sta_params.vht_capa = NULL;
- /* HE and EHT require WME */
+ /* HE, EHT and UHR require WME */
if (params.link_sta_params.he_capa_len ||
params.link_sta_params.he_6ghz_capa ||
- params.link_sta_params.eht_capa_len)
+ params.link_sta_params.eht_capa_len ||
+ params.link_sta_params.uhr_capa_len)
return -EINVAL;
}
@@ -12379,6 +12457,9 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_EHT]))
req.flags |= ASSOC_REQ_DISABLE_EHT;
+ if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_UHR]))
+ req.flags |= ASSOC_REQ_DISABLE_UHR;
+
if (info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK])
memcpy(&req.vht_capa_mask,
nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]),
@@ -13258,6 +13339,9 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_EHT]))
connect.flags |= ASSOC_REQ_DISABLE_EHT;
+ if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_UHR]))
+ connect.flags |= ASSOC_REQ_DISABLE_UHR;
+
if (info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK])
memcpy(&connect.vht_capa_mask,
nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]),
@@ -17690,6 +17774,16 @@ nl80211_add_mod_link_station(struct sk_buff *skb, struct genl_info *info,
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params.eht_capa)
+ return -EINVAL;
+
+ params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY])
params.he_6ghz_capa =
nla_data(info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY]);
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 6cbfa3b78311..139cb27e5a81 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -5,7 +5,7 @@
* Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -1605,6 +1605,8 @@ static u32 map_regdom_flags(u32 rd_flags)
channel_flags |= IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP;
if (rd_flags & NL80211_RRF_ALLOW_20MHZ_ACTIVITY)
channel_flags |= IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY;
+ if (rd_flags & NL80211_RRF_NO_UHR)
+ channel_flags |= IEEE80211_CHAN_NO_UHR;
return channel_flags;
}
diff --git a/net/wireless/util.c b/net/wireless/util.c
index cc55b759694e..371149220031 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -5,7 +5,7 @@
* Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2023, 2025 Intel Corporation
+ * Copyright (C) 2018-2023, 2025-2026 Intel Corporation
*/
#include <linux/export.h>
#include <linux/bitops.h>
@@ -1572,26 +1572,30 @@ static u32 cfg80211_calculate_bitrate_he(struct rate_info *rate)
return result / 10000;
}
-static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate)
+static u32 _cfg80211_calculate_bitrate_eht_uhr(struct rate_info *rate)
{
#define SCALE 6144
- static const u32 mcs_divisors[16] = {
- 102399, /* 16.666666... */
- 51201, /* 8.333333... */
- 34134, /* 5.555555... */
- 25599, /* 4.166666... */
- 17067, /* 2.777777... */
- 12801, /* 2.083333... */
- 11377, /* 1.851725... */
- 10239, /* 1.666666... */
- 8532, /* 1.388888... */
- 7680, /* 1.250000... */
- 6828, /* 1.111111... */
- 6144, /* 1.000000... */
- 5690, /* 0.926106... */
- 5120, /* 0.833333... */
- 409600, /* 66.666666... */
- 204800, /* 33.333333... */
+ static const u32 mcs_divisors[] = {
+ [ 0] = 102399, /* 16.666666... */
+ [ 1] = 51201, /* 8.333333... */
+ [ 2] = 34134, /* 5.555555... */
+ [ 3] = 25599, /* 4.166666... */
+ [ 4] = 17067, /* 2.777777... */
+ [ 5] = 12801, /* 2.083333... */
+ [ 6] = 11377, /* 1.851725... */
+ [ 7] = 10239, /* 1.666666... */
+ [ 8] = 8532, /* 1.388888... */
+ [ 9] = 7680, /* 1.250000... */
+ [10] = 6828, /* 1.111111... */
+ [11] = 6144, /* 1.000000... */
+ [12] = 5690, /* 0.926106... */
+ [13] = 5120, /* 0.833333... */
+ [14] = 409600, /* 66.666666... */
+ [15] = 204800, /* 33.333333... */
+ [17] = 38400, /* 6.250180... */
+ [19] = 19200, /* 3.125090... */
+ [20] = 15360, /* 2.500000... */
+ [23] = 9600, /* 1.562545... */
};
static const u32 rates_996[3] = { 480388888, 453700000, 408333333 };
static const u32 rates_484[3] = { 229411111, 216666666, 195000000 };
@@ -1602,8 +1606,6 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate)
u64 tmp;
u32 result;
- if (WARN_ON_ONCE(rate->mcs > 15))
- return 0;
if (WARN_ON_ONCE(rate->eht_gi > NL80211_RATE_INFO_EHT_GI_3_2))
return 0;
if (WARN_ON_ONCE(rate->eht_ru_alloc >
@@ -1684,7 +1686,7 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate)
rate->eht_ru_alloc == NL80211_RATE_INFO_EHT_RU_ALLOC_26)
result = rates_26[rate->eht_gi];
else {
- WARN(1, "invalid EHT MCS: bw:%d, ru:%d\n",
+ WARN(1, "invalid EHT or UHR MCS: bw:%d, ru:%d\n",
rate->bw, rate->eht_ru_alloc);
return 0;
}
@@ -1698,11 +1700,64 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate)
tmp *= rate->nss;
do_div(tmp, 8);
+ /* and handle interference mitigation - 0.9x */
+ if (rate->flags & RATE_INFO_FLAGS_UHR_IM) {
+ if (WARN(rate->nss != 1 || rate->mcs != 15,
+ "invalid NSS or MCS for UHR IM\n"))
+ return 0;
+ tmp *= 9000;
+ do_div(tmp, 10000);
+ }
+
result = tmp;
return result / 10000;
}
+static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate)
+{
+ if (WARN_ONCE(rate->mcs > 15, "bad EHT MCS %d\n", rate->mcs))
+ return 0;
+
+ if (WARN_ONCE(rate->flags & (RATE_INFO_FLAGS_UHR_ELR_MCS |
+ RATE_INFO_FLAGS_UHR_IM),
+ "bad EHT MCS flags 0x%x\n", rate->flags))
+ return 0;
+
+ return _cfg80211_calculate_bitrate_eht_uhr(rate);
+}
+
+static u32 cfg80211_calculate_bitrate_uhr(struct rate_info *rate)
+{
+ if (rate->flags & RATE_INFO_FLAGS_UHR_ELR_MCS) {
+ WARN_ONCE(rate->eht_gi != NL80211_RATE_INFO_EHT_GI_1_6,
+ "bad UHR ELR guard interval %d\n",
+ rate->eht_gi);
+ WARN_ONCE(rate->mcs > 1, "bad UHR ELR MCS %d\n", rate->mcs);
+ WARN_ONCE(rate->nss != 1, "bad UHR ELR NSS %d\n", rate->nss);
+ WARN_ONCE(rate->bw != RATE_INFO_BW_20,
+ "bad UHR ELR bandwidth %d\n",
+ rate->bw);
+ WARN_ONCE(rate->flags & RATE_INFO_FLAGS_UHR_IM,
+ "bad UHR MCS flags 0x%x\n", rate->flags);
+ if (rate->mcs == 0)
+ return 17;
+ return 33;
+ }
+
+ switch (rate->mcs) {
+ case 0 ... 15:
+ case 17:
+ case 19:
+ case 20:
+ case 23:
+ return _cfg80211_calculate_bitrate_eht_uhr(rate);
+ }
+
+ WARN_ONCE(1, "bad UHR MCS %d\n", rate->mcs);
+ return 0;
+}
+
static u32 cfg80211_calculate_bitrate_s1g(struct rate_info *rate)
{
/* For 1, 2, 4, 8 and 16 MHz channels */
@@ -1827,6 +1882,8 @@ u32 cfg80211_calculate_bitrate(struct rate_info *rate)
return cfg80211_calculate_bitrate_he(rate);
if (rate->flags & RATE_INFO_FLAGS_EHT_MCS)
return cfg80211_calculate_bitrate_eht(rate);
+ if (rate->flags & RATE_INFO_FLAGS_UHR_MCS)
+ return cfg80211_calculate_bitrate_uhr(rate);
if (rate->flags & RATE_INFO_FLAGS_S1G_MCS)
return cfg80211_calculate_bitrate_s1g(rate);
--
2.52.0
^ permalink raw reply related
* [PATCH wireless-next v6 1/3] wifi: ieee80211: add some initial UHR definitions
From: Johannes Berg @ 2026-01-29 12:49 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
This is based on Draft P802.11bn_D1.2, but that's still very
incomplete, so don't handle a number of things and make some
local decisions such as using 40 bits for MAC capabilities
and 8 bits for PHY capabilities.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
v6:
- add 'ap' argument to ieee80211_uhr_phy_cap()
v5:
- use correct EHT MCS len (24 bits, not 24 bytes)
- handle DBE AP/non-AP in ieee80211_uhr_capa_size_ok()
v4:
- update to D1.2, including DBE in UHR capabilities
- fold in suggestions from Pablo
---
include/linux/ieee80211-uhr.h | 219 ++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 33 ++++-
2 files changed, 250 insertions(+), 2 deletions(-)
create mode 100644 include/linux/ieee80211-uhr.h
diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h
new file mode 100644
index 000000000000..8fd0c1e558ed
--- /dev/null
+++ b/include/linux/ieee80211-uhr.h
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * IEEE 802.11 UHR definitions
+ *
+ * Copyright (c) 2025-2026 Intel Corporation
+ */
+#ifndef LINUX_IEEE80211_UHR_H
+#define LINUX_IEEE80211_UHR_H
+
+#include <linux/types.h>
+#include <linux/if_ether.h>
+
+#define IEEE80211_UHR_OPER_PARAMS_DPS_ENA 0x0001
+#define IEEE80211_UHR_OPER_PARAMS_NPCA_ENA 0x0002
+#define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0004
+#define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0008
+
+struct ieee80211_uhr_oper {
+ __le16 params;
+ u8 basic_mcs_nss_set[4];
+ u8 variable[];
+} __packed;
+
+#define IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS 0x0000000F
+#define IEEE80211_UHR_NPCA_PARAMS_MIN_DUR_THRESH 0x000000F0
+#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_DELAY 0x00003F00
+#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_BACK_DELAY 0x000FC000
+#define IEEE80211_UHR_NPCA_PARAMS_INIT_QSRC 0x00300000
+#define IEEE80211_UHR_NPCA_PARAMS_MOPLEN 0x00400000
+#define IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES 0x00800000
+
+struct ieee80211_uhr_npca_info {
+ __le32 params;
+ __le16 dis_subch_bmap[];
+} __packed;
+
+static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len,
+ bool beacon)
+{
+ const struct ieee80211_uhr_oper *oper = (const void *)data;
+ u8 needed = sizeof(*oper);
+
+ if (len < needed)
+ return false;
+
+ /* nothing else present in beacons */
+ if (beacon)
+ return true;
+
+ /* FIXME: DPS, DBE, P-EDCA (consider order, also relative to NPCA) */
+
+ if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) {
+ const struct ieee80211_uhr_npca_info *npca =
+ (const void *)oper->variable;
+
+ needed += sizeof(*npca);
+
+ if (len < needed)
+ return false;
+
+ if (npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES))
+ needed += sizeof(npca->dis_subch_bmap[0]);
+ }
+
+ return len >= needed;
+}
+
+/*
+ * 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_npca_info *
+ieee80211_uhr_npca_info(const struct ieee80211_uhr_oper *oper)
+{
+ if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)))
+ return NULL;
+
+ /* FIXME: DPS */
+
+ return (const void *)oper->variable;
+}
+
+static inline const __le16 *
+ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_oper *oper)
+{
+ const struct ieee80211_uhr_npca_info *npca;
+
+ npca = ieee80211_uhr_npca_info(oper);
+ if (!npca)
+ return NULL;
+ if (!(npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES)))
+ return NULL;
+ return npca->dis_subch_bmap;
+}
+
+#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
+#define IEEE80211_UHR_MAC_CAP0_NPCA_SUPP 0x10
+#define IEEE80211_UHR_MAC_CAP0_ENH_BSR_SUPP 0x20
+#define IEEE80211_UHR_MAC_CAP0_ADD_MAP_TID_SUPP 0x40
+#define IEEE80211_UHR_MAC_CAP0_EOTSP_SUPP 0x80
+
+#define IEEE80211_UHR_MAC_CAP1_DSO_SUPP 0x01
+#define IEEE80211_UHR_MAC_CAP1_PEDCA_SUPP 0x02
+#define IEEE80211_UHR_MAC_CAP1_DBE_SUPP 0x04
+#define IEEE80211_UHR_MAC_CAP1_UL_LLI_SUPP 0x08
+#define IEEE80211_UHR_MAC_CAP1_P2P_LLI_SUPP 0x10
+#define IEEE80211_UHR_MAC_CAP1_PUO_SUPP 0x20
+#define IEEE80211_UHR_MAC_CAP1_AP_PUO_SUPP 0x40
+#define IEEE80211_UHR_MAC_CAP1_DUO_SUPP 0x80
+
+#define IEEE80211_UHR_MAC_CAP2_OMC_UL_MU_DIS_RX_SUPP 0x01
+#define IEEE80211_UHR_MAC_CAP2_AOM_SUPP 0x02
+#define IEEE80211_UHR_MAC_CAP2_IFCS_LOC_SUPP 0x04
+#define IEEE80211_UHR_MAC_CAP2_UHR_TRS_SUPP 0x08
+#define IEEE80211_UHR_MAC_CAP2_TXSPG_SUPP 0x10
+#define IEEE80211_UHR_MAC_CAP2_TXOP_RET_IN_TXSPG 0x20
+#define IEEE80211_UHR_MAC_CAP2_UHR_OM_PU_TO_LOW 0xC0
+
+#define IEEE80211_UHR_MAC_CAP3_UHR_OM_PU_TO_HIGH 0x03
+#define IEEE80211_UHR_MAC_CAP3_PARAM_UPD_ADV_NOTIF_INTV 0x1C
+#define IEEE80211_UHR_MAC_CAP3_UPD_IND_TIM_INTV_LOW 0xE0
+
+#define IEEE80211_UHR_MAC_CAP4_UPD_IND_TIM_INTV_HIGH 0x03
+#define IEEE80211_UHR_MAC_CAP4_BOUNDED_ESS 0x04
+#define IEEE80211_UHR_MAC_CAP4_BTM_ASSURANCE 0x08
+#define IEEE80211_UHR_MAC_CAP4_CO_BF_SUPP 0x10
+
+#define IEEE80211_UHR_MAC_CAP_DBE_MAX_BW 0x07
+#define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES 0x08
+#define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES 0x10
+
+struct ieee80211_uhr_capa_mac {
+ u8 mac_cap[5];
+} __packed;
+
+struct ieee80211_uhr_capa {
+ struct ieee80211_uhr_capa_mac mac;
+ /* DBE, PHY capabilities */
+ u8 variable[];
+} __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
+
+struct ieee80211_uhr_capa_phy {
+ u8 cap;
+} __packed;
+
+static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len, bool ap)
+{
+ const struct ieee80211_uhr_capa *cap = (const void *)data;
+ size_t needed = sizeof(*cap) + sizeof(struct ieee80211_uhr_capa_phy);
+
+ if (len < needed)
+ return false;
+
+ /*
+ * A non-AP STA does not include the DBE Capability Parameters field
+ * in the UHR MAC Capabilities Information field.
+ */
+ if (ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
+ u8 dbe;
+
+ needed += 1;
+ if (len < needed)
+ return false;
+
+ dbe = cap->variable[0];
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ needed += 3;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ needed += 3;
+ }
+
+ return len >= needed;
+}
+
+static inline const struct ieee80211_uhr_capa_phy *
+ieee80211_uhr_phy_cap(const struct ieee80211_uhr_capa *cap, bool ap)
+{
+ u8 offs = 0;
+
+ if (ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
+ u8 dbe = cap->variable[0];
+
+ offs += 1;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ offs += 3;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ offs += 3;
+ }
+
+ return (const void *)&cap->variable[offs];
+}
+
+#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
+#define IEEE80211_SMD_INFO_CAPA_PTK_PER_AP_MLD 0x20
+
+struct ieee80211_smd_info {
+ u8 id[ETH_ALEN];
+ u8 capa;
+ __le16 timeout;
+} __packed;
+
+#endif /* LINUX_IEEE80211_UHR_H */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index fbde215c25aa..82d797be95b9 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -9,7 +9,7 @@
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (c) 2018 - 2025 Intel Corporation
+ * Copyright (c) 2018 - 2026 Intel Corporation
*/
#ifndef LINUX_IEEE80211_H
@@ -1200,8 +1200,9 @@ struct ieee80211_mgmt {
#define BSS_MEMBERSHIP_SELECTOR_SAE_H2E 123
#define BSS_MEMBERSHIP_SELECTOR_HE_PHY 122
#define BSS_MEMBERSHIP_SELECTOR_EHT_PHY 121
+#define BSS_MEMBERSHIP_SELECTOR_UHR_PHY 120
-#define BSS_MEMBERSHIP_SELECTOR_MIN BSS_MEMBERSHIP_SELECTOR_EHT_PHY
+#define BSS_MEMBERSHIP_SELECTOR_MIN BSS_MEMBERSHIP_SELECTOR_UHR_PHY
/* mgmt header + 1 byte category code */
#define IEEE80211_MIN_ACTION_SIZE offsetof(struct ieee80211_mgmt, u.action.u)
@@ -1802,6 +1803,15 @@ enum ieee80211_eid_ext {
WLAN_EID_EXT_BANDWIDTH_INDICATION = 135,
WLAN_EID_EXT_KNOWN_STA_IDENTIFCATION = 136,
WLAN_EID_EXT_NON_AP_STA_REG_CON = 137,
+ WLAN_EID_EXT_UHR_OPER = 151,
+ WLAN_EID_EXT_UHR_CAPA = 152,
+ WLAN_EID_EXT_MACP = 153,
+ WLAN_EID_EXT_SMD = 154,
+ WLAN_EID_EXT_BSS_SMD_TRANS_PARAMS = 155,
+ WLAN_EID_EXT_CHAN_USAGE = 156,
+ WLAN_EID_EXT_UHR_MODE_CHG = 157,
+ WLAN_EID_EXT_UHR_PARAM_UPD = 158,
+ WLAN_EID_EXT_TXPI = 159,
};
/* Action category code */
@@ -2745,6 +2755,22 @@ static inline bool for_each_element_completed(const struct element *element,
#define WLAN_RSNX_CAPA_PROTECTED_TWT BIT(4)
#define WLAN_RSNX_CAPA_SAE_H2E BIT(5)
+/* EBPCC = Enhanced BSS Parameter Change Count */
+#define IEEE80211_ENH_CRIT_UPD_EBPCC 0x0F
+#define IEEE80211_ENH_CRIT_UPD_TYPE 0x70
+#define IEEE80211_ENH_CRIT_UPD_TYPE_NO_UHR 0
+#define IEEE80211_ENH_CRIT_UPD_TYPE_UHR 1
+#define IEEE80211_ENH_CRIT_UPD_ALL 0x80
+
+/**
+ * struct ieee80211_enh_crit_upd - enhanced critical update (UHR)
+ * @v: value of the enhanced critical update data,
+ * see %IEEE80211_ENH_CRIT_UPD_* to parse the bits
+ */
+struct ieee80211_enh_crit_upd {
+ u8 v;
+} __packed;
+
/*
* reduced neighbor report, based on Draft P802.11ax_D6.1,
* section 9.4.2.170 and accepted contributions.
@@ -2763,6 +2789,7 @@ static inline bool for_each_element_completed(const struct element *element,
#define IEEE80211_RNR_TBTT_PARAMS_COLOC_ESS 0x10
#define IEEE80211_RNR_TBTT_PARAMS_PROBE_ACTIVE 0x20
#define IEEE80211_RNR_TBTT_PARAMS_COLOC_AP 0x40
+#define IEEE80211_RNR_TBTT_PARAMS_SAME_SMD 0x80
#define IEEE80211_RNR_TBTT_PARAMS_PSD_NO_LIMIT 127
#define IEEE80211_RNR_TBTT_PARAMS_PSD_RESERVED -128
@@ -2815,12 +2842,14 @@ struct ieee80211_tbtt_info_ge_11 {
u8 bss_params;
s8 psd_20;
struct ieee80211_rnr_mld_params mld_params;
+ struct ieee80211_enh_crit_upd enh_crit_upd;
} __packed;
#include "ieee80211-ht.h"
#include "ieee80211-vht.h"
#include "ieee80211-he.h"
#include "ieee80211-eht.h"
+#include "ieee80211-uhr.h"
#include "ieee80211-mesh.h"
#include "ieee80211-s1g.h"
#include "ieee80211-p2p.h"
--
2.52.0
^ permalink raw reply related
* Re: [PATCH wireless-next v5 1/3] wifi: ieee80211: add some initial UHR definitions
From: Johannes Berg @ 2026-01-29 12:32 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless
In-Reply-To: <be12c67cc6d39203db0463aa703efde18d29e51e.camel@sipsolutions.net>
On Thu, 2026-01-29 at 13:27 +0100, Johannes Berg wrote:
> On Thu, 2026-01-29 at 12:42 +0100, Pablo MARTIN-GOMEZ wrote:
> >
> > > +static inline const struct ieee80211_uhr_capa_phy *
> > > +ieee80211_uhr_phy_cap(const struct ieee80211_uhr_capa *cap)
> > > +{
> > > + u8 offs = 0;
> > > +
> > > + if (cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
> > Shouldn't we check for 'ap' here too?
> >
>
> We don't use this in a non-AP context right now, but I guess it makes
> sense to prepare.
Actually, that wasn't even right, I did add some station handling code
on the AP side ...
johannes
^ permalink raw reply
* Re: [PATCH wireless-next v5 1/3] wifi: ieee80211: add some initial UHR definitions
From: Johannes Berg @ 2026-01-29 12:27 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless
In-Reply-To: <0037ce8a-64ca-4986-a937-e35ef92289ab@freebox.fr>
On Thu, 2026-01-29 at 12:42 +0100, Pablo MARTIN-GOMEZ wrote:
>
> > +static inline const struct ieee80211_uhr_capa_phy *
> > +ieee80211_uhr_phy_cap(const struct ieee80211_uhr_capa *cap)
> > +{
> > + u8 offs = 0;
> > +
> > + if (cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
> Shouldn't we check for 'ap' here too?
>
We don't use this in a non-AP context right now, but I guess it makes
sense to prepare.
johannes
^ permalink raw reply
* Problems building rtw89
From: Iohann Tachy @ 2026-01-29 12:12 UTC (permalink / raw)
To: linux-wireless
Good morning,
Currently there's an issue where it's not possible to build the rtw89 driver.
The RTL8852BE card has a compatibility problem with Intel X99 chipset
and a hacked rtw89 driver is needed to make it work.
I kindly request to read the Github issue:
https://github.com/lwfinger/rtw89/issues/396
Regards,
Iohann
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox