* Re: [PATCH wireless-next v4 1/3] wifi: ieee80211: add some initial UHR definitions
From: Karthikeyan Kathirvel @ 2026-01-29 7:33 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260128133033.aa4a43982df0.I5b11fb0345a933bf497fd802aecc72932d58dd68@changeid>
On 1/28/2026 6:00 PM, 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>
> ---
> v4:
> - update to D1.2, including DBE in UHR capabilities
> - fold in suggestions from Pablo
> ---
> include/linux/ieee80211-uhr.h | 215 ++++++++++++++++++++++++++++++++++
> include/linux/ieee80211.h | 33 +++++-
> 2 files changed, 246 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..06556867b004
> --- /dev/null
> +++ b/include/linux/ieee80211-uhr.h
> @@ -0,0 +1,215 @@
> +/* 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)
> +{
> + 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;
> +
> + if (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)
cpu_to_le32 ?
> + needed += 24;
It should be in bytes right offs += 3 ?
> +
> + if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
> + needed += 24;
Same here
> + }
> +
> + return len >= needed;
> +}
> +
> +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) {
> + u8 dbe = cap->variable[0];
> +
> + offs += 1;
> +
> + if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
cpu_to_le32 ?
> + offs += 24;
It should be in bytes right offs += 3 ?
> +
> + if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
> + offs += 24;
Same here
> + }
> +
> + 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"
^ permalink raw reply
* Re: [PATCH] wireless-regdb: Update regulatory info for Tunisia (TN) on 6GHz for 2025
From: Chen-Yu Tsai @ 2026-01-29 7:25 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, wireless-regdb
In-Reply-To: <20260128091634.28983-1-pkshih@gmail.com>
On Wed, 28 Jan 2026 17:16:34 +0800, Ping-Ke Shih wrote:
> By decree no. 2024-641 of December 11, 2024 [1], which applies to
> frequency band 5925-6425 MHz, with power limits 200 mW, for use inside
> buildings, in compliance with EN 303 687.
>
> [1] https://www.anf.tn/sites/default/files/2025-01/Arr%C3%AAt%C3%A92024_4611.pdf
>
>
> [...]
Applied to master in wens/wireless-regdb.git, thanks!
[1/1] wireless-regdb: Update regulatory info for Tunisia (TN) on 6GHz for 2025
https://git.kernel.org/wens/wireless-regdb/c/5cefe5536421
Best regards,
--
Chen-Yu Tsai <wens@kernel.org>
^ permalink raw reply
* Re: [PATCH 04/13] wifi: mt76: mt7921: add mutex protection in critical paths
From: Zac Bowling @ 2026-01-29 6:19 UTC (permalink / raw)
To: Felix Fietkau
Cc: sean.wang, deren.wu, kvalo, linux-kernel, linux-mediatek,
linux-wireless, linux, lorenzo, ryder.lee, sean.wang
In-Reply-To: <a1113ede-9575-433f-a9d4-f8e9c1c6f8ab@nbd.name>
You are right. I caught that too. After reordering, when I went from
the 24-something patch version of this series earlier this month to
this smaller 11-patch series, to make it easier to follow again, that
happened. It's already gone in my new v7 series. We lock somewhere
else up in the stack now.
I'm cleaning up this whole stack again, dropping the ROC_ABORT back
off hack, because I think actually the solution isn't at this layer at
all, but possibly in the mac80211 layer.
Zac Bowling
On Tue, Jan 27, 2026 at 2:59 AM Felix Fietkau <nbd@nbd.name> wrote:
>
> On 20.01.26 21:10, Zac wrote:
> > From: Zac Bowling <zac@zacbowling.com>
> >
> > Add proper mutex protection for mt7921 driver operations that access
> > hardware state without proper synchronization. This fixes multiple race
> > conditions that can cause system instability.
> >
> > Fixes added:
> >
> > 1. mac.c: mt7921_mac_reset_work()
> > - Wrap ieee80211_iterate_active_interfaces() with mt792x_mutex
> > - The vif_connect_iter callback accesses hw_encap state
> >
> > 2. main.c: mt7921_remain_on_channel()
> > - Remove mt792x_mutex_acquire/release around mt7925_set_channel_state()
> > - The function is already called with mutex held from mac80211
> > - This was causing double-lock deadlock
> >
> > 3. main.c: mt7921_cancel_remain_on_channel()
> > - Remove mt792x_mutex_acquire/release
> > - Function is called from mac80211 with mutex already held
> >
> > 4. pci.c: mt7921_pci_pm_complete()
> > - Remove mt792x_mutex_acquire/release around ieee80211_iterate_active_interfaces
> > - This was causing deadlock as the vif connect iteration tries
> > to acquire the mutex again
> >
> > 5. usb.c: mt7921_usb_pm_complete()
> > - Same fix as pci.c for USB driver path
> Changelog should be below "---" after the commit description, so it
> doesn't get picked up.
>
> > These changes prevent both missing mutex protection and mutex deadlocks
> > in the mt7921 driver.
> >
> > Fixes: 5c14a5f944b9 ("wifi: mt76: mt7921: introduce remain_on_channel support")
> > Signed-off-by: Zac Bowling <zac@zacbowling.com>
>
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> > index 5fae9a6e273c..196fcb1e2e94 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
> > @@ -373,6 +373,11 @@ void mt7921_roc_abort_sync(struct mt792x_dev *dev)
> >
> > timer_delete_sync(&phy->roc_timer);
> > cancel_work_sync(&phy->roc_work);
> > + /* Note: caller must hold mutex if ieee80211_iterate_interfaces is
> > + * needed for ROC cleanup. Some call sites (like mt7921_mac_sta_remove)
> > + * already hold the mutex via mt76_sta_remove(). For suspend paths,
> > + * the mutex should be acquired before calling this function.
> > + */
> > if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
> > ieee80211_iterate_interfaces(mt76_hw(dev),
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > @@ -619,6 +624,7 @@ void mt7921_set_runtime_pm(struct mt792x_dev *dev)
> > bool monitor = !!(hw->conf.flags & IEEE80211_CONF_MONITOR);
> >
> > pm->enable = pm->enable_user && !monitor;
> > + /* Note: caller (debugfs) must hold mutex before calling this function */
> > ieee80211_iterate_active_interfaces(hw,
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > mt7921_pm_interface_iter, dev);
> > @@ -765,6 +771,9 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add)
> > struct mt792x_dev *dev = phy->dev;
> > u32 valid_vif_num = 0;
> >
> > + /* Note: caller (mt7921_mac_sta_add/remove via mt76_sta_add/remove)
> > + * already holds dev->mt76.mutex, so we must not acquire it here.
> > + */
> > ieee80211_iterate_active_interfaces(mt76_hw(dev),
> > IEEE80211_IFACE_ITER_RESUME_ALL,
> > mt7921_calc_vif_num, &valid_vif_num);
>
> It looks like these comments should be replaced with
> lockdep_assert_held, so that these assumptions can be verified
> automatically instead of doing so by hand.
>
>
> > diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> > index ec9686183251..9f76b334b93d 100644
> > --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
> > @@ -426,7 +426,9 @@ static int mt7921_pci_suspend(struct device *device)
> > cancel_delayed_work_sync(&pm->ps_work);
> > cancel_work_sync(&pm->wake_work);
> >
> > + mt792x_mutex_acquire(dev);
> > mt7921_roc_abort_sync(dev);
> > + mt792x_mutex_release(dev);
> The next patch is removing those...
>
> - Felix
^ permalink raw reply
* Re: [PATCH v2] wifi: ath11k: fix memory leaks in beacon template setup
From: Zilin Guan @ 2026-01-29 6:13 UTC (permalink / raw)
To: jeff.johnson
Cc: ath11k, baochen.qiang, jianhao.xu, jjohnson, linux-kernel,
linux-wireless, zilin
In-Reply-To: <6cc72c12-6f50-4e91-9b7a-bde8633009c4@oss.qualcomm.com>
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?
Best regards,
Zilin
^ permalink raw reply
* Re: [PATCH] wireless-regdb: add IN rules for lower 6 GHz (5925-6425 MHz) LPI and VLP
From: Chen-Yu Tsai @ 2026-01-29 6:13 UTC (permalink / raw)
To: gaurav.kansal; +Cc: linux-wireless, wireless-regdb, Ping-Ke Shih
In-Reply-To: <CAHrRpuk7Xqab+BC57m_5KWJVr8rc+_LHWwKr-pWmvR3y9W2ghA@mail.gmail.com>
On Thu, Jan 29, 2026 at 10:10 AM Ping-Ke Shih <pkshih@gmail.com> wrote:
>
> <gaurav.kansal@nic.in> wrote:
> >
> > From: Gaurav Kansal <gaurav.kansal@nic.in>
> >
> > DoT / Gazette notification (20 Jan 2026) delicensed lower 6 GHz (5925-6425 MHz).
> > Add rules for:
> > - Low Power Indoor (LPI) 5925-6425 MHz, up to 1000 mW (30 dBm) EIRP, NO-OUTDOOR
> > - Very Low Power (VLP) 5925-6425 MHz, up to 25 mW (14 dBm) EIRP
> >
> > References:
> > - DoT / Gazette notification (Jan 20, 2026)
> > https://blog.gauravkansal.in/2026/01/Gazette%20on%20the%20Use%20of%20Low%20Power%20and%20Very%20Low%20Power%20Wireless%20Access%20System%20in%20Lower%206%20GHz%20band%20-%202026.pdf
>
> Is it possible to tag original official link?
>
> > https://blog.gauravkansal.in/2026/01/Opening-India-Lower-6-GHz-Band-for-Wireless-Use:-A-Strategic-Regulatory-Landmark.html
> > https://en.wikipedia.org/w/index.php?title=List_of_WLAN_channels#India_2
> > - Linux wireless-regdb db.txt format docs
> > Signed-off-by: Gaurav Kansal gaurav.kansal@nic.in
Somehow your commit message is indented.
Please have an empty line before the Signed-off-by.
> > ---
> > db.txt | 8 ++++++++
> > 1 file changed, 8 insertions(+)
> >
> > diff --git a/db.txt b/db.txt
> > index dc72989..0f90f6e 100644
> > --- a/db.txt
> > +++ b/db.txt
> > @@ -974,12 +974,20 @@ country IL: DFS-ETSI
> > # Source:
> > # https://dot.gov.in/spectrummanagement/delicensing-24-24835-ghz-band-gsr-45-e-5150-5350-ghz-gsr-46-e-and-5725-5875-ghz
> > # https://dot.gov.in/spectrummanagement/license-exemption-5-ghz-gsr-1048e-dated-22102018
> > +# Info about 6 GHz added by Gaurav Kansal (gaurav.kansal@nic.in)
> > +# Lower 6 GHz delicensed for India: 5925-6425 MHz (on 21.Jan.2026)
> > +# LPI: Low Power Indoor (indoor only) — 1000 mW = 30 dBm EIRP
> > +# VLP: Very Low Power (outdoor allowed at very low EIRP) — 25 mW = 14 dBm EIRP
> > +# https://blog.gauravkansal.in/2026/01/Opening-India-Lower-6-GHz-Band-for-Wireless-Use:-A-Strategic-Regulatory-Landmark.html
> > +# https://blog.gauravkansal.in/2026/01/Gazette%20on%20the%20Use%20of%20Low%20Power%20and%20Very%20Low%20Power%20Wireless%20Access%20System%20in%20Lower%206%20GHz%20band%20-%202026.pdf
> > country IN:
> > (2402 - 2482 @ 40), (30)
> > (5150 - 5250 @ 80), (30)
> > (5250 - 5350 @ 80), (24), DFS
> > (5470 - 5725 @ 160), (24), DFS
> > (5725 - 5875 @ 80), (30)
> > + (5925 - 6425 @ 320), (1000 mW), NO-OUTDOOR, AUTO-BW
>
> I'd prefer in unit of dBm as [1].
Please stick with what the official document uses.
> I think no need AUTO-BW that is to merge adjacent rules to have large bandwidth.
>
> > + (5925 - 6425 @ 320), (25 mW), AUTO-BW
>
> Use tab as indent instead.
>
> I'm not aware we can possibly have two entries with the same frequency range,
> and I only added one entry for indoor (or no indoor/outdoor restriction) before.
> Checking freq_reg_info_regd(), I think only first matched entry is adopted, but
> no error to build reg-db by 'make'.
That's my impression as well. Linux cannot handle two rules for the same
band. Only one is ever used.
ChenYu
> >
> > # Source:
> > # https://asnad.cra.ir/fa/Public/Documents/Details/73af8590-f065-eb11-968f-0050569b0899
> > --
> > 2.47.3
> >
> >
^ permalink raw reply
* Re: [PATCH v2 2/2] wifi: ath12k: Fix firmware stats leak when pdev list is empty
From: Baochen Qiang @ 2026-01-29 2:27 UTC (permalink / raw)
To: Saikiran B; +Cc: ath12k, linux-wireless, kvalo
In-Reply-To: <CAAFDt1s8HPQWJpSiDDk8PmUAKqkQHELcJj_F0faZ1C9RfiYu6g@mail.gmail.com>
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;
>>>> }
>>>>
>>>
^ permalink raw reply
* [PATCH ath-next 2/2] wifi: ath12k: fix station lookup failure when disconnecting from AP
From: Baochen Qiang @ 2026-01-29 2:24 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
In-Reply-To: <20260129-ath12k-fw-stats-fixes-v1-0-55d66064f4d5@oss.qualcomm.com>
In ath12k_wmi_tlv_fw_stats_data_parse() and
ath12k_wmi_tlv_rssi_chain_parse(), the driver uses
ieee80211_find_sta_by_ifaddr() to look up the station associated with the
incoming firmware statistics. This works under normal conditions but fails
during AP disconnection, resulting in log messages like:
wlan0: deauthenticating from xxxxxx by local choice (Reason: 3=DEAUTH_LEAVING)
wlan0: moving STA xxxxxx to state 3
wlan0: moving STA xxxxxx to state 2
wlan0: moving STA xxxxxx to state 1
ath12k_pci 0000:02:00.0: not found station bssid xxxxxx for vdev stat
ath12k_pci 0000:02:00.0: not found station of bssid xxxxxx for rssi chain
ath12k_pci 0000:02:00.0: failed to pull fw stats: -71
ath12k_pci 0000:02:00.0: time out while waiting for get fw stats
wlan0: Removed STA xxxxxx
wlan0: Destroyed STA xxxxxx
The failure happens because the station has already been removed from
ieee80211_local::sta_hash by the time firmware statistics are requested
through drv_sta_statistics().
Switch the lookup to ath12k_link_sta_find_by_addr(), which searches the
driver's link station hash table that still has the station recorded
at that time. This also implicitly fixes another issue: the current code
always uses deflink regardless of which link the statistics belong to,
which is incorrect in MLO scenarios. The new helper returns the correct
link station.
Additionally, raise the log level on lookup failures. With the updated
helper, such failures should no longer occur under normal conditions.
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Fixes: 79e7b04b5388 ("wifi: ath12k: report station mode signal strength")
Fixes: 6af5bc381b36 ("wifi: ath12k: report station mode per-chain signal strength")
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/wmi.c | 36 +++++++++++++----------------------
1 file changed, 13 insertions(+), 23 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index cce3d699112d..92e4f820005e 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -8099,8 +8099,6 @@ static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,
struct ath12k_fw_stats *stats = parse->stats;
struct ath12k *ar;
struct ath12k_link_vif *arvif;
- struct ieee80211_sta *sta;
- struct ath12k_sta *ahsta;
struct ath12k_link_sta *arsta;
int i, ret = 0;
const void *data = ptr;
@@ -8136,21 +8134,19 @@ static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,
arvif = ath12k_mac_get_arvif(ar, le32_to_cpu(src->vdev_id));
if (arvif) {
- sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar),
- arvif->bssid,
- NULL);
- if (sta) {
- ahsta = ath12k_sta_to_ahsta(sta);
- arsta = &ahsta->deflink;
+ spin_lock_bh(&ab->base_lock);
+ arsta = ath12k_link_sta_find_by_addr(ab, arvif->bssid);
+ if (arsta) {
arsta->rssi_beacon = le32_to_cpu(src->beacon_snr);
ath12k_dbg(ab, ATH12K_DBG_WMI,
"wmi stats vdev id %d snr %d\n",
src->vdev_id, src->beacon_snr);
} else {
- ath12k_dbg(ab, ATH12K_DBG_WMI,
- "not found station bssid %pM for vdev stat\n",
- arvif->bssid);
+ ath12k_warn(ab,
+ "not found link sta with bssid %pM for vdev stat\n",
+ arvif->bssid);
}
+ spin_unlock_bh(&ab->base_lock);
}
data += sizeof(*src);
@@ -8221,8 +8217,6 @@ static int ath12k_wmi_tlv_rssi_chain_parse(struct ath12k_base *ab,
struct ath12k_fw_stats *stats = parse->stats;
struct ath12k_link_vif *arvif;
struct ath12k_link_sta *arsta;
- struct ieee80211_sta *sta;
- struct ath12k_sta *ahsta;
struct ath12k *ar;
int vdev_id;
int j;
@@ -8258,19 +8252,15 @@ static int ath12k_wmi_tlv_rssi_chain_parse(struct ath12k_base *ab,
"stats bssid %pM vif %p\n",
arvif->bssid, arvif->ahvif->vif);
- sta = ieee80211_find_sta_by_ifaddr(ath12k_ar_to_hw(ar),
- arvif->bssid,
- NULL);
- if (!sta) {
- ath12k_dbg(ab, ATH12K_DBG_WMI,
- "not found station of bssid %pM for rssi chain\n",
- arvif->bssid);
+ guard(spinlock_bh)(&ab->base_lock);
+ arsta = ath12k_link_sta_find_by_addr(ab, arvif->bssid);
+ if (!arsta) {
+ ath12k_warn(ab,
+ "not found link sta with bssid %pM for rssi chain\n",
+ arvif->bssid);
return -EPROTO;
}
- ahsta = ath12k_sta_to_ahsta(sta);
- arsta = &ahsta->deflink;
-
BUILD_BUG_ON(ARRAY_SIZE(arsta->chain_signal) >
ARRAY_SIZE(stats_rssi->rssi_avg_beacon));
--
2.25.1
^ permalink raw reply related
* [PATCH ath-next 1/2] wifi: ath12k: use correct pdev id when requesting firmware stats
From: Baochen Qiang @ 2026-01-29 2:24 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
In-Reply-To: <20260129-ath12k-fw-stats-fixes-v1-0-55d66064f4d5@oss.qualcomm.com>
To get firmware statistics, currently ar->pdev->pdev_id is passed as an
argument to ath12k_mac_get_fw_stats() in ath12k_mac_op_sta_statistics().
For single pdev device like WCN7850, its value is 0 which represents the
SoC pdev id. As a result, WCN7850 firmware sends the same reply to host
twice, which further results in memory leak:
unreferenced object 0xffff88812e286000 (size 192):
comm "softirq", pid 0, jiffies 4294981997
hex dump (first 32 bytes):
10 a5 40 11 81 88 ff ff 10 a5 40 11 81 88 ff ff ..@.......@.....
00 00 00 00 00 00 00 00 80 ff ff ff 33 05 00 00 ............3...
backtrace (crc cecc8c82):
__kmalloc_cache_noprof
ath12k_wmi_tlv_fw_stats_parse
ath12k_wmi_tlv_iter
ath12k_wmi_op_rx
ath12k_htc_rx_completion_handler
ath12k_ce_per_engine_service
ath12k_pci_ce_workqueue
process_one_work
bh_worker
tasklet_action
handle_softirqs
Detailed explanation is:
1. ath12k_mac_get_fw_stats() called in ath12k_mac_op_sta_statistics() to
get vdev statistics, making the caller thread wait.
2. firmware sends the first reply, ath12k_wmi_tlv_fw_stats_data_parse()
allocates buffers to cache necessary information. Following that, in
ath12k_wmi_fw_stats_process() if events of all started vdev haved been
received, is_end flag is set hence the waiting thread gets waken up by
the ar->fw_stats_done/->fw_stats_complete signals.
3. ath12k_mac_get_fw_stats() wakes up and returns successfully.
ath12k_mac_op_sta_statistics() saves required parameters and calls
ath12k_fw_stats_reset() to free buffers allocated earlier.
4. firmware sends the second reply. As usual, buffers are allocated and
attached to the ar->fw_stats.vdevs list. Note this time there is no
thread waiting, therefore no chance to free those buffers.
5. ath12k module gets unloaded. If there has been no more firmware
statistics request made since step 4, or if the request fails (see
the example in the following patch), there is no chance to call
ath12k_fw_stats_reset(). Consequently those buffers leak.
Actually for single pdev device, using SoC pdev id in
ath12k_mac_op_sta_statistics() is wrong, because the purpose is to get
statistics of a specific station, which is mapped to a specific pdev. That
said, the id of actual individual pdev should be fetched and used instead.
The helper ath12k_mac_get_target_pdev_id() serves for this purpose, hence
use it to fix this issue. Note it also works for other devices as well due
to the single_pdev_only check inside.
The same applies to ath12k_mac_op_get_txpower() and
ath12k_mac_op_link_sta_statistics() as well.
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Fixes: 79e7b04b5388 ("wifi: ath12k: report station mode signal strength")
Fixes: e92c658b056b ("wifi: ath12k: add get_txpower mac ops")
Fixes: ebebe66ec208 ("wifi: ath12k: fill link station statistics for MLO")
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
drivers/net/wireless/ath/ath12k/mac.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index cdb72439dcf4..46253347e357 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -5264,7 +5264,7 @@ int ath12k_mac_op_get_txpower(struct ieee80211_hw *hw,
ar->last_tx_power_update))
goto send_tx_power;
- params.pdev_id = ar->pdev->pdev_id;
+ params.pdev_id = ath12k_mac_get_target_pdev_id(ar);
params.vdev_id = arvif->vdev_id;
params.stats_id = WMI_REQUEST_PDEV_STAT;
ret = ath12k_mac_get_fw_stats(ar, ¶ms);
@@ -13286,7 +13286,7 @@ void ath12k_mac_op_sta_statistics(struct ieee80211_hw *hw,
/* TODO: Use real NF instead of default one. */
signal = rate_info.rssi_comb;
- params.pdev_id = ar->pdev->pdev_id;
+ params.pdev_id = ath12k_mac_get_target_pdev_id(ar);
params.vdev_id = 0;
params.stats_id = WMI_REQUEST_VDEV_STAT;
@@ -13414,7 +13414,7 @@ void ath12k_mac_op_link_sta_statistics(struct ieee80211_hw *hw,
spin_unlock_bh(&ar->ab->dp->dp_lock);
if (!signal && ahsta->ahvif->vdev_type == WMI_VDEV_TYPE_STA) {
- params.pdev_id = ar->pdev->pdev_id;
+ params.pdev_id = ath12k_mac_get_target_pdev_id(ar);
params.vdev_id = 0;
params.stats_id = WMI_REQUEST_VDEV_STAT;
--
2.25.1
^ permalink raw reply related
* [PATCH ath-next 0/2] wifi: ath12k: fixes to station statistics request
From: Baochen Qiang @ 2026-01-29 2:24 UTC (permalink / raw)
To: Jeff Johnson; +Cc: linux-wireless, ath12k, Baochen Qiang
ath12k hits timeout and memory leak when requesting firmware statistics,
fix them.
Signed-off-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
---
Baochen Qiang (2):
wifi: ath12k: use correct pdev id when requesting firmware stats
wifi: ath12k: fix station lookup failure when disconnecting from AP
drivers/net/wireless/ath/ath12k/mac.c | 6 +++---
drivers/net/wireless/ath/ath12k/wmi.c | 36 +++++++++++++----------------------
2 files changed, 16 insertions(+), 26 deletions(-)
---
base-commit: 8c6bd5e9db28152563c984d8c48322610cac8609
change-id: 20251121-ath12k-fw-stats-fixes-6e008a4054fd
Best regards,
--
Baochen Qiang <baochen.qiang@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH] wireless-regdb: add IN rules for lower 6 GHz (5925-6425 MHz) LPI and VLP
From: Ping-Ke Shih @ 2026-01-29 2:10 UTC (permalink / raw)
To: gaurav.kansal; +Cc: wens, linux-wireless, wireless-regdb
In-Reply-To: <20260128102525.872517-1-gaurav.kansal@nic.in>
<gaurav.kansal@nic.in> wrote:
>
> From: Gaurav Kansal <gaurav.kansal@nic.in>
>
> DoT / Gazette notification (20 Jan 2026) delicensed lower 6 GHz (5925-6425 MHz).
> Add rules for:
> - Low Power Indoor (LPI) 5925-6425 MHz, up to 1000 mW (30 dBm) EIRP, NO-OUTDOOR
> - Very Low Power (VLP) 5925-6425 MHz, up to 25 mW (14 dBm) EIRP
>
> References:
> - DoT / Gazette notification (Jan 20, 2026)
> https://blog.gauravkansal.in/2026/01/Gazette%20on%20the%20Use%20of%20Low%20Power%20and%20Very%20Low%20Power%20Wireless%20Access%20System%20in%20Lower%206%20GHz%20band%20-%202026.pdf
Is it possible to tag original official link?
> https://blog.gauravkansal.in/2026/01/Opening-India-Lower-6-GHz-Band-for-Wireless-Use:-A-Strategic-Regulatory-Landmark.html
> https://en.wikipedia.org/w/index.php?title=List_of_WLAN_channels#India_2
> - Linux wireless-regdb db.txt format docs
> Signed-off-by: Gaurav Kansal gaurav.kansal@nic.in
> ---
> db.txt | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/db.txt b/db.txt
> index dc72989..0f90f6e 100644
> --- a/db.txt
> +++ b/db.txt
> @@ -974,12 +974,20 @@ country IL: DFS-ETSI
> # Source:
> # https://dot.gov.in/spectrummanagement/delicensing-24-24835-ghz-band-gsr-45-e-5150-5350-ghz-gsr-46-e-and-5725-5875-ghz
> # https://dot.gov.in/spectrummanagement/license-exemption-5-ghz-gsr-1048e-dated-22102018
> +# Info about 6 GHz added by Gaurav Kansal (gaurav.kansal@nic.in)
> +# Lower 6 GHz delicensed for India: 5925-6425 MHz (on 21.Jan.2026)
> +# LPI: Low Power Indoor (indoor only) — 1000 mW = 30 dBm EIRP
> +# VLP: Very Low Power (outdoor allowed at very low EIRP) — 25 mW = 14 dBm EIRP
> +# https://blog.gauravkansal.in/2026/01/Opening-India-Lower-6-GHz-Band-for-Wireless-Use:-A-Strategic-Regulatory-Landmark.html
> +# https://blog.gauravkansal.in/2026/01/Gazette%20on%20the%20Use%20of%20Low%20Power%20and%20Very%20Low%20Power%20Wireless%20Access%20System%20in%20Lower%206%20GHz%20band%20-%202026.pdf
> country IN:
> (2402 - 2482 @ 40), (30)
> (5150 - 5250 @ 80), (30)
> (5250 - 5350 @ 80), (24), DFS
> (5470 - 5725 @ 160), (24), DFS
> (5725 - 5875 @ 80), (30)
> + (5925 - 6425 @ 320), (1000 mW), NO-OUTDOOR, AUTO-BW
I'd prefer in unit of dBm as [1].
I think no need AUTO-BW that is to merge adjacent rules to have large bandwidth.
> + (5925 - 6425 @ 320), (25 mW), AUTO-BW
Use tab as indent instead.
I'm not aware we can possibly have two entries with the same frequency range,
and I only added one entry for indoor (or no indoor/outdoor restriction) before.
Checking freq_reg_info_regd(), I think only first matched entry is adopted, but
no error to build reg-db by 'make'.
>
> # Source:
> # https://asnad.cra.ir/fa/Public/Documents/Details/73af8590-f065-eb11-968f-0050569b0899
> --
> 2.47.3
>
>
^ permalink raw reply
* Re: [PATCH v4 ath-current 1/2] wifi: ath11k: add usecase firmware handling based on device compatible
From: Baochen Qiang @ 2026-01-29 2:10 UTC (permalink / raw)
To: Miaoqing Pan, jjohnson, johannes, robh, krzk+dt, conor+dt
Cc: ath11k, linux-wireless, linux-kernel, devicetree, jonas.gorski,
krzk
In-Reply-To: <20260121095055.3683957-2-miaoqing.pan@oss.qualcomm.com>
On 1/21/2026 5:50 PM, Miaoqing Pan wrote:
> For M.2 WLAN chips, there is no suitable DTS node to specify the
> firmware-name property. In addition, assigning firmware for the
> M.2 PCIe interface causes chips that do not use usecase specific
> firmware to fail. Therefore, abandoning the approach of specifying
> firmware in DTS. As an alternative, propose a static lookup table
> mapping device compatible to firmware names. Currently, only WCN6855
> HW2.1 requires this.
>
> However, support for the firmware-name property is retained to keep
> the ABI backwards compatible.
>
> For details on usecase specific firmware, see:
> https://lore.kernel.org/all/20250522013444.1301330-3-miaoqing.pan@oss.qualcomm.com/.
>
> Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
>
> Fixes: edbbc647c4f3 ("wifi: ath11k: support usercase-specific firmware overrides")
> Signed-off-by: Miaoqing Pan <miaoqing.pan@oss.qualcomm.com>
> ---
> drivers/net/wireless/ath/ath11k/core.c | 27 ++++++++++++++++++++++++++
> drivers/net/wireless/ath/ath11k/core.h | 4 ++++
> 2 files changed, 31 insertions(+)
>
> diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
> index de84906d1b27..3f6f4db5b7ee 100644
> --- a/drivers/net/wireless/ath/ath11k/core.c
> +++ b/drivers/net/wireless/ath/ath11k/core.c
> @@ -1044,6 +1044,33 @@ static const struct dmi_system_id ath11k_pm_quirk_table[] = {
> {}
> };
>
> +static const struct __ath11k_core_usecase_firmware_table {
> + u32 hw_rev;
> + const char *compatible;
> + const char *firmware_name;
> +} ath11k_core_usecase_firmware_table[] = {
> + { ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"},
> + { ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"},
> + { ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"},
> + { /* Sentinel */ }
> +};
> +
> +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab)
> +{
> + const struct __ath11k_core_usecase_firmware_table *entry = NULL;
> +
> + entry = ath11k_core_usecase_firmware_table;
> + while (entry->compatible) {
> + if (ab->hw_rev == entry->hw_rev &&
> + of_machine_is_compatible(entry->compatible))
> + return entry->firmware_name;
> + entry++;
> + }
> +
> + return NULL;
> +}
> +EXPORT_SYMBOL(ath11k_core_get_usecase_firmware);
> +
> void ath11k_fw_stats_pdevs_free(struct list_head *head)
> {
> struct ath11k_fw_stats_pdev *i, *tmp;
> diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
> index 3f41e6569a78..a0d725923ef2 100644
> --- a/drivers/net/wireless/ath/ath11k/core.h
> +++ b/drivers/net/wireless/ath/ath11k/core.h
> @@ -1292,6 +1292,7 @@ bool ath11k_core_coldboot_cal_support(struct ath11k_base *ab);
>
> const struct firmware *ath11k_core_firmware_request(struct ath11k_base *ab,
> const char *filename);
> +const char *ath11k_core_get_usecase_firmware(struct ath11k_base *ab);
>
> static inline const char *ath11k_scan_state_str(enum ath11k_scan_state state)
> {
> @@ -1346,6 +1347,9 @@ static inline void ath11k_core_create_firmware_path(struct ath11k_base *ab,
>
> of_property_read_string(ab->dev->of_node, "firmware-name", &fw_name);
>
> + if (!fw_name)
> + fw_name = ath11k_core_get_usecase_firmware(ab);
> +
> if (fw_name && strncmp(filename, "board", 5))
> snprintf(buf, buf_len, "%s/%s/%s/%s", ATH11K_FW_DIR,
> ab->hw_params.fw.dir, fw_name, filename);
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
^ permalink raw reply
* [syzbot] Monthly wireless report (Jan 2026)
From: syzbot @ 2026-01-28 22:39 UTC (permalink / raw)
To: linux-kernel, linux-wireless, netdev, syzkaller-bugs
Hello wireless maintainers/developers,
This is a 31-day syzbot report for the wireless subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/wireless
During the period, 2 new issues were detected and 0 were fixed.
In total, 56 issues are still open and 171 have already been fixed.
Some of the still happening issues:
Ref Crashes Repro Title
<1> 25396 Yes WARNING in rate_control_rate_init (3)
https://syzkaller.appspot.com/bug?extid=9bdc0c5998ab45b05030
<2> 10510 Yes WARNING in __rate_control_send_low (3)
https://syzkaller.appspot.com/bug?extid=34463a129786910405dd
<3> 7244 No WARNING in kcov_remote_start (6)
https://syzkaller.appspot.com/bug?extid=3f51ad7ac3ae57a6fdcc
<4> 6921 Yes WARNING in __cfg80211_ibss_joined (2)
https://syzkaller.appspot.com/bug?extid=7f064ba1704c2466e36d
<5> 2111 Yes WARNING in __alloc_workqueue
https://syzkaller.appspot.com/bug?extid=392a2c3f461094707435
<6> 1226 Yes WARNING in ieee80211_start_next_roc
https://syzkaller.appspot.com/bug?extid=c3a167b5615df4ccd7fb
<7> 1026 Yes INFO: task hung in reg_process_self_managed_hints
https://syzkaller.appspot.com/bug?extid=1f16507d9ec05f64210a
<8> 842 Yes INFO: task hung in reg_check_chans_work (7)
https://syzkaller.appspot.com/bug?extid=a2de4763f84f61499210
<9> 750 Yes INFO: rcu detected stall in ieee80211_handle_queued_frames
https://syzkaller.appspot.com/bug?extid=1c991592da3ef18957c0
<10> 648 No WARNING in ieee80211_tx_h_rate_ctrl
https://syzkaller.appspot.com/bug?extid=0d516b33238bd97ee864
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders
To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem
You may send multiple commands in a single email message.
^ permalink raw reply
* [PATCH AUTOSEL 6.18-5.10] wifi: cfg80211: Fix bitrate calculation overflow for HE rates
From: Sasha Levin @ 2026-01-28 22:33 UTC (permalink / raw)
To: patches, stable
Cc: Veerendranath Jakkam, Johannes Berg, Sasha Levin, johannes,
linux-wireless
In-Reply-To: <20260128223332.2806589-1-sashal@kernel.org>
From: Veerendranath Jakkam <veerendranath.jakkam@oss.qualcomm.com>
[ Upstream commit a3034bf0746d88a00cceda9541534a5721445a24 ]
An integer overflow occurs in cfg80211_calculate_bitrate_he() when
calculating bitrates for high throughput HE configurations.
For example, with 160 MHz bandwidth, HE-MCS 13, HE-NSS 4, and HE-GI 0,
the multiplication (result * rate->nss) overflows the 32-bit 'result'
variable before division by 8, leading to significantly underestimated
bitrate values.
The overflow occurs because the NSS multiplication operates on a 32-bit
integer that cannot accommodate intermediate values exceeding
4,294,967,295. When overflow happens, the value wraps around, producing
incorrect bitrates for high MCS and NSS combinations.
Fix this by utilizing the 64-bit 'tmp' variable for the NSS
multiplication and subsequent divisions via do_div(). This approach
preserves full precision throughout the entire calculation, with the
final value assigned to 'result' only after completing all operations.
Signed-off-by: Veerendranath Jakkam <veerendranath.jakkam@oss.qualcomm.com>
Link: https://patch.msgid.link/20260109-he_bitrate_overflow-v1-1-95575e466b6e@oss.qualcomm.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
I can see the buggy code is still present in this version (the fix
hasn't been applied yet to this version of util.c). This is the code
being patched. Let me double check the diff shows exactly what I expect:
Looking at the diff again:
```c
- result = tmp;
/* and take NSS, DCM into account */
- result = (result * rate->nss) / 8;
+ tmp *= rate->nss;
+ do_div(tmp, 8);
if (rate->he_dcm)
- result /= 2;
+ do_div(tmp, 2);
+
+ result = tmp;
```
The fix:
1. Removes `result = tmp` after the MCS division (keeps value in 64-bit
`tmp`)
2. Multiplies NSS in 64-bit: `tmp *= rate->nss` instead of 32-bit
`result * rate->nss`
3. Uses `do_div(tmp, 8)` instead of 32-bit division
4. Uses `do_div(tmp, 2)` for DCM instead of 32-bit division
5. Only assigns to 32-bit `result` at the end when the value is
guaranteed to fit
This matches exactly the same fix pattern that was applied to
`cfg80211_calculate_bitrate_eht()` in commit 18429c51c7ff6.
## Summary Analysis
### 1. COMMIT MESSAGE ANALYSIS
- **Subject**: Clearly indicates a bug fix for bitrate calculation
overflow in HE rates
- **Body**: Provides detailed technical explanation of the overflow
scenario (160 MHz, MCS 13, NSS 4, GI 0)
- **Root cause**: 32-bit integer overflow when multiplying `result *
rate->nss` before division by 8
- **Fix approach**: Use 64-bit `tmp` variable throughout the
calculation, assign to 32-bit `result` only at the end
- **Tags**: Has Signed-off-by from both Qualcomm developer and Johannes
Berg (wireless maintainer)
### 2. CODE CHANGE ANALYSIS
- **Bug mechanism**: When `result` (a u32) is large (e.g., 0x44b85dd4 =
1,152,933,332), multiplying by NSS 4 produces 0x112e17750
(4,611,733,328) which overflows u32 max (0xFFFFFFFF = 4,294,967,295)
- **Result of bug**: The overflow wraps to 0x12e17750, producing a
bitrate of ~3,959 (400 Mbps) instead of correct ~57,646 (5.76 Gbps)
- **Fix correctness**: The fix keeps all arithmetic in 64-bit until the
final assignment, preventing overflow
- **DCM handling**: Also moved to 64-bit for consistency and safety
### 3. CLASSIFICATION
- **Type**: Bug fix (integer overflow causing incorrect bitrate
reporting)
- **Security**: Not a security issue, but affects user-visible data
correctness
- **Exception categories**: Not applicable - this is a straightforward
bug fix
### 4. SCOPE AND RISK ASSESSMENT
- **Lines changed**: ~10 lines, very small and localized
- **Files touched**: 1 file (net/wireless/util.c)
- **Subsystem**: WiFi cfg80211, mature and stable
- **Risk**: Very LOW - the exact same fix pattern was already applied to
the EHT version of this function and has been in stable since 6.1
- **Could break something**: Extremely unlikely - only changes
intermediate arithmetic precision, final result is more accurate
### 5. USER IMPACT
- **Who is affected**: Users with WiFi 6 (802.11ax) devices using high
bandwidth (160 MHz), high MCS (12/13), and multiple spatial streams
(NSS 3-8)
- **Visibility**: Users see incorrect bitrate reporting in tools like
`iw`, network managers, etc.
- **Severity**: Low-medium - cosmetic but confusing to users (reported
speed is much lower than actual)
- **Real-world scenario**: Modern high-end WiFi 6 devices can absolutely
hit these configurations
### 6. STABILITY INDICATORS
- **Testing**: Qualcomm developer tested on their hardware
- **Review**: Accepted by Johannes Berg (long-time wireless maintainer)
- **Precedent**: Same fix was applied to EHT function years ago (commit
18429c51c7ff6) and has proven stable
### 7. DEPENDENCY CHECK
- **Dependencies**: None - self-contained fix
- **Affected versions**: The HE MCS 12/13 support was added in v5.11, so
stable trees 5.11+ are affected
- **Backport complexity**: Trivial - the affected code exists unchanged
in all relevant stable trees
### Risk vs Benefit Trade-off
- **Benefit**: Corrects incorrect bitrate reporting for high-throughput
WiFi 6 configurations
- **Risk**: Minimal - follows proven fix pattern already in production
since kernel 6.1
- **Verdict**: HIGH benefit, VERY LOW risk
**YES**
net/wireless/util.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 4eb028ad16836..81d6d27d273cc 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -1561,12 +1561,14 @@ static u32 cfg80211_calculate_bitrate_he(struct rate_info *rate)
tmp = result;
tmp *= SCALE;
do_div(tmp, mcs_divisors[rate->mcs]);
- result = tmp;
/* and take NSS, DCM into account */
- result = (result * rate->nss) / 8;
+ tmp *= rate->nss;
+ do_div(tmp, 8);
if (rate->he_dcm)
- result /= 2;
+ do_div(tmp, 2);
+
+ result = tmp;
return result / 10000;
}
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.18-6.1] wifi: mac80211: correctly check if CSA is active
From: Sasha Levin @ 2026-01-28 22:33 UTC (permalink / raw)
To: patches, stable
Cc: Miri Korenblit, Johannes Berg, Sasha Levin, johannes,
linux-wireless
In-Reply-To: <20260128223332.2806589-1-sashal@kernel.org>
From: Miri Korenblit <miriam.rachel.korenblit@intel.com>
[ Upstream commit db1d0b6ab11f612ea8a327663a578c8946efeee9 ]
We are not adding an interface if an existing one is doing CSA.
But the check won't work for MLO station interfaces, since for those,
vif->bss_conf is zeroed out.
Fix this by checking if any link of the vif has an active CSA.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260111191912.7ceff62fc561.Ia38d27f42684d1cfd82d930d232bd5dea6ab9282@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Now I have a complete picture. Let me analyze this commit
comprehensively.
## Analysis
### 1. COMMIT MESSAGE ANALYSIS
**Subject**: "wifi: mac80211: correctly check if CSA is active"
The commit clearly describes a bug fix - the existing CSA (Channel
Switch Announcement) check doesn't work properly for MLO (Multi-Link
Operation) station interfaces. The message explains:
- The problem: "the check won't work for MLO station interfaces, since
for those, vif->bss_conf is zeroed out"
- The fix: "checking if any link of the vif has an active CSA"
**Tags present**:
- Reviewed-by: Johannes Berg (mac80211 maintainer) - strong credibility
signal
- Signed-off-by: Miri Korenblit (Intel wireless developer)
- Link to mailing list
No explicit Fixes: tag, but that's expected for commits we're reviewing.
### 2. CODE CHANGE ANALYSIS
**The Bug:**
The original code at line 379:
```c
if (nsdata->vif.bss_conf.csa_active)
return -EBUSY;
```
This check is used in `ieee80211_check_concurrent_iface()` to prevent
adding a new interface while an existing interface is performing a
channel switch operation.
**Why it's broken for MLO:**
For MLO (Multi-Link Operation) station interfaces introduced in WiFi 7
(802.11be):
- `vif->bss_conf` is zeroed out by design
- Each link has its own BSS configuration stored in `link_conf[link_id]`
- The CSA state (`csa_active`) is per-link, not per-VIF
- Checking `vif->bss_conf.csa_active` will always return `false` for MLO
interfaces, even if one of the links is actively switching channels
**The Fix:**
```c
struct ieee80211_link_data *link;
...
for_each_link_data(nsdata, link) {
if (link->conf->csa_active)
return -EBUSY;
}
```
This properly iterates through all valid links and checks if ANY link
has an active CSA. The `for_each_link_data` macro:
- For non-MLO interfaces: iterates once with the single (deflink) link
- For MLO interfaces: iterates through all links with bits set in
`valid_links`
**Technical mechanism**: The fix is correct because it unifies the check
- it works for both MLO and non-MLO interfaces while maintaining the
same semantics.
### 3. CLASSIFICATION
This is clearly a **bug fix**, not a new feature:
- The concurrent interface protection during CSA was always intended to
work
- It just doesn't work for the newer MLO interface type
- This is a correctness fix for existing functionality
### 4. SCOPE AND RISK ASSESSMENT
**Lines changed**: Small - adds ~6 lines, removes 2 lines (net +4)
**Files touched**: 1 file (net/mac80211/iface.c)
**Complexity**: Low - simple loop iteration replacing a single field
access
**Subsystem**: mac80211 WiFi subsystem - widely used but WiFi-specific
**Risk level**: LOW
- The change is straightforward and the logic is sound
- The `for_each_link_data` macro is designed for exactly this use case
- Reviewed by Johannes Berg, the mac80211 maintainer
- Failure mode: If the fix doesn't work, we're no worse than before
(still have the bug)
### 5. USER IMPACT
**Who is affected?**: Users with MLO-capable WiFi hardware (WiFi 7 /
802.11be devices)
**Severity**: Medium - Without this fix:
- A new interface could be incorrectly allowed to be added during an
ongoing channel switch on an MLO interface
- This could cause interference, connectivity issues, or undefined
behavior
- Channel switches are relatively rare events, so impact is somewhat
limited
### 6. STABILITY INDICATORS
- **Reviewed-by**: Johannes Berg (mac80211 maintainer) - strong positive
signal
- Author: Miri Korenblit from Intel - active wireless developer
### 7. DEPENDENCY CHECK
**Critical dependency**: This fix uses the `for_each_link_data` macro
which was introduced in commit f61c7b3d442be ("wifi: mac80211: Add link
iteration macro for link data") in kernel v6.16.
**Backport applicability**:
- **v6.16+**: Can be applied directly - has the macro
- **v6.1-v6.15**: Would require either:
1. Backporting the `for_each_link_data` macro first, OR
2. Writing an alternative fix using open-coded link iteration
MLO support was introduced in v6.1, so the bug exists in all kernels
v6.1 through current that have MLO station interface support.
### Summary
| Criteria | Assessment |
|----------|------------|
| Obviously correct | YES - simple iteration fix |
| Fixes real bug | YES - incorrect CSA check for MLO |
| Important issue | MEDIUM - affects interface addition during channel
switch |
| Small and contained | YES - 4 net lines changed |
| No new features | YES - fixes existing feature |
| Applies cleanly | DEPENDS - needs v6.16+ for macro dependency |
### VERDICT
This is a legitimate bug fix for MLO WiFi support:
- It fixes a real, demonstrable bug where CSA detection fails for MLO
interfaces
- The fix is small, surgical, and well-reviewed
- It doesn't introduce new features or change APIs
- It improves correctness of existing functionality
**However**, there are important caveats:
1. The fix has a dependency on the `for_each_link_data` macro
(introduced in v6.16)
2. For stable trees before v6.16, a backport would need the macro too or
use an alternative approach
3. MLO is relatively new technology (WiFi 7), so the affected user base
is smaller
Given that this fix:
- Is obviously correct and addresses a real bug
- Was reviewed by the subsystem maintainer
- Has low risk of regression
- Affects real user scenarios (channel switching during interface
operations)
The fix is appropriate for stable backporting to kernels that have both
MLO support and the `for_each_link_data` macro (v6.16+). For older
stable trees (v6.1-v6.15), a modified backport with the macro or open-
coded iteration would be needed.
**YES**
net/mac80211/iface.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 0ca55b9655a7f..72c129478da08 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -350,6 +350,8 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
/* we hold the RTNL here so can safely walk the list */
list_for_each_entry(nsdata, &local->interfaces, list) {
if (nsdata != sdata && ieee80211_sdata_running(nsdata)) {
+ struct ieee80211_link_data *link;
+
/*
* Only OCB and monitor mode may coexist
*/
@@ -376,8 +378,10 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
* will not add another interface while any channel
* switch is active.
*/
- if (nsdata->vif.bss_conf.csa_active)
- return -EBUSY;
+ for_each_link_data(nsdata, link) {
+ if (link->conf->csa_active)
+ return -EBUSY;
+ }
/*
* The remaining checks are only performed for interfaces
--
2.51.0
^ permalink raw reply related
* [PATCH AUTOSEL 6.18-5.10] wifi: mac80211: don't increment crypto_tx_tailroom_needed_cnt twice
From: Sasha Levin @ 2026-01-28 22:33 UTC (permalink / raw)
To: patches, stable
Cc: Miri Korenblit, Johannes Berg, Sasha Levin, johannes,
linux-wireless
In-Reply-To: <20260128223332.2806589-1-sashal@kernel.org>
From: Miri Korenblit <miriam.rachel.korenblit@intel.com>
[ Upstream commit 3f3d8ff31496874a69b131866f62474eb24ed20a ]
In reconfig, in case the driver asks to disconnect during the reconfig,
all the keys of the interface are marked as tainted.
Then ieee80211_reenable_keys will loop over all the interface keys, and
for each one it will
a) increment crypto_tx_tailroom_needed_cnt
b) call ieee80211_key_enable_hw_accel, which in turn will detect that
this key is tainted, so it will mark it as "not in hardware", which is
paired with crypto_tx_tailroom_needed_cnt incrementation, so we get two
incrementations for each tainted key.
Then we get a warning in ieee80211_free_keys.
To fix it, don't increment the count in ieee80211_reenable_keys for
tainted keys
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260118092821.4ca111fddcda.Id6e554f4b1c83760aa02d5a9e4e3080edb197aa2@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
So KEY_FLAG_TAINTED was introduced in v4.20. The bug being fixed is
likely in all kernels from 4.20 onward. Let me verify the current state
after the patch would be applied.
The patch being analyzed changes:
```c
list_for_each_entry(key, &sdata->key_list, list) {
increment_tailroom_need_count(sdata); // OLD: unconditional
ieee80211_key_enable_hw_accel(key);
}
```
to:
```c
list_for_each_entry(key, &sdata->key_list, list) {
if (!(key->flags & KEY_FLAG_TAINTED)) // NEW: skip for tainted
increment_tailroom_need_count(sdata);
ieee80211_key_enable_hw_accel(key);
}
```
This is correct because:
- For non-tainted keys: increment once here, and if HW accel succeeds,
it will be decremented in `ieee80211_key_enable_hw_accel()` (line
187). Net result: proper accounting.
- For tainted keys: skip here, let `ieee80211_key_enable_hw_accel()`
handle the increment (line 141) when clearing
`KEY_FLAG_UPLOADED_TO_HARDWARE`. Net result: proper accounting.
## Analysis Summary
**1. COMMIT MESSAGE ANALYSIS:**
- Clear explanation of the problem: double increment of
`crypto_tx_tailroom_needed_cnt` for tainted keys during reconfig
- The symptom is a warning in `ieee80211_free_keys`
- Reviewed-by from Johannes Berg (mac80211 maintainer) adds confidence
**2. CODE CHANGE ANALYSIS:**
- The bug: In `ieee80211_reenable_keys()`, for each key:
1. `increment_tailroom_need_count(sdata)` is called unconditionally
2. `ieee80211_key_enable_hw_accel(key)` is called
- For tainted keys (marked when driver requests disconnect during
reconfig), `ieee80211_key_enable_hw_accel()` will:
- Also call `increment_tailroom_need_count(sdata)` at line 141 if the
key was uploaded to hardware
- Clear the `KEY_FLAG_UPLOADED_TO_HARDWARE` flag
- Result: Two increments per tainted key instead of one
- The fix: Skip the first increment for tainted keys since they'll get
their increment inside `ieee80211_key_enable_hw_accel()`
**3. CLASSIFICATION:**
- This is a clear BUG FIX for a reference counting error
- Not a feature addition
- Fixes a real warning that users would see in kernel logs
**4. SCOPE AND RISK ASSESSMENT:**
- Very small change: adds a simple conditional check (1 line changed)
- Localized to mac80211 key handling
- Low risk: the logic is straightforward and has been reviewed by the
subsystem maintainer
- The worst-case if the fix is wrong would be the opposite accounting
error
**5. USER IMPACT:**
- WiFi users doing rekeys or going through suspend/resume/HW restart
with certain driver/firmware combinations could hit this
- The symptom is a kernel warning (WARN_ON_ONCE in
`ieee80211_free_keys`)
- It causes incorrect tailroom accounting which could potentially lead
to issues with software encryption
**6. STABILITY INDICATORS:**
- Reviewed-by: Johannes Berg (mac80211 maintainer)
- The code path and fix are straightforward
**7. DEPENDENCY CHECK:**
- KEY_FLAG_TAINTED exists since v4.20
- The function `ieee80211_reenable_keys()` exists in stable trees
- The code affected exists in all supported stable kernels
- No dependency on other commits - this is a standalone fix
The fix is small, surgical, reviewed by the subsystem maintainer, fixes
a real bug (warning + incorrect reference counting), and meets all
stable kernel criteria.
**YES**
net/mac80211/key.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index d5da7ccea66e0..04c8809173d7f 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -987,7 +987,8 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata)
if (ieee80211_sdata_running(sdata)) {
list_for_each_entry(key, &sdata->key_list, list) {
- increment_tailroom_need_count(sdata);
+ if (!(key->flags & KEY_FLAG_TAINTED))
+ increment_tailroom_need_count(sdata);
ieee80211_key_enable_hw_accel(key);
}
}
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v2] wifi: ath10k: fix lock protection in ath10k_wmi_event_peer_sta_ps_state_chg()
From: Jeff Johnson @ 2026-01-28 16:36 UTC (permalink / raw)
To: Ziyi Guo, Jeff Johnson; +Cc: linux-wireless, ath10k, linux-kernel
In-Reply-To: <20260123175611.767731-1-n7l8m4@u.northwestern.edu>
On 1/23/2026 9:56 AM, Ziyi Guo wrote:
> ath10k_wmi_event_peer_sta_ps_state_chg() uses lockdep_assert_held() to
> assert that ar->data_lock should be held by the caller, but neither
> ath10k_wmi_10_2_op_rx() nor ath10k_wmi_10_4_op_rx() acquire this lock
> before calling this function.
>
> The field arsta->peer_ps_state is documented as protected by
> ar->data_lock in core.h, and other accessors (ath10k_peer_ps_state_disable,
> ath10k_dbg_sta_read_peer_ps_state) properly acquire this lock.
>
> Add spin_lock_bh()/spin_unlock_bh() around the peer_ps_state update,
> and remove the lockdep_assert_held() to be aligned with new locking,
> following the pattern used by other WMI event handlers in the driver.
>
> Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
> ---
> v2:
> - Remove lockdep_assert_held() as suggested, since
> we are now taking the lock internally.
>
> drivers/net/wireless/ath/ath10k/wmi.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c
> index b4aad6604d6d..061a2fa8f00f 100644
> --- a/drivers/net/wireless/ath/ath10k/wmi.c
> +++ b/drivers/net/wireless/ath/ath10k/wmi.c
> @@ -5289,7 +5289,6 @@ ath10k_wmi_event_peer_sta_ps_state_chg(struct ath10k *ar, struct sk_buff *skb)
> struct ath10k_sta *arsta;
> u8 peer_addr[ETH_ALEN];
>
> - lockdep_assert_held(&ar->data_lock);
note that removing just this line results in an ath10k-check warning:
drivers/net/wireless/ath/ath10k/wmi.c:5292: Please don't use multiple blank lines
I'll also remove one of the blank lines when I apply this patch
>
> ev = (struct wmi_peer_sta_ps_state_chg_event *)skb->data;
> ether_addr_copy(peer_addr, ev->peer_macaddr.addr);
> @@ -5305,7 +5304,9 @@ ath10k_wmi_event_peer_sta_ps_state_chg(struct ath10k *ar, struct sk_buff *skb)
> }
>
> arsta = (struct ath10k_sta *)sta->drv_priv;
> + spin_lock_bh(&ar->data_lock);
> arsta->peer_ps_state = __le32_to_cpu(ev->peer_ps_state);
> + spin_unlock_bh(&ar->data_lock);
>
> exit:
> rcu_read_unlock();
^ permalink raw reply
* Re: [PATCH v2] wifi: ath11k: fix memory leaks in beacon template setup
From: Jeff Johnson @ 2026-01-28 16:30 UTC (permalink / raw)
To: Zilin Guan, jjohnson
Cc: baochen.qiang, linux-wireless, ath11k, linux-kernel, jianhao.xu
In-Reply-To: <20260120063731.2383695-1-zilin@seu.edu.cn>
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?
> }
>
> if (tx_arvif == arvif) {
> - if (ath11k_mac_set_vif_params(tx_arvif, beacons->bcn[0].skb))
> - return -EINVAL;
> + if (ath11k_mac_set_vif_params(tx_arvif, beacons->bcn[0].skb)) {
> + ret = -EINVAL;
> + goto free;
> + }
> } else {
> arvif->wpaie_present = tx_arvif->wpaie_present;
> }
> @@ -1589,11 +1591,11 @@ static int ath11k_mac_setup_bcn_tmpl_ema(struct ath11k_vif *arvif,
> }
> }
>
> - ieee80211_beacon_free_ema_list(beacons);
> -
> if (tx_arvif != arvif && !nontx_vif_params_set)
> - return -EINVAL; /* Profile not found in the beacons */
> + ret = -EINVAL; /* Profile not found in the beacons */
>
> +free:
> + ieee80211_beacon_free_ema_list(beacons);
> return ret;
> }
>
> @@ -1622,19 +1624,22 @@ static int ath11k_mac_setup_bcn_tmpl_mbssid(struct ath11k_vif *arvif,
> }
>
> if (tx_arvif == arvif) {
> - if (ath11k_mac_set_vif_params(tx_arvif, bcn))
> - return -EINVAL;
> + if (ath11k_mac_set_vif_params(tx_arvif, bcn)) {
> + ret = -EINVAL;
> + goto free;
> + }
> } else if (!ath11k_mac_set_nontx_vif_params(tx_arvif, arvif, bcn)) {
> - return -EINVAL;
> + ret = -EINVAL;
> + goto free;
> }
>
> ret = ath11k_wmi_bcn_tmpl(ar, arvif->vdev_id, &offs, bcn, 0);
> - kfree_skb(bcn);
> -
> if (ret)
> ath11k_warn(ab, "failed to submit beacon template command: %d\n",
> ret);
>
> +free:
> + kfree_skb(bcn);
> return ret;
> }
>
^ permalink raw reply
* Re: [PATCH v4 ath-current 1/2] wifi: ath11k: add usecase firmware handling based on device compatible
From: Vasanthakumar Thiagarajan @ 2026-01-28 15:42 UTC (permalink / raw)
To: Miaoqing Pan, jjohnson, johannes, robh, krzk+dt, conor+dt
Cc: ath11k, linux-wireless, linux-kernel, devicetree, jonas.gorski,
krzk
In-Reply-To: <20260121095055.3683957-2-miaoqing.pan@oss.qualcomm.com>
On 1/21/2026 3:20 PM, Miaoqing Pan wrote:
> For M.2 WLAN chips, there is no suitable DTS node to specify the
> firmware-name property. In addition, assigning firmware for the
> M.2 PCIe interface causes chips that do not use usecase specific
> firmware to fail. Therefore, abandoning the approach of specifying
> firmware in DTS. As an alternative, propose a static lookup table
> mapping device compatible to firmware names. Currently, only WCN6855
> HW2.1 requires this.
>
> However, support for the firmware-name property is retained to keep
> the ABI backwards compatible.
>
> For details on usecase specific firmware, see:
> https://lore.kernel.org/all/20250522013444.1301330-3-miaoqing.pan@oss.qualcomm.com/.
>
> Tested-on: WCN6855 hw2.1 PCI WLAN.HSP.1.1-04685-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
>
> Fixes: edbbc647c4f3 ("wifi: ath11k: support usercase-specific firmware overrides")
> Signed-off-by: Miaoqing Pan <miaoqing.pan@oss.qualcomm.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH v4 ath-current 0/2] wifi: ath11k: add usecase firmware handling based on device compatible
From: Jeff Johnson @ 2026-01-28 15:30 UTC (permalink / raw)
To: Krzysztof Kozlowski, Miaoqing Pan, jjohnson, johannes, robh,
krzk+dt, conor+dt
Cc: ath11k, linux-wireless, linux-kernel, devicetree, jonas.gorski
In-Reply-To: <32845805-5758-4c35-8818-ebccbfd9546a@kernel.org>
On 1/28/2026 12:01 AM, Krzysztof Kozlowski wrote:
> On 27/01/2026 18:31, Jeff Johnson wrote:
>> On 1/21/2026 1:50 AM, Miaoqing Pan wrote:
>>> The 'firmware-name' property was introduced to allow end-users and
>>> integrators to select use-case-specific firmware for the WCN6855.
>>> But for M.2 WLAN chips, there is no suitable DTS node to specify
>>> the 'firmware-name' property. In addition, assigning firmware for
>>> the M.2 PCIe interface causes chips that do not use use-case-specific
>>> firmware to fail. Therefore, abandoning the approach of specifying
>>> firmware in DTS. As an alternative, propose a static lookup table
>>> mapping device compatible to firmware names.
>>>
>>> ---
>>> v2:
>>> - Drops `firmware-name` from completely.
>>> - Updates the commit message to clearly state that the property is
>>> obsolete and the change is ABI-breaking but safe for upstream.
>>> v3:
>>> - Deprecate 'firmware-name' property instead of obsolete.
>>> - Keep the ABI backwards compatible.
>>> v4:
>>> - Use of_machine_is_compatible() to simplify the code.
>>> - Add back Acked-by tag.
>>> ---
>>>
>>> Miaoqing Pan (2):
>>> wifi: ath11k: add usecase firmware handling based on device compatible
>>> dt-bindings: net: wireless: ath11k-pci: deprecate 'firmware-name'
>>> property
>>>
>>> .../net/wireless/qcom,ath11k-pci.yaml | 1 +
>>> drivers/net/wireless/ath/ath11k/core.c | 27 +++++++++++++++++++
>>> drivers/net/wireless/ath/ath11k/core.h | 4 +++
>>> 3 files changed, 32 insertions(+)
>>>
>>>
>>> base-commit: d8e1f4a193101a72235416f189b01131a57e26e9
>>
>> Krzysztof,
>> Since you previously NAKed this series, can you confirm that your review
>> comments have been addressed?
>
>
> Binding looks fine, but I did not check the driver.
Thanks Krzysztof for your re-review.
Although tagged for ath-current, we are too far in the v6.19 release cycle for
this, so I'll be taking it through ath-next for the v7.0 merge window.
/jeff
^ permalink raw reply
* [PATCH wireless-next] wifi: mac80211: give the AP more time for EPPKE as well
From: Johannes Berg @ 2026-01-28 13:24 UTC (permalink / raw)
To: linux-wireless; +Cc: Kavita Kavita, Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
EPPKE authentication can use SAE (via PASN), so give the
AP more time to respond to EPPKE case just like for SAE.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/mlme.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index df170556825f..4f4d6449555d 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -8408,7 +8408,8 @@ static int ieee80211_auth(struct ieee80211_sub_if_data *sdata)
return -ETIMEDOUT;
}
- if (auth_data->algorithm == WLAN_AUTH_SAE)
+ if (auth_data->algorithm == WLAN_AUTH_SAE ||
+ auth_data->algorithm == WLAN_AUTH_EPPKE)
info.duration = jiffies_to_msecs(IEEE80211_AUTH_TIMEOUT_SAE);
info.link_id = auth_data->link_id;
--
2.52.0
^ permalink raw reply related
* Re: [PATCH wireless-next v3] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
From: Johannes Berg @ 2026-01-28 12:57 UTC (permalink / raw)
To: Lorenzo Bianconi
Cc: Ryder Lee, Sean Wang, Matthias Brugger,
AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel
In-Reply-To: <aXoHdmjLKB-g54ie@lore-desk>
On Wed, 2026-01-28 at 13:56 +0100, Lorenzo Bianconi wrote:
> >
> > > + if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params))
> > > + return;
> > > +
> > > + ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
> >
> > IMHO it would make more sense to pass "ptr, opt_len" rather than "mgmt,
> > opt_len", since that's just what gets copied and then the callee doesn't
> > need to dig it out.
>
> But I guess we need mgmt pointer to set even other fields (e.g. dst-addr,
> dialog_token, ecc.).
Yeah, good point, passing them all individually isn't great either.
johannes
^ permalink raw reply
* Re: [PATCH wireless-next v3] wifi: mac80211: Add eMLSR/eMLMR action frame parsing support
From: Lorenzo Bianconi @ 2026-01-28 12:56 UTC (permalink / raw)
To: Johannes Berg
Cc: Ryder Lee, Sean Wang, Matthias Brugger,
AngeloGioacchino Del Regno, linux-wireless, Felix Fietkau,
Shayne Chen, Christian Marangi, linux-mediatek, linux-arm-kernel
In-Reply-To: <69d939aae0abecfdd011219e00076542c6e1abb1.camel@sipsolutions.net>
[-- Attachment #1: Type: text/plain, Size: 1391 bytes --]
> I probably missed this the last time already:
>
> > + 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 &&
>
> Surely 0 is an invalid bitmap? To disable it, you send a frame without
> IEEE80211_EML_CTRL_EMLSR_MODE or IEEE80211_EML_CTRL_EMLMR_MODE set, so I
> don't see how an empty bitmap would be valid?
ack, right. When we disable EMLSR IEEE80211_EML_CTRL_EMLSR_MODE so link_bitmap
is not present. I will fix it in v4.
>
> > + !(eml_params.link_bitmap & sdata->vif.active_links))
>
> That's the wrong check, I think? It only checks that you have any
> overlap, but we need all bits to be valid, i.e. only
>
> (link_bitmap & active_links) == link_bitmap
>
> is valid, no?
ack, I will fix it in v4.
>
> > + return;
> > + }
> > +
> > + if (drv_set_eml_op_mode(sdata, &sta->sta, &eml_params))
> > + return;
> > +
> > + ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
>
> IMHO it would make more sense to pass "ptr, opt_len" rather than "mgmt,
> opt_len", since that's just what gets copied and then the callee doesn't
> need to dig it out.
But I guess we need mgmt pointer to set even other fields (e.g. dst-addr,
dialog_token, ecc.).
Regards,
Lorenzo
>
> johannes
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [PATCH wireless-next v4 3/3] wifi: mac80211: add initial UHR support
From: Johannes Berg @ 2026-01-28 12:30 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260128133033.aa4a43982df0.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>
---
v4:
- fix NPCA validation
v3:
- use uhr_oper instead of removed uhr_capa
- fix indentation (Jeff Johnson)
---
include/net/mac80211.h | 33 ++++++++++-
net/mac80211/Makefile | 2 +-
net/mac80211/cfg.c | 14 +++++
net/mac80211/ieee80211_i.h | 17 +++++-
net/mac80211/main.c | 13 ++++-
net/mac80211/mlme.c | 117 ++++++++++++++++++++++++++++++++++---
net/mac80211/parse.c | 20 +++++++
net/mac80211/rx.c | 26 +++++++++
net/mac80211/sta_info.c | 11 ++++
net/mac80211/sta_info.h | 80 ++++++++++++++++++-------
net/mac80211/uhr.c | 29 +++++++++
net/mac80211/util.c | 36 +++++++++++-
12 files changed, 364 insertions(+), 34 deletions(-)
create mode 100644 net/mac80211/uhr.c
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf3..7bfcb2c02801 100644
--- a/include/net/mac80211.h
+++ b/include/net/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..5f816b4ec438 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -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..b0a3b264d678 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/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..b209e865a657 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -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..558eb3385bf5 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -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)) {
+ 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..a2776196f97c 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -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..24dda075820d 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-2025 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..18029fa93f40
--- /dev/null
+++ b/net/mac80211/uhr.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UHR handling
+ *
+ * Copyright(c) 2025 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)))
--
2.52.0
^ permalink raw reply related
* [PATCH wireless-next v4 2/3] wifi: cfg80211: add initial UHR support
From: Johannes Berg @ 2026-01-28 12:30 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260128133033.aa4a43982df0.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>
---
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 | 56 ++++++++++++++++++-
include/uapi/linux/nl80211.h | 30 +++++++++++
net/wireless/nl80211.c | 102 +++++++++++++++++++++++++++++++++--
net/wireless/reg.c | 2 +
net/wireless/util.c | 99 ++++++++++++++++++++++++++--------
5 files changed, 262 insertions(+), 27 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 0ae0aa7594a3..d497a0a131c7 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.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..f33054a907fe 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);
+}
+
/* 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..3bd4a64a0f92 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -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..cc8a9fba7b93 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -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 v4 1/3] wifi: ieee80211: add some initial UHR definitions
From: Johannes Berg @ 2026-01-28 12:30 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>
---
v4:
- update to D1.2, including DBE in UHR capabilities
- fold in suggestions from Pablo
---
include/linux/ieee80211-uhr.h | 215 ++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 33 +++++-
2 files changed, 246 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..06556867b004
--- /dev/null
+++ b/include/linux/ieee80211-uhr.h
@@ -0,0 +1,215 @@
+/* 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)
+{
+ 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;
+
+ if (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 += 24;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ needed += 24;
+ }
+
+ return len >= needed;
+}
+
+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) {
+ u8 dbe = cap->variable[0];
+
+ offs += 1;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ offs += 24;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ offs += 24;
+ }
+
+ 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
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