* [PATCH wireless-next v8 0/3] wifi: initial UHR support
@ 2026-01-30 15:21 Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 1/3] wifi: ieee80211: add some initial UHR definitions Johannes Berg
` (4 more replies)
0 siblings, 5 replies; 23+ messages in thread
From: Johannes Berg @ 2026-01-30 15:21 UTC (permalink / raw)
To: linux-wireless
Should probably just not be trying to do this while I have a fever...
Maybe this one's going to be the last respin despite that.
FWIW, I'm also working on further NPCA support, both rudimentary AP
(just configuration of the NPCA chandef) and non-AP side. But I'm
not going to post _that_ until I get through my fever ;-)
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH wireless-next v8 1/3] wifi: ieee80211: add some initial UHR definitions
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
@ 2026-01-30 15:21 ` Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support Johannes Berg
` (3 subsequent siblings)
4 siblings, 0 replies; 23+ messages in thread
From: Johannes Berg @ 2026-01-30 15:21 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
This is based on Draft P802.11bn_D1.2, but that's still very
incomplete, so don't handle a number of things and make some
local decisions such as using 40 bits for MAC capabilities
and 8 bits for PHY capabilities.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
v6:
- add 'ap' argument to ieee80211_uhr_phy_cap()
v5:
- use correct EHT MCS len (24 bits, not 24 bytes)
- handle DBE AP/non-AP in ieee80211_uhr_capa_size_ok()
v4:
- update to D1.2, including DBE in UHR capabilities
- fold in suggestions from Pablo
---
include/linux/ieee80211-uhr.h | 220 ++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 33 ++++-
2 files changed, 251 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..132acced7d79
--- /dev/null
+++ b/include/linux/ieee80211-uhr.h
@@ -0,0 +1,220 @@
+/* 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_operation {
+ __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_operation *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_operation *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_operation *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_cap_mac {
+ u8 mac_cap[5];
+} __packed;
+
+struct ieee80211_uhr_cap {
+ struct ieee80211_uhr_cap_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_cap_phy {
+ u8 cap;
+} __packed;
+
+static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len,
+ bool from_ap)
+{
+ const struct ieee80211_uhr_cap *cap = (const void *)data;
+ size_t needed = sizeof(*cap) + sizeof(struct ieee80211_uhr_cap_phy);
+
+ if (len < needed)
+ return false;
+
+ /*
+ * A non-AP STA does not include the DBE Capability Parameters field
+ * in the UHR MAC Capabilities Information field.
+ */
+ if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
+ u8 dbe;
+
+ needed += 1;
+ if (len < needed)
+ return false;
+
+ dbe = cap->variable[0];
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ needed += 3;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ needed += 3;
+ }
+
+ return len >= needed;
+}
+
+static inline const struct ieee80211_uhr_cap_phy *
+ieee80211_uhr_phy_cap(const struct ieee80211_uhr_cap *cap, bool from_ap)
+{
+ u8 offs = 0;
+
+ if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) {
+ u8 dbe = cap->variable[0];
+
+ offs += 1;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES)
+ offs += 3;
+
+ if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES)
+ offs += 3;
+ }
+
+ return (const void *)&cap->variable[offs];
+}
+
+#define IEEE80211_SMD_INFO_CAPA_DL_DATA_FWD 0x01
+#define IEEE80211_SMD_INFO_CAPA_MAX_NUM_PREP 0x0E
+#define IEEE80211_SMD_INFO_CAPA_TYPE 0x10
+#define IEEE80211_SMD_INFO_CAPA_PTK_PER_AP_MLD 0x20
+
+struct ieee80211_smd_info {
+ u8 id[ETH_ALEN];
+ u8 capa;
+ __le16 timeout;
+} __packed;
+
+#endif /* LINUX_IEEE80211_UHR_H */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index fbde215c25aa..82d797be95b9 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -9,7 +9,7 @@
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (c) 2018 - 2025 Intel Corporation
+ * Copyright (c) 2018 - 2026 Intel Corporation
*/
#ifndef LINUX_IEEE80211_H
@@ -1200,8 +1200,9 @@ struct ieee80211_mgmt {
#define BSS_MEMBERSHIP_SELECTOR_SAE_H2E 123
#define BSS_MEMBERSHIP_SELECTOR_HE_PHY 122
#define BSS_MEMBERSHIP_SELECTOR_EHT_PHY 121
+#define BSS_MEMBERSHIP_SELECTOR_UHR_PHY 120
-#define BSS_MEMBERSHIP_SELECTOR_MIN BSS_MEMBERSHIP_SELECTOR_EHT_PHY
+#define BSS_MEMBERSHIP_SELECTOR_MIN BSS_MEMBERSHIP_SELECTOR_UHR_PHY
/* mgmt header + 1 byte category code */
#define IEEE80211_MIN_ACTION_SIZE offsetof(struct ieee80211_mgmt, u.action.u)
@@ -1802,6 +1803,15 @@ enum ieee80211_eid_ext {
WLAN_EID_EXT_BANDWIDTH_INDICATION = 135,
WLAN_EID_EXT_KNOWN_STA_IDENTIFCATION = 136,
WLAN_EID_EXT_NON_AP_STA_REG_CON = 137,
+ WLAN_EID_EXT_UHR_OPER = 151,
+ WLAN_EID_EXT_UHR_CAPA = 152,
+ WLAN_EID_EXT_MACP = 153,
+ WLAN_EID_EXT_SMD = 154,
+ WLAN_EID_EXT_BSS_SMD_TRANS_PARAMS = 155,
+ WLAN_EID_EXT_CHAN_USAGE = 156,
+ WLAN_EID_EXT_UHR_MODE_CHG = 157,
+ WLAN_EID_EXT_UHR_PARAM_UPD = 158,
+ WLAN_EID_EXT_TXPI = 159,
};
/* Action category code */
@@ -2745,6 +2755,22 @@ static inline bool for_each_element_completed(const struct element *element,
#define WLAN_RSNX_CAPA_PROTECTED_TWT BIT(4)
#define WLAN_RSNX_CAPA_SAE_H2E BIT(5)
+/* EBPCC = Enhanced BSS Parameter Change Count */
+#define IEEE80211_ENH_CRIT_UPD_EBPCC 0x0F
+#define IEEE80211_ENH_CRIT_UPD_TYPE 0x70
+#define IEEE80211_ENH_CRIT_UPD_TYPE_NO_UHR 0
+#define IEEE80211_ENH_CRIT_UPD_TYPE_UHR 1
+#define IEEE80211_ENH_CRIT_UPD_ALL 0x80
+
+/**
+ * struct ieee80211_enh_crit_upd - enhanced critical update (UHR)
+ * @v: value of the enhanced critical update data,
+ * see %IEEE80211_ENH_CRIT_UPD_* to parse the bits
+ */
+struct ieee80211_enh_crit_upd {
+ u8 v;
+} __packed;
+
/*
* reduced neighbor report, based on Draft P802.11ax_D6.1,
* section 9.4.2.170 and accepted contributions.
@@ -2763,6 +2789,7 @@ static inline bool for_each_element_completed(const struct element *element,
#define IEEE80211_RNR_TBTT_PARAMS_COLOC_ESS 0x10
#define IEEE80211_RNR_TBTT_PARAMS_PROBE_ACTIVE 0x20
#define IEEE80211_RNR_TBTT_PARAMS_COLOC_AP 0x40
+#define IEEE80211_RNR_TBTT_PARAMS_SAME_SMD 0x80
#define IEEE80211_RNR_TBTT_PARAMS_PSD_NO_LIMIT 127
#define IEEE80211_RNR_TBTT_PARAMS_PSD_RESERVED -128
@@ -2815,12 +2842,14 @@ struct ieee80211_tbtt_info_ge_11 {
u8 bss_params;
s8 psd_20;
struct ieee80211_rnr_mld_params mld_params;
+ struct ieee80211_enh_crit_upd enh_crit_upd;
} __packed;
#include "ieee80211-ht.h"
#include "ieee80211-vht.h"
#include "ieee80211-he.h"
#include "ieee80211-eht.h"
+#include "ieee80211-uhr.h"
#include "ieee80211-mesh.h"
#include "ieee80211-s1g.h"
#include "ieee80211-p2p.h"
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 1/3] wifi: ieee80211: add some initial UHR definitions Johannes Berg
@ 2026-01-30 15:21 ` Johannes Berg
2026-02-11 14:19 ` Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 3/3] wifi: mac80211: " Johannes Berg
` (2 subsequent siblings)
4 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-01-30 15:21 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
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>
---
v8:
- fix IM MCS validation
- fix AP parameters uhr_oper type
v7:
- adjust for changed naming
v5:
- validate NL80211_ATTR_UHR_CAPABILITY for non-AP (only)
v4:
- check for correct NSS/MCS for interference mitigation
v3:
- remove UHR capa pointer from AP settings, it's not in
the beacon anyway
- fix kernel-doc (Jeff Johnson)
---
include/net/cfg80211.h | 58 ++++++++++++++++++--
include/uapi/linux/nl80211.h | 30 +++++++++++
net/wireless/nl80211.c | 102 +++++++++++++++++++++++++++++++++--
net/wireless/reg.c | 4 +-
net/wireless/util.c | 101 ++++++++++++++++++++++++++--------
5 files changed, 265 insertions(+), 30 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 7911ed58abbb..fc01de19c798 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -7,7 +7,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/ethtool.h>
@@ -126,6 +126,7 @@ struct wiphy;
* @IEEE80211_CHAN_NO_4MHZ: 4 MHz bandwidth is not permitted on this channel.
* @IEEE80211_CHAN_NO_8MHZ: 8 MHz bandwidth is not permitted on this channel.
* @IEEE80211_CHAN_NO_16MHZ: 16 MHz bandwidth is not permitted on this channel.
+ * @IEEE80211_CHAN_NO_UHR: UHR operation is not permitted on this channel.
*/
enum ieee80211_channel_flags {
IEEE80211_CHAN_DISABLED = BIT(0),
@@ -143,6 +144,7 @@ enum ieee80211_channel_flags {
IEEE80211_CHAN_NO_10MHZ = BIT(12),
IEEE80211_CHAN_NO_HE = BIT(13),
/* can use free bits here */
+ IEEE80211_CHAN_NO_UHR = BIT(18),
IEEE80211_CHAN_NO_320MHZ = BIT(19),
IEEE80211_CHAN_NO_EHT = BIT(20),
IEEE80211_CHAN_DFS_CONCURRENT = BIT(21),
@@ -429,6 +431,18 @@ struct ieee80211_sta_eht_cap {
u8 eht_ppe_thres[IEEE80211_EHT_PPE_THRES_MAX_LEN];
};
+/**
+ * struct ieee80211_sta_uhr_cap - STA's UHR capabilities
+ * @has_uhr: true iff UHR is supported and data is valid
+ * @mac: fixed MAC capabilities
+ * @phy: fixed PHY capabilities
+ */
+struct ieee80211_sta_uhr_cap {
+ bool has_uhr;
+ struct ieee80211_uhr_cap_mac mac;
+ struct ieee80211_uhr_cap_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_operation *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_cap *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)
@@ -3262,6 +3312,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),
@@ -3272,6 +3323,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 706a98686068..b63f71850906 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2977,6 +2977,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
@@ -3547,6 +3554,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,
@@ -3899,6 +3909,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 {
@@ -3932,6 +3948,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,
@@ -4254,6 +4273,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
*/
@@ -4271,6 +4294,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,
@@ -4453,6 +4478,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
@@ -4502,6 +4529,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,
@@ -4715,6 +4743,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,
@@ -4741,6 +4770,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 9aa83a6943a2..6e58b238a1f8 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -332,6 +332,15 @@ static int validate_nan_cluster_id(const struct nlattr *attr,
return 0;
}
+static int validate_uhr_capa(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+
+ return ieee80211_uhr_capa_size_ok(data, len, false);
+}
+
/* policy for the attributes */
static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR];
@@ -934,6 +943,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_BSS_PARAM] = { .type = NLA_FLAG },
[NL80211_ATTR_S1G_PRIMARY_2MHZ] = { .type = NLA_FLAG },
[NL80211_ATTR_EPP_PEER] = { .type = NLA_FLAG },
+ [NL80211_ATTR_UHR_CAPABILITY] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_capa, 255),
+ [NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -1319,6 +1331,9 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy,
if ((chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) &&
nla_put_flag(msg, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY))
goto nla_put_failure;
+ if ((chan->flags & IEEE80211_CHAN_NO_UHR) &&
+ nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_UHR))
+ goto nla_put_failure;
}
if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER,
@@ -1954,6 +1969,7 @@ nl80211_send_iftype_data(struct sk_buff *msg,
{
const struct ieee80211_sta_he_cap *he_cap = &iftdata->he_cap;
const struct ieee80211_sta_eht_cap *eht_cap = &iftdata->eht_cap;
+ const struct ieee80211_sta_uhr_cap *uhr_cap = &iftdata->uhr_cap;
if (nl80211_put_iftypes(msg, NL80211_BAND_IFTYPE_ATTR_IFTYPES,
iftdata->types_mask))
@@ -2005,6 +2021,14 @@ nl80211_send_iftype_data(struct sk_buff *msg,
return -ENOBUFS;
}
+ if (uhr_cap->has_uhr) {
+ if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC,
+ sizeof(uhr_cap->mac), &uhr_cap->mac) ||
+ nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY,
+ sizeof(uhr_cap->phy), &uhr_cap->phy))
+ return -ENOBUFS;
+ }
+
if (sband->band == NL80211_BAND_6GHZ &&
nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA,
sizeof(iftdata->he_6ghz_capa),
@@ -6462,6 +6486,17 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
cap->datalen - 1))
return -EINVAL;
}
+
+ cap = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
+ if (cap) {
+ if (!cap->datalen)
+ return -EINVAL;
+ params->uhr_oper = (void *)(cap->data + 1);
+ if (!ieee80211_uhr_oper_size_ok((const u8 *)params->uhr_oper,
+ cap->datalen - 1, true))
+ return -EINVAL;
+ }
+
return 0;
}
@@ -6593,6 +6628,9 @@ static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params
(channel->flags & IEEE80211_CHAN_NO_EHT))
return -EOPNOTSUPP;
+ if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
+ return -EOPNOTSUPP;
+
return 0;
}
@@ -7175,7 +7213,8 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr)
break;
case RATE_INFO_BW_EHT_RU:
rate_flg = 0;
- WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS));
+ WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS) &&
+ !(info->flags & RATE_INFO_FLAGS_UHR_MCS));
break;
}
@@ -7228,6 +7267,23 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr)
nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC,
info->eht_ru_alloc))
return false;
+ } else if (info->flags & RATE_INFO_FLAGS_UHR_MCS) {
+ if (nla_put_u8(msg, NL80211_RATE_INFO_UHR_MCS, info->mcs))
+ return false;
+ if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_NSS, info->nss))
+ return false;
+ if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_GI, info->eht_gi))
+ return false;
+ if (info->bw == RATE_INFO_BW_EHT_RU &&
+ nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC,
+ info->eht_ru_alloc))
+ return false;
+ if (info->flags & RATE_INFO_FLAGS_UHR_ELR_MCS &&
+ nla_put_flag(msg, NL80211_RATE_INFO_UHR_ELR))
+ return false;
+ if (info->flags & RATE_INFO_FLAGS_UHR_IM &&
+ nla_put_flag(msg, NL80211_RATE_INFO_UHR_IM))
+ return false;
}
nla_nest_end(msg, rate);
@@ -8101,7 +8157,8 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
if (params->ext_capab || params->link_sta_params.ht_capa ||
params->link_sta_params.vht_capa ||
params->link_sta_params.he_capa ||
- params->link_sta_params.eht_capa)
+ params->link_sta_params.eht_capa ||
+ params->link_sta_params.uhr_capa)
return -EINVAL;
if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_SPP_AMSDU))
return -EINVAL;
@@ -8321,6 +8378,16 @@ static int nl80211_set_station_tdls(struct genl_info *info,
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params->link_sta_params.eht_capa)
+ return -EINVAL;
+
+ params->link_sta_params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params->link_sta_params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_S1G_CAPABILITY])
params->link_sta_params.s1g_capa =
nla_data(info->attrs[NL80211_ATTR_S1G_CAPABILITY]);
@@ -8641,6 +8708,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params.link_sta_params.eht_capa)
+ return -EINVAL;
+
+ params.link_sta_params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params.link_sta_params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_EML_CAPABILITY]) {
params.eml_cap_present = true;
params.eml_cap =
@@ -8700,10 +8777,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
params.link_sta_params.ht_capa = NULL;
params.link_sta_params.vht_capa = NULL;
- /* HE and EHT require WME */
+ /* HE, EHT and UHR require WME */
if (params.link_sta_params.he_capa_len ||
params.link_sta_params.he_6ghz_capa ||
- params.link_sta_params.eht_capa_len)
+ params.link_sta_params.eht_capa_len ||
+ params.link_sta_params.uhr_capa_len)
return -EINVAL;
}
@@ -12376,6 +12454,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]),
@@ -13248,6 +13329,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]),
@@ -17680,6 +17764,16 @@ nl80211_add_mod_link_station(struct sk_buff *skb, struct genl_info *info,
}
}
+ if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) {
+ if (!params.eht_capa)
+ return -EINVAL;
+
+ params.uhr_capa =
+ nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ params.uhr_capa_len =
+ nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]);
+ }
+
if (info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY])
params.he_6ghz_capa =
nla_data(info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY]);
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 6cbfa3b78311..139cb27e5a81 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -5,7 +5,7 @@
* Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -1605,6 +1605,8 @@ static u32 map_regdom_flags(u32 rd_flags)
channel_flags |= IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP;
if (rd_flags & NL80211_RRF_ALLOW_20MHZ_ACTIVITY)
channel_flags |= IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY;
+ if (rd_flags & NL80211_RRF_NO_UHR)
+ channel_flags |= IEEE80211_CHAN_NO_UHR;
return channel_flags;
}
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 08c525835518..404fe604a8db 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -5,7 +5,7 @@
* Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2023, 2025 Intel Corporation
+ * Copyright (C) 2018-2023, 2025-2026 Intel Corporation
*/
#include <linux/export.h>
#include <linux/bitops.h>
@@ -1574,26 +1574,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 };
@@ -1604,8 +1608,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 >
@@ -1686,7 +1688,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;
}
@@ -1700,11 +1702,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 */
@@ -1829,6 +1884,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 [flat|nested] 23+ messages in thread
* [PATCH wireless-next v8 3/3] wifi: mac80211: add initial UHR support
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 1/3] wifi: ieee80211: add some initial UHR definitions Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support Johannes Berg
@ 2026-01-30 15:21 ` Johannes Berg
2026-01-30 18:29 ` Pablo MARTIN-GOMEZ
2026-02-02 10:27 ` [PATCH wireless-next v8 0/3] wifi: " Pablo MARTIN-GOMEZ
2026-02-02 10:50 ` Karthikeyan Kathirvel
4 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-01-30 15:21 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
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>
---
v7:
- adjust and normalize naming
v6:
- fix ieee80211_uhr_phy_cap() usage
v5:
- parse UHR capa as from AP
v4:
- fix NPCA validation
v3:
- use uhr_oper instead of removed uhr_capa
- fix indentation (Jeff Johnson)
---
include/net/mac80211.h | 35 ++++++++++-
net/mac80211/Makefile | 2 +-
net/mac80211/cfg.c | 16 +++++-
net/mac80211/ieee80211_i.h | 19 +++++-
net/mac80211/main.c | 15 ++++-
net/mac80211/mlme.c | 115 ++++++++++++++++++++++++++++++++++---
net/mac80211/parse.c | 22 ++++++-
net/mac80211/rx.c | 26 +++++++++
net/mac80211/sta_info.c | 13 ++++-
net/mac80211/sta_info.h | 80 +++++++++++++++++++-------
net/mac80211/uhr.c | 30 ++++++++++
net/mac80211/util.c | 36 +++++++++++-
12 files changed, 370 insertions(+), 39 deletions(-)
create mode 100644 net/mac80211/uhr.c
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 36ae7fe9ddf3..7a55762f9af8 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7,7 +7,7 @@
* Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*/
#ifndef MAC80211_H
@@ -706,6 +706,7 @@ struct ieee80211_parsed_tpe {
* @pwr_reduction: power constraint of BSS.
* @eht_support: does this BSS support EHT
* @epcs_support: does this BSS support EPCS
+ * @uhr_support: does this BSS support UHR
* @csa_active: marks whether a channel switch is going on.
* @mu_mimo_owner: indicates interface owns MU-MIMO capability
* @chanctx_conf: The channel context this interface is assigned to, or %NULL
@@ -832,6 +833,8 @@ struct ieee80211_bss_conf {
u8 pwr_reduction;
bool eht_support;
bool epcs_support;
+ bool uhr_support;
+
bool csa_active;
bool mu_mimo_owner;
@@ -1598,6 +1601,7 @@ enum mac80211_rx_encoding {
RX_ENC_VHT,
RX_ENC_HE,
RX_ENC_EHT,
+ RX_ENC_UHR,
};
/**
@@ -1631,7 +1635,7 @@ enum mac80211_rx_encoding {
* @antenna: antenna used
* @rate_idx: index of data rate into band's supported rates or MCS index if
* HT or VHT is used (%RX_FLAG_HT/%RX_FLAG_VHT)
- * @nss: number of streams (VHT, HE and EHT only)
+ * @nss: number of streams (VHT, HE, EHT and UHR only)
* @flag: %RX_FLAG_\*
* @encoding: &enum mac80211_rx_encoding
* @bw: &enum rate_info_bw
@@ -1642,6 +1646,11 @@ enum mac80211_rx_encoding {
* @eht: EHT specific rate information
* @eht.ru: EHT RU, from &enum nl80211_eht_ru_alloc
* @eht.gi: EHT GI, from &enum nl80211_eht_gi
+ * @uhr: UHR specific rate information
+ * @uhr.ru: UHR RU, from &enum nl80211_eht_ru_alloc
+ * @uhr.gi: UHR GI, from &enum nl80211_eht_gi
+ * @uhr.elr: UHR ELR MCS was used
+ * @uhr.im: UHR interference mitigation was used
* @rx_flags: internal RX flags for mac80211
* @ampdu_reference: A-MPDU reference number, must be a different value for
* each A-MPDU but the same for each subframe within one A-MPDU
@@ -1673,6 +1682,12 @@ struct ieee80211_rx_status {
u8 ru:4;
u8 gi:2;
} eht;
+ struct {
+ u8 ru:4;
+ u8 gi:2;
+ u8 elr:1;
+ u8 im:1;
+ } uhr;
};
u8 rate_idx;
u8 nss;
@@ -2434,6 +2449,7 @@ struct ieee80211_sta_aggregates {
* @he_cap: HE capabilities of this STA
* @he_6ghz_capa: on 6 GHz, holds the HE 6 GHz band capabilities
* @eht_cap: EHT capabilities of this STA
+ * @uhr_cap: UHR capabilities of this STA
* @s1g_cap: S1G capabilities of this STA
* @agg: per-link data for multi-link aggregation
* @bandwidth: current bandwidth the station can receive with
@@ -2457,6 +2473,7 @@ struct ieee80211_link_sta {
struct ieee80211_sta_he_cap he_cap;
struct ieee80211_he_6ghz_capa he_6ghz_capa;
struct ieee80211_sta_eht_cap eht_cap;
+ struct ieee80211_sta_uhr_cap uhr_cap;
struct ieee80211_sta_s1g_cap s1g_cap;
struct ieee80211_sta_aggregates agg;
@@ -7289,6 +7306,20 @@ ieee80211_get_eht_iftype_cap_vif(const struct ieee80211_supported_band *sband,
return ieee80211_get_eht_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
}
+/**
+ * ieee80211_get_uhr_iftype_cap_vif - return UHR capabilities for sband/vif
+ * @sband: the sband to search for the iftype on
+ * @vif: the vif to get the iftype from
+ *
+ * Return: pointer to the struct ieee80211_sta_uhr_cap, or %NULL is none found
+ */
+static inline const struct ieee80211_sta_uhr_cap *
+ieee80211_get_uhr_iftype_cap_vif(const struct ieee80211_supported_band *sband,
+ struct ieee80211_vif *vif)
+{
+ return ieee80211_get_uhr_iftype_cap(sband, ieee80211_vif_type_p2p(vif));
+}
+
/**
* ieee80211_update_mu_groups - set the VHT MU-MIMO groud data
*
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index a33884967f21..b0e392eb7753 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -36,7 +36,7 @@ mac80211-y := \
tdls.o \
ocb.o \
airtime.o \
- eht.o
+ eht.o uhr.o
mac80211-$(CONFIG_MAC80211_LEDS) += led.o
mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 964f440e31cd..f83dda0755a7 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5,7 +5,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/ieee80211.h>
@@ -1608,6 +1608,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
link_conf->eht_mu_beamformer = false;
}
+ if (params->uhr_oper) {
+ if (!link_conf->eht_support)
+ return -EOPNOTSUPP;
+
+ link_conf->uhr_support = true;
+ }
+
if (sdata->vif.type == NL80211_IFTYPE_AP &&
params->mbssid_config.tx_wdev) {
err = ieee80211_set_ap_mbssid_options(sdata,
@@ -2085,6 +2092,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
params->vht_capa ||
params->he_capa ||
params->eht_capa ||
+ params->uhr_capa ||
params->s1g_capa ||
params->opmode_notif_used;
@@ -2163,6 +2171,12 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
params->eht_capa_len,
link_sta);
+ if (params->uhr_capa)
+ ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
+ params->uhr_capa,
+ params->uhr_capa_len,
+ link_sta);
+
if (params->s1g_capa)
ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
link_sta);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index ea4c9293d299..3ab40a69163d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -5,7 +5,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2007-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#ifndef IEEE80211_I_H
@@ -394,9 +394,10 @@ enum ieee80211_conn_mode {
IEEE80211_CONN_MODE_VHT,
IEEE80211_CONN_MODE_HE,
IEEE80211_CONN_MODE_EHT,
+ IEEE80211_CONN_MODE_UHR,
};
-#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_EHT
+#define IEEE80211_CONN_MODE_HIGHEST IEEE80211_CONN_MODE_UHR
enum ieee80211_conn_bw_limit {
IEEE80211_CONN_BW_LIMIT_20,
@@ -1824,6 +1825,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_cap *uhr_cap;
+ const struct ieee80211_uhr_operation *uhr_operation;
/* not the order in the psd values is per element, not per chandef */
struct ieee80211_parsed_tpe tpe;
@@ -1848,6 +1851,8 @@ struct ieee802_11_elems {
u8 country_elem_len;
u8 bssid_index_len;
u8 eht_cap_len;
+ u8 uhr_cap_len;
+ u8 uhr_operation_len;
/* mult-link element can be de-fragmented and thus u8 is not sufficient */
size_t ml_basic_len;
@@ -2691,6 +2696,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);
@@ -2866,6 +2874,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_cap *uhr_cap,
+ u8 uhr_cap_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..bedc81956fbc 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -5,7 +5,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <net/mac80211.h>
@@ -1123,7 +1123,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
int result, i;
enum nl80211_band band;
int channels, max_bitrates;
- bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g;
+ bool supp_ht, supp_vht, supp_he, supp_eht, supp_s1g, supp_uhr;
struct cfg80211_chan_def dflt_chandef = {};
if (ieee80211_hw_check(hw, QUEUE_CONTROL) &&
@@ -1237,6 +1237,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
supp_he = false;
supp_eht = false;
supp_s1g = false;
+ supp_uhr = false;
for (band = 0; band < NUM_NL80211_BANDS; band++) {
const struct ieee80211_sband_iftype_data *iftd;
struct ieee80211_supported_band *sband;
@@ -1293,6 +1294,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
supp_he = supp_he || iftd->he_cap.has_he;
supp_eht = supp_eht || iftd->eht_cap.has_eht;
+ supp_uhr = supp_uhr || iftd->uhr_cap.has_uhr;
if (band == NL80211_BAND_2GHZ)
he_40_mhz_cap =
@@ -1325,6 +1327,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (WARN_ON(supp_eht && !supp_he))
return -EINVAL;
+ /* UHR requires EHT support */
+ if (WARN_ON(supp_uhr && !supp_eht))
+ return -EINVAL;
+
if (!sband->ht_cap.ht_supported)
continue;
@@ -1437,6 +1443,11 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
IEEE80211_EHT_PPE_THRES_MAX_LEN;
}
+ if (supp_uhr)
+ local->scan_ies_len +=
+ 3 + sizeof(struct ieee80211_uhr_cap) +
+ sizeof(struct ieee80211_uhr_cap_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 7461f4fa6e08..e83582b2c377 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -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_operation *uhr_oper = elems->uhr_operation;
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_operation_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_cap) +
+ sizeof(struct ieee80211_uhr_cap_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_operation && elems->uhr_cap &&
+ link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) {
+ ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
+ elems->uhr_cap,
+ elems->uhr_cap_len,
+ link_sta);
+
+ bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr;
+ } 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..8260f6bdd5b2 100644
--- a/net/mac80211/parse.c
+++ b/net/mac80211/parse.c
@@ -6,7 +6,7 @@
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*
* element parsing for mac80211
*/
@@ -189,6 +189,26 @@ ieee80211_parse_extension_element(u32 *crc,
elems->ttlm_num++;
}
break;
+ case WLAN_EID_EXT_UHR_OPER:
+ if (params->mode < IEEE80211_CONN_MODE_UHR)
+ break;
+ calc_crc = true;
+ if (ieee80211_uhr_oper_size_ok(data, len,
+ params->type == (IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_BEACON))) {
+ elems->uhr_operation = data;
+ elems->uhr_operation_len = len;
+ }
+ break;
+ case WLAN_EID_EXT_UHR_CAPA:
+ if (params->mode < IEEE80211_CONN_MODE_UHR)
+ break;
+ calc_crc = true;
+ if (ieee80211_uhr_capa_size_ok(data, len, true)) {
+ elems->uhr_cap = data;
+ elems->uhr_cap_len = len;
+ }
+ break;
}
if (crc && calc_crc)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 30b9b4d76357..69034d83a7b6 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -5518,6 +5518,32 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
status->rate_idx, status->nss, status->eht.gi))
goto drop;
break;
+ case RX_ENC_UHR:
+ if (WARN_ONCE(!(status->rate_idx <= 15 ||
+ status->rate_idx == 17 ||
+ status->rate_idx == 19 ||
+ status->rate_idx == 20 ||
+ status->rate_idx == 23) ||
+ !status->nss ||
+ status->nss > 8 ||
+ status->uhr.gi > NL80211_RATE_INFO_EHT_GI_3_2,
+ "Rate marked as a UHR rate but data is invalid: MCS:%d, NSS:%d, GI:%d\n",
+ status->rate_idx, status->nss, status->uhr.gi))
+ goto drop;
+ if (WARN_ONCE(status->uhr.elr &&
+ (status->nss != 1 || status->rate_idx > 1 ||
+ status->uhr.gi != NL80211_RATE_INFO_EHT_GI_1_6 ||
+ status->bw != RATE_INFO_BW_20 || status->uhr.im),
+ "bad UHR ELR MCS MCS:%d, NSS:%d, GI:%d, BW:%d, IM:%d\n",
+ status->rate_idx, status->nss, status->uhr.gi,
+ status->bw, status->uhr.im))
+ goto drop;
+ if (WARN_ONCE(status->uhr.im &&
+ (status->nss != 1 || status->rate_idx == 15),
+ "bad UHR IM MCS MCS:%d, NSS:%d\n",
+ status->rate_idx, status->nss))
+ goto drop;
+ break;
default:
WARN_ON_ONCE(1);
fallthrough;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 22e8561ad6fc..a79ebeb43585 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -4,7 +4,7 @@
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/module.h>
@@ -2567,6 +2567,17 @@ static void sta_stats_decode_rate(struct ieee80211_local *local, u32 rate,
rinfo->eht_gi = STA_STATS_GET(EHT_GI, rate);
rinfo->eht_ru_alloc = STA_STATS_GET(EHT_RU, rate);
break;
+ case STA_STATS_RATE_TYPE_UHR:
+ rinfo->flags = RATE_INFO_FLAGS_UHR_MCS;
+ rinfo->mcs = STA_STATS_GET(UHR_MCS, rate);
+ rinfo->nss = STA_STATS_GET(UHR_NSS, rate);
+ rinfo->eht_gi = STA_STATS_GET(UHR_GI, rate);
+ rinfo->eht_ru_alloc = STA_STATS_GET(UHR_RU, rate);
+ if (STA_STATS_GET(UHR_ELR, rate))
+ rinfo->flags |= RATE_INFO_FLAGS_UHR_ELR_MCS;
+ if (STA_STATS_GET(UHR_IM, rate))
+ rinfo->flags |= RATE_INFO_FLAGS_UHR_IM;
+ break;
}
}
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index b1edf8ed102f..2875ef7d7946 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -3,7 +3,7 @@
* Copyright 2002-2005, Devicescape Software, Inc.
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright(c) 2015-2017 Intel Deutschland GmbH
- * Copyright(c) 2020-2024 Intel Corporation
+ * Copyright(c) 2020-2026 Intel Corporation
*/
#ifndef STA_INFO_H
@@ -1009,25 +1009,49 @@ enum sta_stats_type {
STA_STATS_RATE_TYPE_HE,
STA_STATS_RATE_TYPE_S1G,
STA_STATS_RATE_TYPE_EHT,
+ STA_STATS_RATE_TYPE_UHR,
};
-#define STA_STATS_FIELD_HT_MCS GENMASK( 7, 0)
-#define STA_STATS_FIELD_LEGACY_IDX GENMASK( 3, 0)
-#define STA_STATS_FIELD_LEGACY_BAND GENMASK( 7, 4)
-#define STA_STATS_FIELD_VHT_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_VHT_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_HE_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_HE_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_EHT_MCS GENMASK( 3, 0)
-#define STA_STATS_FIELD_EHT_NSS GENMASK( 7, 4)
-#define STA_STATS_FIELD_BW GENMASK(12, 8)
-#define STA_STATS_FIELD_SGI GENMASK(13, 13)
-#define STA_STATS_FIELD_TYPE GENMASK(16, 14)
-#define STA_STATS_FIELD_HE_RU GENMASK(19, 17)
-#define STA_STATS_FIELD_HE_GI GENMASK(21, 20)
-#define STA_STATS_FIELD_HE_DCM GENMASK(22, 22)
-#define STA_STATS_FIELD_EHT_RU GENMASK(20, 17)
-#define STA_STATS_FIELD_EHT_GI GENMASK(22, 21)
+/* common */
+#define STA_STATS_FIELD_TYPE 0x0000000F
+#define STA_STATS_FIELD_BW 0x000001F0
+#define STA_STATS_FIELD_RESERVED 0x00000E00
+
+/* STA_STATS_RATE_TYPE_LEGACY */
+#define STA_STATS_FIELD_LEGACY_IDX 0x0000F000
+#define STA_STATS_FIELD_LEGACY_BAND 0x000F0000
+
+/* STA_STATS_RATE_TYPE_HT */
+#define STA_STATS_FIELD_HT_MCS 0x000FF000
+
+/* STA_STATS_RATE_TYPE_VHT */
+#define STA_STATS_FIELD_VHT_MCS 0x0000F000
+#define STA_STATS_FIELD_VHT_NSS 0x000F0000
+
+/* HT & VHT */
+#define STA_STATS_FIELD_SGI 0x00100000
+
+/* STA_STATS_RATE_TYPE_HE */
+#define STA_STATS_FIELD_HE_MCS 0x0000F000
+#define STA_STATS_FIELD_HE_NSS 0x000F0000
+#define STA_STATS_FIELD_HE_RU 0x00700000
+#define STA_STATS_FIELD_HE_GI 0x01800000
+#define STA_STATS_FIELD_HE_DCM 0x02000000
+
+/* STA_STATS_RATE_TYPE_EHT */
+#define STA_STATS_FIELD_EHT_MCS 0x0000F000
+#define STA_STATS_FIELD_EHT_NSS 0x000F0000
+#define STA_STATS_FIELD_EHT_RU 0x00F00000
+#define STA_STATS_FIELD_EHT_GI 0x03000000
+
+/* STA_STATS_RATE_TYPE_UHR */
+#define STA_STATS_FIELD_UHR_MCS 0x0001F000
+#define STA_STATS_FIELD_UHR_NSS 0x001E0000
+#define STA_STATS_FIELD_UHR_RU 0x01E00000
+#define STA_STATS_FIELD_UHR_GI 0x06000000
+#define STA_STATS_FIELD_UHR_ELR 0x08000000
+#define STA_STATS_FIELD_UHR_IM 0x10000000
+
#define STA_STATS_FIELD(_n, _v) FIELD_PREP(STA_STATS_FIELD_ ## _n, _v)
#define STA_STATS_GET(_n, _v) FIELD_GET(STA_STATS_FIELD_ ## _n, _v)
@@ -1040,8 +1064,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
r = STA_STATS_FIELD(BW, s->bw);
- if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
- r |= STA_STATS_FIELD(SGI, 1);
+ switch (s->encoding) {
+ case RX_ENC_HT:
+ case RX_ENC_VHT:
+ if (s->enc_flags & RX_ENC_FLAG_SHORT_GI)
+ r |= STA_STATS_FIELD(SGI, 1);
+ break;
+ default:
+ break;
+ }
switch (s->encoding) {
case RX_ENC_VHT:
@@ -1073,6 +1104,15 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s)
r |= STA_STATS_FIELD(EHT_GI, s->eht.gi);
r |= STA_STATS_FIELD(EHT_RU, s->eht.ru);
break;
+ case RX_ENC_UHR:
+ r |= STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_UHR);
+ r |= STA_STATS_FIELD(UHR_NSS, s->nss);
+ r |= STA_STATS_FIELD(UHR_MCS, s->rate_idx);
+ r |= STA_STATS_FIELD(UHR_GI, s->uhr.gi);
+ r |= STA_STATS_FIELD(UHR_RU, s->uhr.ru);
+ r |= STA_STATS_FIELD(UHR_ELR, s->uhr.elr);
+ r |= STA_STATS_FIELD(UHR_IM, s->uhr.im);
+ break;
default:
WARN_ON(1);
return STA_STATS_RATE_INVALID;
diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c
new file mode 100644
index 000000000000..2d8f5e5480ef
--- /dev/null
+++ b/net/mac80211/uhr.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UHR handling
+ *
+ * Copyright(c) 2025-2026 Intel Corporation
+ */
+
+#include "ieee80211_i.h"
+
+void
+ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_supported_band *sband,
+ const struct ieee80211_uhr_cap *uhr_cap,
+ u8 uhr_cap_len,
+ struct link_sta_info *link_sta)
+{
+ struct ieee80211_sta_uhr_cap *sta_uhr_cap = &link_sta->pub->uhr_cap;
+ bool from_ap;
+
+ memset(sta_uhr_cap, 0, sizeof(*sta_uhr_cap));
+
+ if (!ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif))
+ return;
+
+ sta_uhr_cap->has_uhr = true;
+
+ sta_uhr_cap->mac = uhr_cap->mac;
+ from_ap = sdata->vif.type == NL80211_IFTYPE_STATION;
+ sta_uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_cap, from_ap);
+}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 4d5680da7aa0..868346f9e5c9 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_cap) +
+ sizeof(struct ieee80211_uhr_cap_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 [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 3/3] wifi: mac80211: add initial UHR support
2026-01-30 15:21 ` [PATCH wireless-next v8 3/3] wifi: mac80211: " Johannes Berg
@ 2026-01-30 18:29 ` Pablo MARTIN-GOMEZ
2026-02-02 8:27 ` Johannes Berg
0 siblings, 1 reply; 23+ messages in thread
From: Pablo MARTIN-GOMEZ @ 2026-01-30 18:29 UTC (permalink / raw)
To: Johannes Berg, linux-wireless; +Cc: Johannes Berg
On 30/01/2026 16:21, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
>
> Add support for making UHR connections and accepting AP
> stations with UHR support.
>
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
> v7:
> - adjust and normalize naming
> v6:
> - fix ieee80211_uhr_phy_cap() usage
> v5:
> - parse UHR capa as from AP
> v4:
> - fix NPCA validation
> v3:
> - use uhr_oper instead of removed uhr_capa
> - fix indentation (Jeff Johnson)
> ---
> include/net/mac80211.h | 35 ++++++++++-
> net/mac80211/Makefile | 2 +-
> net/mac80211/cfg.c | 16 +++++-
> net/mac80211/ieee80211_i.h | 19 +++++-
> net/mac80211/main.c | 15 ++++-
> net/mac80211/mlme.c | 115 ++++++++++++++++++++++++++++++++++---
> net/mac80211/parse.c | 22 ++++++-
> net/mac80211/rx.c | 26 +++++++++
> net/mac80211/sta_info.c | 13 ++++-
> net/mac80211/sta_info.h | 80 +++++++++++++++++++-------
> net/mac80211/uhr.c | 30 ++++++++++
> net/mac80211/util.c | 36 +++++++++++-
> 12 files changed, 370 insertions(+), 39 deletions(-)
> create mode 100644 net/mac80211/uhr.c
>
[...]
> diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c
> new file mode 100644
> index 000000000000..2d8f5e5480ef
> --- /dev/null
> +++ b/net/mac80211/uhr.c
> @@ -0,0 +1,30 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * UHR handling
> + *
> + * Copyright(c) 2025-2026 Intel Corporation
> + */
> +
> +#include "ieee80211_i.h"
> +
> +void
> +ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
> + struct ieee80211_supported_band *sband,
> + const struct ieee80211_uhr_cap *uhr_cap,
> + u8 uhr_cap_len,
> + struct link_sta_info *link_sta)
> +{
> + struct ieee80211_sta_uhr_cap *sta_uhr_cap = &link_sta->pub->uhr_cap;
> + bool from_ap;
> +
> + memset(sta_uhr_cap, 0, sizeof(*sta_uhr_cap));
> +
> + if (!ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif))
> + return;
> +
> + sta_uhr_cap->has_uhr = true;
> +
> + sta_uhr_cap->mac = uhr_cap->mac;
> + from_ap = sdata->vif.type == NL80211_IFTYPE_STATION;
My knowledge of mac80211 is quite limited so I might be very well wrong
but it feels that either this is wrong and it should be `sdata->vif.type
== NL80211_IFTYPE_AP` or this is correct but it won't work with mesh
point for example and that `sdata->vif.type != NL80211_IFTYPE_AP` would
be better.
> + sta_uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_cap, from_ap);
> +}
Pablo MG
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 3/3] wifi: mac80211: add initial UHR support
2026-01-30 18:29 ` Pablo MARTIN-GOMEZ
@ 2026-02-02 8:27 ` Johannes Berg
0 siblings, 0 replies; 23+ messages in thread
From: Johannes Berg @ 2026-02-02 8:27 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless
On Fri, 2026-01-30 at 19:29 +0100, Pablo MARTIN-GOMEZ wrote:
>
> > +void
> > +ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata,
> > + struct ieee80211_supported_band *sband,
> > + const struct ieee80211_uhr_cap *uhr_cap,
> > + u8 uhr_cap_len,
> > + struct link_sta_info *link_sta)
> > +{
> > + struct ieee80211_sta_uhr_cap *sta_uhr_cap = &link_sta->pub->uhr_cap;
> > + bool from_ap;
> > +
> > + memset(sta_uhr_cap, 0, sizeof(*sta_uhr_cap));
> > +
> > + if (!ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif))
> > + return;
> > +
> > + sta_uhr_cap->has_uhr = true;
> > +
> > + sta_uhr_cap->mac = uhr_cap->mac;
> > + from_ap = sdata->vif.type == NL80211_IFTYPE_STATION;
> My knowledge of mac80211 is quite limited so I might be very well wrong
> but it feels that either this is wrong and it should be `sdata->vif.type
> == NL80211_IFTYPE_AP`
If we _are_ the AP then we won't parse data coming _from_ the AP, so
it's correct.
> or this is correct but it won't work with mesh
> point for example and that `sdata->vif.type != NL80211_IFTYPE_AP` would
> be better.
Also no, according to the spec, I even quoted it the other day. But I
guess if someone wants UHR on mesh they have a bunch of work anyway, so
it doesn't even matter.
johannes
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 0/3] wifi: initial UHR support
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
` (2 preceding siblings ...)
2026-01-30 15:21 ` [PATCH wireless-next v8 3/3] wifi: mac80211: " Johannes Berg
@ 2026-02-02 10:27 ` Pablo MARTIN-GOMEZ
2026-02-02 11:13 ` Johannes Berg
2026-02-02 10:50 ` Karthikeyan Kathirvel
4 siblings, 1 reply; 23+ messages in thread
From: Pablo MARTIN-GOMEZ @ 2026-02-02 10:27 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
On 30/01/2026 16:21, Johannes Berg wrote:
> Should probably just not be trying to do this while I have a fever...
>
> Maybe this one's going to be the last respin despite that.
>
> FWIW, I'm also working on further NPCA support, both rudimentary AP
> (just configuration of the NPCA chandef) and non-AP side. But I'm
> not going to post _that_ until I get through my fever ;-)
>
> johannes
>
Look good to me, but I don't feel I have the sufficient knowledge to
either the standard or mac80211 to warrant a Reviewed-by or an Ack-by.
Pablo
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 0/3] wifi: initial UHR support
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
` (3 preceding siblings ...)
2026-02-02 10:27 ` [PATCH wireless-next v8 0/3] wifi: " Pablo MARTIN-GOMEZ
@ 2026-02-02 10:50 ` Karthikeyan Kathirvel
2026-02-02 11:12 ` Johannes Berg
4 siblings, 1 reply; 23+ messages in thread
From: Karthikeyan Kathirvel @ 2026-02-02 10:50 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
On 1/30/2026 8:51 PM, Johannes Berg wrote:
> Should probably just not be trying to do this while I have a fever...
>
> Maybe this one's going to be the last respin despite that.
>
> FWIW, I'm also working on further NPCA support, both rudimentary AP
> (just configuration of the NPCA chandef) and non-AP side. But I'm
> not going to post _that_ until I get through my fever ;-)
>
> johannes
>
>
Would like to see your approach of NPCA for AP side, since we are
working on Enhanced critical update for all UHR operation features(which
includes NPCA as well).
/KK
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 0/3] wifi: initial UHR support
2026-02-02 10:50 ` Karthikeyan Kathirvel
@ 2026-02-02 11:12 ` Johannes Berg
2026-02-05 8:38 ` Karthikeyan Kathirvel
0 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-02-02 11:12 UTC (permalink / raw)
To: Karthikeyan Kathirvel, linux-wireless
On Mon, 2026-02-02 at 16:20 +0530, Karthikeyan Kathirvel wrote:
>
> Would like to see your approach of NPCA for AP side, since we are
> working on Enhanced critical update for all UHR operation features(which
> includes NPCA as well).
So, FWIW, I went over this a few times in my head ... how do we support
it when one station has NPCA and another doesn't (need a per-STA flag?),
how do we update the NPCA operation when stations with different
parameters join/leave the BSS, etc.
Ultimately I decided that I'm going to leave these problems to you ;-)
For now literally all I've implemented for testing is an ability to
represent NPCA in the chandef (adding npca_chan and npca_punctured
bitmap to it), and two new nl80211 attributes to configure such a
chandef.
That won't even be an upstream hostapd patch, just some hack to set the
NPCA chandef in nl80211 and advertise it in the UHR operation element,
but it's already good enough to test a single client.
(Oh and nl80211 won't accept that unless the driver advertises NPCA
support in UHR for the AP interface, which our driver won't do outside
some testing mode.)
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 0/3] wifi: initial UHR support
2026-02-02 10:27 ` [PATCH wireless-next v8 0/3] wifi: " Pablo MARTIN-GOMEZ
@ 2026-02-02 11:13 ` Johannes Berg
0 siblings, 0 replies; 23+ messages in thread
From: Johannes Berg @ 2026-02-02 11:13 UTC (permalink / raw)
To: Pablo MARTIN-GOMEZ, linux-wireless
On Mon, 2026-02-02 at 11:27 +0100, Pablo MARTIN-GOMEZ wrote:
> On 30/01/2026 16:21, Johannes Berg wrote:
> > Should probably just not be trying to do this while I have a fever...
> >
> > Maybe this one's going to be the last respin despite that.
> >
> > FWIW, I'm also working on further NPCA support, both rudimentary AP
> > (just configuration of the NPCA chandef) and non-AP side. But I'm
> > not going to post _that_ until I get through my fever ;-)
> >
> > johannes
> >
> Look good to me, but I don't feel I have the sufficient knowledge to
> either the standard or mac80211 to warrant a Reviewed-by or an Ack-by.
Thanks :)
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 0/3] wifi: initial UHR support
2026-02-02 11:12 ` Johannes Berg
@ 2026-02-05 8:38 ` Karthikeyan Kathirvel
0 siblings, 0 replies; 23+ messages in thread
From: Karthikeyan Kathirvel @ 2026-02-05 8:38 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
On 2/2/2026 4:42 PM, Johannes Berg wrote:
> On Mon, 2026-02-02 at 16:20 +0530, Karthikeyan Kathirvel wrote:
>>
>> Would like to see your approach of NPCA for AP side, since we are
>> working on Enhanced critical update for all UHR operation features(which
>> includes NPCA as well).
>
> So, FWIW, I went over this a few times in my head ... how do we support
> it when one station has NPCA and another doesn't (need a per-STA flag?),
> how do we update the NPCA operation when stations with different
> parameters join/leave the BSS, etc.
>
> Ultimately I decided that I'm going to leave these problems to you ;-)
This I'll check and come back
>
> For now literally all I've implemented for testing is an ability to
> represent NPCA in the chandef (adding npca_chan and npca_punctured
> bitmap to it), and two new nl80211 attributes to configure such a
> chandef.
>
> That won't even be an upstream hostapd patch, just some hack to set the
> NPCA chandef in nl80211 and advertise it in the UHR operation element,
> but it's already good enough to test a single client.
>
If it is a hack that's fine, but any change in NPCA parameters in AP
side should go through Enhanced Critical Update procedure as per spec
> (Oh and nl80211 won't accept that unless the driver advertises NPCA
> support in UHR for the AP interface, which our driver won't do outside
> some testing mode.)
>
> johannes
Sorry for the delayed response
/KK
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-01-30 15:21 ` [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support Johannes Berg
@ 2026-02-11 14:19 ` Johannes Berg
2026-02-12 11:08 ` Harshitha Prem
0 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-02-11 14:19 UTC (permalink / raw)
To: linux-wireless; +Cc: Karthikeyan Kathirvel
On Fri, 2026-01-30 at 16:21 +0100, Johannes Berg wrote:
>
> @@ -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;
> + }
> +
OK, I just basically copied this from EHT, but it's useless. Due to the
reduced information in the beacon, we don't really have anything here
(such as NPCA timings.)
Should we add a separate netlink attribute for the UHR operation, which
hostapd would fill with the _full_ data like it appears in association
response etc.?
That way, hostapd doesn't need to build a separate data/attribute
structure but can just use hostapd_eid_uhr_operation(..., false) for it.
An alternative would be to add more attributes for everything, but it's
probably more complicated on both sides?
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-11 14:19 ` Johannes Berg
@ 2026-02-12 11:08 ` Harshitha Prem
2026-02-13 10:11 ` Johannes Berg
0 siblings, 1 reply; 23+ messages in thread
From: Harshitha Prem @ 2026-02-12 11:08 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan
On 2/11/2026 7:49 PM, Johannes Berg wrote:
> WARNING: This email originated from outside of Qualcomm. Please be wary of any links or attachments, and do not enable macros.
>
> On Fri, 2026-01-30 at 16:21 +0100, Johannes Berg wrote:
>>
>> @@ -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;
>> + }
>> +
>
> OK, I just basically copied this from EHT, but it's useless. Due to the
> reduced information in the beacon, we don't really have anything here
> (such as NPCA timings.)
>
> Should we add a separate netlink attribute for the UHR operation, which
> hostapd would fill with the _full_ data like it appears in association
> response etc.?
>
> That way, hostapd doesn't need to build a separate data/attribute
> structure but can just use hostapd_eid_uhr_operation(..., false) for it.
>
> An alternative would be to add more attributes for everything, but it's
> probably more complicated on both sides?
>
> johannes
>
Hi Johannes,
Thank you for the suggestions.
We feel that using separate nested attributes for each feature is the better approach, as this allows us to reuse the attributes for the Enhanced BSS Parameter Critical Update procedure, where similar information is carried in the UHR parameters update element.
While this approach is slightly more verbose, we believe it offers better extensibility for the future.
Please let us know your thoughts.
Regards,
Harshitha
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-12 11:08 ` Harshitha Prem
@ 2026-02-13 10:11 ` Johannes Berg
2026-02-13 10:26 ` Johannes Berg
2026-02-16 17:17 ` Harshitha Prem
0 siblings, 2 replies; 23+ messages in thread
From: Johannes Berg @ 2026-02-13 10:11 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih
Hi Harshita,
> > Should we add a separate netlink attribute for the UHR operation, which
> > hostapd would fill with the _full_ data like it appears in association
> > response etc.?
> >
> > That way, hostapd doesn't need to build a separate data/attribute
> > structure but can just use hostapd_eid_uhr_operation(..., false) for it.
> >
> > An alternative would be to add more attributes for everything, but it's
> > probably more complicated on both sides?
> Thank you for the suggestions.
>
> We feel that using separate nested attributes for each feature is the better approach, as this allows us to reuse the attributes for the Enhanced BSS Parameter Critical Update procedure, where similar information is carried in the UHR parameters update element.
Heh, I'll admit I'm surprised - I'm usually the one advocating for
finer-grained attributes, and here I didn't ;-)
> While this approach is slightly more verbose, we believe it offers better extensibility for the future.
Does it actually, though?
I had sort of expected hostapd to add the UHR Parameters Update element
to the beacons, and configure some way of having firmware set the
countdown ("Countdown Timer"). Turns out this is not only in beacons but
also in probe response, (re)association response and (UHR? [1]) link
reconfiguration response frames.
[1] the spec says "Link Reconfiguration Response" but I think it should
say "UHR Link Reconfiguration Response"
But I guess this is going to end up a continuation of the previous wifi7
discussion from last year [2] which hadn't really completed. And Lorenzo
just posted another thing on this [3].
[2] https://lore.kernel.org/linux-wireless/20250717045540.27208-1-aditya.kumar.singh@oss.qualcomm.com/
[3] https://lore.kernel.org/linux-wireless/20260212-mt7996-link-reconf-v1-0-2b110340d6c4@kernel.org/
So I think maybe we need to figure out how we will do all of this first?
Naively, I would've said ... something stupid. I'm reading the spec as
I'm writing this ;-) The UHR Parameters Update element is actually
included in all the beacons across the entire AP MLD.
Maybe we need to take a step back from our previous discussion as well,
and introduce a broader concept here?
I could imagine, for example, something where you say in the nl80211 API
some variation of
1) Let's start a new update operation ("NL80211_CMD_START_MLD_BSS_UPDATE"),
I guess already with some parameters saying:
- the updated affiliated AP (link)
- the number of beacon intervals you want to do it for
- the post-update UHR operation (?)
(or the new channel if it's CSA? etc.?)
- maybe - more critical if we use it for CSA - already the beacon
templates for all the links? with all the things I say in (2)
below, but that's more complex maybe?
the reason I say this is that there's a difference here in how
the counter is done - for CSA etc. the things have to disappear
from the beacon immediately, for UHR updates they stick around
and the counter indicates "in the past"
returning a cookie for the operation.
2) hostapd updates each link's beacon now including the UHR Parameters
Update element(s), for each currently ongoing update it includes -
indexed by the cookie - a list of offsets where the counters are
updated.
Thing is that this depends on the operation - CSA will already need
the post-switch beacon template so the flow can continue without
involving hostapd, hostapd may however need to update the beacon in
the interim (both pre- and post-switch ones!) and need to refer
again to where the counters are filled in ...
Either way, it feels like we've reached the end of where the current
design with CSA and BSS color updates will take us. Lorenzo already gave
up and put the parsing into the driver to find the offsets, but I
personally think that's very inflexible.
Some operations such as link removal may not need to have perfect
timing, but some others like CSA really do want the updates to happen at
the precise moment.
In some way, it's almost good that we haven't completed the WiFi7 part
since the UHR part throws another curveball with the counters
decrementing below zero - for past updates - in a sense.
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-13 10:11 ` Johannes Berg
@ 2026-02-13 10:26 ` Johannes Berg
2026-02-16 17:39 ` Harshitha Prem
2026-02-16 17:17 ` Harshitha Prem
1 sibling, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-02-13 10:26 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih
On Fri, 2026-02-13 at 11:11 +0100, Johannes Berg wrote:
> Hi Harshita,
>
> > > Should we add a separate netlink attribute for the UHR operation, which
> > > hostapd would fill with the _full_ data like it appears in association
> > > response etc.?
> > >
> > > That way, hostapd doesn't need to build a separate data/attribute
> > > structure but can just use hostapd_eid_uhr_operation(..., false) for it.
> > >
> > > An alternative would be to add more attributes for everything, but it's
> > > probably more complicated on both sides?
>
> > Thank you for the suggestions.
> >
> > We feel that using separate nested attributes for each feature is the better approach, as this allows us to reuse the attributes for the Enhanced BSS Parameter Critical Update procedure, where similar information is carried in the UHR parameters update element.
>
> Heh, I'll admit I'm surprised - I'm usually the one advocating for
> finer-grained attributes, and here I didn't ;-)
Wait, so I wrote a lot and forgot to circle back to this question ...
Basically I think that it's not going to be useful to split it up. I
have no objections to it, but it complicates the code (especially in
hostapd) quite a bit, because it's going to be either
1) include each thing (NPCA, DBE, ...) in its own attribute, so that
e.g. NPCA would be 4 or 6 bytes per spec format, but then we need
separate validation for each in nl80211
2) we really break it all down to each individual value, so e.g. NPCA
would have separate attributes for minimum duration threshold,
switch and switch back delay, initial QSRC and a MOPLEN flag; this
is a bit easier to capture in a policy, but a LOT of parameters
overall.
The thing - and why I wrote so much - is that we basically only need a
single current, and in the case of updates additionally a single post-
update, UHR operation.
So unless we're going to completely design away from beacon templates
and create an API where including the UHR Parameters Update element is
fully the firmware's (or driver's) responsibility across all the
different frame types, then the split isn't really needed. And even if
we _do_ design it completely that way, giving the post-update UHR
operation and comparing to the pre-update one isn't a huge stretch for a
design that just required fully rebuilding all the frames (parsing all
the way into fragmented elements and putting them back together in a
completely new way, including re-fragmenting elements and subelements
etc. which all sounds very messy to me.)
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-13 10:11 ` Johannes Berg
2026-02-13 10:26 ` Johannes Berg
@ 2026-02-16 17:17 ` Harshitha Prem
2026-02-17 10:05 ` Johannes Berg
1 sibling, 1 reply; 23+ messages in thread
From: Harshitha Prem @ 2026-02-16 17:17 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih
On 2/13/2026 3:41 PM, Johannes Berg wrote:
> Hi Harshita,
>
>>> Should we add a separate netlink attribute for the UHR operation, which
>>> hostapd would fill with the _full_ data like it appears in association
>>> response etc.?
>>>
>>> That way, hostapd doesn't need to build a separate data/attribute
>>> structure but can just use hostapd_eid_uhr_operation(..., false) for it.
>>>
>>> An alternative would be to add more attributes for everything, but it's
>>> probably more complicated on both sides?
>
>> Thank you for the suggestions.
>>
>> We feel that using separate nested attributes for each feature is the better approach, as this allows us to reuse the attributes for the Enhanced BSS Parameter Critical Update procedure, where similar information is carried in the UHR parameters update element.
>
> Heh, I'll admit I'm surprised - I'm usually the one advocating for
> finer-grained attributes, and here I didn't ;-)
>
>> While this approach is slightly more verbose, we believe it offers better extensibility for the future.
>
> Does it actually, though?
>
> I had sort of expected hostapd to add the UHR Parameters Update element
> to the beacons, and configure some way of having firmware set the
> countdown ("Countdown Timer"). Turns out this is not only in beacons but
> also in probe response, (re)association response and (UHR? [1]) link
> reconfiguration response frames.
>
> [1] the spec says "Link Reconfiguration Response" but I think it should
> say "UHR Link Reconfiguration Response"
>
> But I guess this is going to end up a continuation of the previous wifi7
> discussion from last year [2] which hadn't really completed. And Lorenzo
> just posted another thing on this [3].
>
> [2] https://lore.kernel.org/linux-wireless/20250717045540.27208-1-aditya.kumar.singh@oss.qualcomm.com/
> [3] https://lore.kernel.org/linux-wireless/20260212-mt7996-link-reconf-v1-0-2b110340d6c4@kernel.org/
>
>
> So I think maybe we need to figure out how we will do all of this first?
>
> Naively, I would've said ... something stupid. I'm reading the spec as
> I'm writing this ;-) The UHR Parameters Update element is actually
> included in all the beacons across the entire AP MLD.
>
> Maybe we need to take a step back from our previous discussion as well,
> and introduce a broader concept here?
>
>
> I could imagine, for example, something where you say in the nl80211 API
> some variation of
>
> 1) Let's start a new update operation ("NL80211_CMD_START_MLD_BSS_UPDATE"),
> I guess already with some parameters saying:
> - the updated affiliated AP (link)
> - the number of beacon intervals you want to do it for
> - the post-update UHR operation (?)
> (or the new channel if it's CSA? etc.?)
>
> - maybe - more critical if we use it for CSA - already the beacon
> templates for all the links? with all the things I say in (2)
> below, but that's more complex maybe?
>
> the reason I say this is that there's a difference here in how
> the counter is done - for CSA etc. the things have to disappear
> from the beacon immediately, for UHR updates they stick around
> and the counter indicates "in the past"
>
> returning a cookie for the operation.
>
> 2) hostapd updates each link's beacon now including the UHR Parameters
> Update element(s), for each currently ongoing update it includes -
> indexed by the cookie - a list of offsets where the counters are
> updated.
>
> Thing is that this depends on the operation - CSA will already need
> the post-switch beacon template so the flow can continue without
> involving hostapd, hostapd may however need to update the beacon in
> the interim (both pre- and post-switch ones!) and need to refer
> again to where the counters are filled in ...
>
>
Hi Johannes,
This approach looks well suited to handling overlapping update scenarios.
To make sure I understand it correctly, I’d like to walk through a
concise example where a UHR critical update and a CSA overlap on link 0
of a 3‑link AP MLD.
t1: A UHR critical update is triggered. hostapd sends
NL80211_CMD_START_MLD_BSS_UPDATE with an advanced notification (5
TBTTs), post‑interval (5 TBTTs), and the beacon template with UHR
parameters update element for link 0. Timers start in mac80211, and
hostapd receives cookie X.
t2: hostapd sends NL80211_CMD_UPDATE_AP with cookie X and offset where
the counter is updated for link 1 (and then link 2).
the countdown values would be handled in mac80211 with the offset
mentioned similar to ieee80211_set_beacon_cntdwn().
t3: Before the UHR advanced interval completes, a CSA is triggered (due
to radar or user‑initiated). Another NL80211_CMD_START_MLD_BSS_UPDATE is
issued with CSA countdown 5, including CSA and after beacon templates.
The after template carries cookie X and the offset. Since UHR CU is
already in progress, hostapd could also include an updated parameters
update element. Also, an updated UHR operation element which can be
modified in after beacon template if CSA finishes after the UHR CU
advance interval. (why to provide the UHR operation element separately
is because the advance notification can be before or after the CSA
finalize).
hostapd then receives cookie Y for the CSA.
t4: hostapd issues NL80211_CMD_UPDATE_AP for link 1 with cookie X (UHR
offset) and cookie Y (CSA offset), followed by link 2.
t5: When the UHR advanced notification interval expires, the CSA after
beacon template is updated to reflect the new operation element by
mac80211 or getting it from hostapd.
t6: Once the CSA completes, the driver updates the after beacon template
along with updated counter for UHR parameter update element. (since it
will be in the post notification interval)
t7: When the UHR post‑interval completes, the UHR parameter update
element is removed. Since offsets are known, the driver can remove it
from the latest beacon template.
Please let me know if this sequence aligns with the intended behavior,
or if I’m missing any edge cases.
Also, CSA during NPCA parameter update seems very tricky as the channel
information advertised in NPCA may not be relevant when there is a CSA,
perhaps stop the NPCA CU (still spec has not mentioned any details on
the abort scenario though)
Even, in case of link removal during UHR CU update, we need to stop the
UHR CU.
Furthermore, the UHR capabilities carries the advance notification
interval, post notification (i guess yet to be added in element as I
see it in "37-8—Enhanced Critical Updates Mechanism") and update
indication in TIM interval, these intervals can be sent in the START_AP.
I assume, these intervals cannot be modified dynamically as these are
exchanged in assoc response (Will reconfirm again).
For beacon offloaded drivers, NL80211_CMD_START_MLD_BSS_UPDATE can be as
a trigger. May be we can add the updated UHR operation element in case
of UHR. So, that firmware/driver handle the timers as well as
constructing the elements since "the encoding of fields in Mode specific
parameters for any feature say DPS/NPCA/DBE etc.. is same as the
corresponding field in operation parameters field for that feature" (but
i see bit variations for NPCA between both)
I guess, this means the proposal of having a separate netlink attribute
for the UHR operation with _full_ data like is much useful.
Still, we are thinking through on all the other cases of wifi-7/CCA etc..
> Either way, it feels like we've reached the end of where the current
> design with CSA and BSS color updates will take us. Lorenzo already gave
> up and put the parsing into the driver to find the offsets, but I
> personally think that's very inflexible.
>
> Some operations such as link removal may not need to have perfect
> timing, but some others like CSA really do want the updates to happen at
> the precise moment.
>
> In some way, it's almost good that we haven't completed the WiFi7 part
> since the UHR part throws another curveball with the counters
> decrementing below zero - for past updates - in a sense.
>
>
> johannes
Thanks in advance for your time and clarification.
Regards,
Harshitha
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-13 10:26 ` Johannes Berg
@ 2026-02-16 17:39 ` Harshitha Prem
0 siblings, 0 replies; 23+ messages in thread
From: Harshitha Prem @ 2026-02-16 17:39 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih
On 2/13/2026 3:56 PM, Johannes Berg wrote:
> On Fri, 2026-02-13 at 11:11 +0100, Johannes Berg wrote:
>> Hi Harshita,
>>
>>>> Should we add a separate netlink attribute for the UHR operation, which
>>>> hostapd would fill with the _full_ data like it appears in association
>>>> response etc.?
>>>>
>>>> That way, hostapd doesn't need to build a separate data/attribute
>>>> structure but can just use hostapd_eid_uhr_operation(..., false) for it.
>>>>
>>>> An alternative would be to add more attributes for everything, but it's
>>>> probably more complicated on both sides?
>>
>>> Thank you for the suggestions.
>>>
>>> We feel that using separate nested attributes for each feature is the better approach, as this allows us to reuse the attributes for the Enhanced BSS Parameter Critical Update procedure, where similar information is carried in the UHR parameters update element.
>>
>> Heh, I'll admit I'm surprised - I'm usually the one advocating for
>> finer-grained attributes, and here I didn't ;-)
>
> Wait, so I wrote a lot and forgot to circle back to this question ...
>
> Basically I think that it's not going to be useful to split it up. I
> have no objections to it, but it complicates the code (especially in
> hostapd) quite a bit, because it's going to be either
>
> 1) include each thing (NPCA, DBE, ...) in its own attribute, so that
> e.g. NPCA would be 4 or 6 bytes per spec format, but then we need
> separate validation for each in nl80211
>
> 2) we really break it all down to each individual value, so e.g. NPCA
> would have separate attributes for minimum duration threshold,
> switch and switch back delay, initial QSRC and a MOPLEN flag; this
> is a bit easier to capture in a policy, but a LOT of parameters
> overall.
>
> The thing - and why I wrote so much - is that we basically only need a
> single current, and in the case of updates additionally a single post-
> update, UHR operation.
>
> So unless we're going to completely design away from beacon templates
> and create an API where including the UHR Parameters Update element is
> fully the firmware's (or driver's) responsibility across all the
> different frame types, then the split isn't really needed. And even if
> we _do_ design it completely that way, giving the post-update UHR
> operation and comparing to the pre-update one isn't a huge stretch for a
> design that just required fully rebuilding all the frames (parsing all
> the way into fragmented elements and putting them back together in a
> completely new way, including re-fragmenting elements and subelements
> etc. which all sounds very messy to me.)
>
For beacon‑offloaded drivers, we initially thought a separate NL command
for the UHR critical update, with dedicated nested attributes, would
make it easier to trigger the firmware/driver. At the same time, we’ve
been having ongoing discussions around CSA and the UHR critical update,
especially around the overlap with post‑beacon template handling in CSA.
Thank you. The proposal to introduce CMD_START_MLD_BSS_UPDATE gave us a
different perspective and helped us rethink how best to handle this.
> johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-16 17:17 ` Harshitha Prem
@ 2026-02-17 10:05 ` Johannes Berg
2026-02-24 11:01 ` Harshitha Prem
0 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-02-17 10:05 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih
Hi,
> This approach looks well suited to handling overlapping update scenarios.
>
> To make sure I understand it correctly, I’d like to walk through a
> concise example where a UHR critical update and a CSA overlap on link 0
> of a 3‑link AP MLD.
Well, so honestly, you should probably treat what I write as equivalent
to a paper napkin sketch during a night of drinking ;-)
I surely didn't think it totally through. I was just trying to
illustrate that I feel like we need to have some kind of new design for
all these overlapping updates (CSA, BSS color change, EHT and UHR
critical updates?) than the piecemeal design we have now.
And, importantly, that perhaps I think that this means we need to have
"post-update UHR operation" more than the individual UHR operation
updates broken out - which after all is where we started from.
> t1: A UHR critical update is triggered. hostapd sends
> NL80211_CMD_START_MLD_BSS_UPDATE with an advanced notification (5
> TBTTs), post‑interval (5 TBTTs), and the beacon template with UHR
> parameters update element for link 0. Timers start in mac80211, and
> hostapd receives cookie X.
Don't know if timers would be really in mac80211 - I guess if the driver
pulls each individual beacon then mac80211 could handle the countdown,
but otherwise this might just be given to firmware.
Also I guess the counter would have offset(s) from the start, i.e. the
beacon template would be in this operation already. Or maybe even beacon
updates for multiple links? Don't know if that really matters much
either way.
> t2: hostapd sends NL80211_CMD_UPDATE_AP with cookie X and offset where
> the counter is updated for link 1 (and then link 2).
>
> the countdown values would be handled in mac80211 with the offset
> mentioned similar to ieee80211_set_beacon_cntdwn().
Sort of, yeah. I imagined that the CMD_UPDATE_AP would come with a list
of "cookie X: { offset X_1, offset X_2 }, cookie Y: { offset Y_1 }" or
something like that.
> t3: Before the UHR advanced interval completes, a CSA is triggered (due
> to radar or user‑initiated). Another NL80211_CMD_START_MLD_BSS_UPDATE is
> issued with CSA countdown 5, including CSA and after beacon templates.
> The after template carries cookie X and the offset. Since UHR CU is
> already in progress, hostapd could also include an updated parameters
> update element.
Would have to, I think? I think in your example it's unlikely the _after
CSA_ template still has cookie X / offset(s) X_n, since you only had 10
beacon intervals overall for the UHR critical update and CSA might be
longer, but we could also imagine the UHR critical update was advertised
for a longer time.
> Also, an updated UHR operation element which can be
> modified in after beacon template if CSA finishes after the UHR CU
> advance interval. (why to provide the UHR operation element separately
> is because the advance notification can be before or after the CSA
> finalize).
> hostapd then receives cookie Y for the CSA.
This could get trickier than I imagined - you now have three periods of
time:
- now
- after CSA but before UHR update
- after UHR update
and actually all three might need different UHR operation, since the CSA
can change the bandwidth and therefore e.g. DBE/NPCA. The intermediate
period ("after CSA but before UHR update") can be captured by the CSA
operation (given a template/UHR operation for after) easily.
But I was imagining we capture all this in the operations already, so I
guess to do that we would need a "NL80211_CMD_MODIFY_MLD_BSS_UPDATE"
command that takes the cookie and updates the post-operation values, so
that the changes due to the CSA could be taken into account in the
previously started UHR update.
FWIW, I was also kind of imagining that we'd design this combined update
command in a way that it replaces the CSA and color change commands,
handles the proposed link removal thing from Lorenzo, and then we don't
need to handle overlapping operations of all kinds, just of this new
kind that can do many different things. Not a huge difference though
since CSA/CCA would map to a subset of the new "thing".
> t4: hostapd issues NL80211_CMD_UPDATE_AP for link 1 with cookie X (UHR
> offset) and cookie Y (CSA offset), followed by link 2.
Not sure really about this. You also need new beacon templates for the
two periods I mentioned above ("after CSA but before UHR update" and
"after UHR update").
I think in my head the beacon templates for post-operations for all
links would've been handle by NL80211_CMD_START_MLD_BSS_UPDATE and, as I
discovered above, NL80211_CMD_MODIFY_MLD_BSS_UPDATE, rather than by
NL80211_CMD_UPDATE_AP.
I was, however, imagining that updates to the *current* template could
still happen via NL80211_CMD_UPDATE_AP, so - let's build a side branch
here first, e.g.:
- we start UHR update to happen in 10 beacon intervals
- this comes with a post-update template and post-update UHR operation
- something else happens, e.g. HT mixed mode needs updating
in this case, NL80211_CMD_UPDATE_AP could happen with the cookie(s)
and offset(s) for the _current_ beacon
Although I think perhaps this ends up being racy, and in fact hostapd
would need to use a hypothetical NL80211_CMD_MODIFY_MLD_BSS_UPDATE again
to update the link's beacon for HT mixed mode for all of the ongoing BSS
updates and their post beacon(s)?
Anyway, back to your scenario:
> t5: When the UHR advanced notification interval expires, the CSA after
> beacon template is updated to reflect the new operation element by
> mac80211 or getting it from hostapd.
Yeah so that's also an option, if we think it'd work / not be too racy?
As I wrote, I was thinking CSA becomes another MLD BSS update and then
the post-CSA template is already given there.
> t6: Once the CSA completes, the driver updates the after beacon template
> along with updated counter for UHR parameter update element. (since it
> will be in the post notification interval)
>
>
> t7: When the UHR post‑interval completes, the UHR parameter update
> element is removed. Since offsets are known, the driver can remove it
> from the latest beacon template.
I wasn't really imagining the driver would remove it I guess, but rather
a post-update template would be provided already.
Ah! I think you were maybe thinking "this is the offset where to
insert/remove the UHR Parameter Update element", and I was thinking
"this is the offset of the counter" so that we could also put it into
the RNR etc.?
> Please let me know if this sequence aligns with the intended behavior,
> or if I’m missing any edge cases.
>
> Also, CSA during NPCA parameter update seems very tricky as the channel
> information advertised in NPCA may not be relevant when there is a CSA,
> perhaps stop the NPCA CU (still spec has not mentioned any details on
> the abort scenario though)
Right, it's a bit tricky all over for concurrency scenarios. I hear that
two UHR critical updates concurrently are not going to be allowed
though.
> Even, in case of link removal during UHR CU update, we need to stop the
> UHR CU.
Well if the link is removed then it probably isn't relevant to update it
any more anyway?
> Furthermore, the UHR capabilities carries the advance notification
> interval, post notification (i guess yet to be added in element as I
> see it in "37-8—Enhanced Critical Updates Mechanism") and update
> indication in TIM interval, these intervals can be sent in the START_AP.
> I assume, these intervals cannot be modified dynamically as these are
> exchanged in assoc response (Will reconfirm again).
Yeah, I agree, I would tend to think these are fixed.
> For beacon offloaded drivers, NL80211_CMD_START_MLD_BSS_UPDATE can be as
> a trigger. May be we can add the updated UHR operation element in case
> of UHR. So, that firmware/driver handle the timers as well as
> constructing the elements since "the encoding of fields in Mode specific
> parameters for any feature say DPS/NPCA/DBE etc.. is same as the
> corresponding field in operation parameters field for that feature" (but
> i see bit variations for NPCA between both)
The bit variants are going to get fixed, it's just not entirely clear
yet which way around.
> I guess, this means the proposal of having a separate netlink attribute
> for the UHR operation with _full_ data like is much useful.
OK. We can always also just parse it out anyway, just that element
(contents) isn't really too difficult to get at.
> Still, we are thinking through on all the other cases of wifi-7/CCA etc..
Of course.
Thanks,
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-17 10:05 ` Johannes Berg
@ 2026-02-24 11:01 ` Harshitha Prem
2026-03-06 12:43 ` Johannes Berg
0 siblings, 1 reply; 23+ messages in thread
From: Harshitha Prem @ 2026-02-24 11:01 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih,
Manish Dharanenthiran
On 2/17/2026 3:35 PM, Johannes Berg wrote:
Hi Johannes,
Thank you very much for your patience, and apologies for the delayed
response. We spent some time discussing this internally and wanted to
follow up thoughtfully.
> Hi,
>
>> This approach looks well suited to handling overlapping update scenarios.
>>
>> To make sure I understand it correctly, I’d like to walk through a
>> concise example where a UHR critical update and a CSA overlap on link 0
>> of a 3‑link AP MLD.
>
> Well, so honestly, you should probably treat what I write as equivalent
> to a paper napkin sketch during a night of drinking ;-)
>
> I surely didn't think it totally through. I was just trying to
> illustrate that I feel like we need to have some kind of new design for
> all these overlapping updates (CSA, BSS color change, EHT and UHR
> critical updates?) than the piecemeal design we have now.
>
> And, importantly, that perhaps I think that this means we need to have
> "post-update UHR operation" more than the individual UHR operation
> updates broken out - which after all is where we started from.
>
>
>> t1: A UHR critical update is triggered. hostapd sends
>> NL80211_CMD_START_MLD_BSS_UPDATE with an advanced notification (5
>> TBTTs), post‑interval (5 TBTTs), and the beacon template with UHR
>> parameters update element for link 0. Timers start in mac80211, and
>> hostapd receives cookie X.
>
> Don't know if timers would be really in mac80211 - I guess if the driver
> pulls each individual beacon then mac80211 could handle the countdown,
> but otherwise this might just be given to firmware.
Yes, agreed.
>
> Also I guess the counter would have offset(s) from the start, i.e. the
> beacon template would be in this operation already. Or maybe even beacon
> updates for multiple links? Don't know if that really matters much
> either way.
>
>> t2: hostapd sends NL80211_CMD_UPDATE_AP with cookie X and offset where
>> the counter is updated for link 1 (and then link 2).
>>
>> the countdown values would be handled in mac80211 with the offset
>> mentioned similar to ieee80211_set_beacon_cntdwn().
>
> Sort of, yeah. I imagined that the CMD_UPDATE_AP would come with a list
> of "cookie X: { offset X_1, offset X_2 }, cookie Y: { offset Y_1 }" or
> something like that.
>
>> t3: Before the UHR advanced interval completes, a CSA is triggered (due
>> to radar or user‑initiated). Another NL80211_CMD_START_MLD_BSS_UPDATE is
>> issued with CSA countdown 5, including CSA and after beacon templates.
>> The after template carries cookie X and the offset. Since UHR CU is
>> already in progress, hostapd could also include an updated parameters
>> update element.
>
> Would have to, I think? I think in your example it's unlikely the _after
> CSA_ template still has cookie X / offset(s) X_n, since you only had 10
> beacon intervals overall for the UHR critical update and CSA might be
> longer, but we could also imagine the UHR critical update was advertised
> for a longer time.
>
>> Also, an updated UHR operation element which can be
>> modified in after beacon template if CSA finishes after the UHR CU
>> advance interval. (why to provide the UHR operation element separately
>> is because the advance notification can be before or after the CSA
>> finalize).
>> hostapd then receives cookie Y for the CSA.
>
> This could get trickier than I imagined - you now have three periods of
> time:
>
> - now
> - after CSA but before UHR update
> - after UHR update
>
> and actually all three might need different UHR operation, since the CSA
> can change the bandwidth and therefore e.g. DBE/NPCA. The intermediate
> period ("after CSA but before UHR update") can be captured by the CSA
> operation (given a template/UHR operation for after) easily.
>
> But I was imagining we capture all this in the operations already, so I
> guess to do that we would need a "NL80211_CMD_MODIFY_MLD_BSS_UPDATE"
> command that takes the cookie and updates the post-operation values, so
> that the changes due to the CSA could be taken into account in the
> previously started UHR update.
The idea of introducing an NL80211_CMD_MODIFY_MLD_BSS_UPDATE command
makes a lot of sense to us. In cases where
NL80211_CMD_START_MLD_BSS_UPDATE is already in progress, having a modify
path to update the current beacon seems easier to reason about and
manage. From that perspective, a pairing such as
NL80211_CMD_START_MLD_BSS_UPDATE together with
NL80211_CMD_MODIFY_MLD_BSS_UPDATE feels quite natural.
>
> FWIW, I was also kind of imagining that we'd design this combined update
> command in a way that it replaces the CSA and color change commands,
> handles the proposed link removal thing from Lorenzo, and then we don't
> need to handle overlapping operations of all kinds, just of this new
> kind that can do many different things. Not a huge difference though
> since CSA/CCA would map to a subset of the new "thing".
>
[...]
Please find below the envisioned design flow for the UHR CU and CSA
intersection.
Hostapd (User) mac80211 (Kernel) Air / Station
| | |
1 | CMD_START_AP [Adv Notif, | |
| Post Notif, Upd Int] | |
|-------------------------->| |
| | |
2 | CMD_START_MLD_BSS_UPDATE | |
| [Link:0, CurTmpl+Offset | |
| (All), Timer, PostTmpl | |
| (All), Type:UHR_CU, | |
| Post UHR Op element] | |
|-------------------------->| |
| | |
3 | | [Set Tmpl, Timer: Adv=10, |
| | Post=10, TIM Update] |
| | |
4 | Cookie X | |
|<--------------------------| |
| | |
5 | EVENT_UHR_CU (CU_START) | |
|<--------------------------| |
| | |
6 | | Beacons: 10, 9, 8... |
| |-------------------------->|
| | |
7 | [CSA Triggered: Link0, | |
| Count 10. Sees Cookie X] | |
| | |
8 | CMD_START_MLD_BSS_UPDATE | |
| [Type:CSA, Link:0, Tmpls, | |
| Cookie X + Offset, | |
| Post Tmpl (No UHR ele)] | |
|-------------------------->| |
| | |
9 | Cookie Y | |
|<--------------------------| |
| | |
10 | CMD_CH_SWITCH_STARTED_ | |
| NOTIFY | |
|<--------------------------| |
| | |
11 | | Beacons: X=7, Y=10 |
| |-------------------------->|
| | |
12 | CMD_MODIFY_MLD_BSS_UPDATE | |
| (Cookie X Post Tmpl w/ | |
| Chan Info, 3 Links) | |
|-------------------------->| |
| | |
13 | | Beacons: X=1, Y=4 |
| |-------------------------->|
| | |
14 | | [X=0: Modify Cur Tmpl |
| | w/ Post UHR Op element] |
| | |
15 | EVENT_UHR_CU | |
| (CU_ADVANCE_COMPLETE) | |
|<--------------------------| |
| | |
16 | CMD_MODIFY_MLD_BSS_UPDATE | |
| (Cookie Y Post Tmpl w/ | |
| UHR Op + Param elements) | |
|-------------------------->| |
| | |
17 | | Beacons: X=127, Y=3 |
| |-------------------------->|
| | |
18 | | Beacons: X=128/129, |
| | Y=2/1 (CSA done) |
| |-------------------------->|
| | |
19 | | [Y=0: Set Post Tmpl |
| | cookie Y, UHR Param |
| | Off, X=130] |
| | |
20 | CMD_CH_SWITCH_NOTIFY | |
|<--------------------------| |
| | |
21 | CMD_MLD_BSS_UPDATE_NOTIFY | |
| (Complete Cookie Y) | |
|<--------------------------| |
| | |
22 | CMD_MODIFY_MLD_BSS_UPDATE | |
| (Cookie X Post Tmpl w/ | |
| UpdatedChan Info) | |
|-------------------------->| |
| | |
23 | | Beacons Continue... |
| |-------------------------->|
| | |
24 | | Probe Request |
| |<--------------------------|
| | [Fetch TBTT] |
| send_mgmt (TBTT) | |
|<--------------------------| |
| | |
25 | send_mgmt (Probe Resp | |
| w/ TBTT in UHR Param) | |
|-------------------------->| |
| | |
26 | | [Post Notif Complete: |
| | Set Post Tmpl Cookie X] |
| | |
27 | EVENT_UHR_CU | |
| (CU_POST_NOTIF_COMPLETE) | |
|<--------------------------| |
| | |
28 | | [Continue w/ Updated TIM] |
| | |
29 | EVENT_UHR_CU | |
| (CU_SESSION_END) | |
|<--------------------------| |
| | |
The diagram uses a number of abbreviations, so an explanation is
provided below for clarity.
Step 2 – NL80211_CMD_START_MLD_BSS_UPDATE
This would include:
1. The affected link ID
2. Current beacon templates for all links, along with the offsets
where counters need to be updated.
3. Post‑beacon templates for all links
4. The post‑UHR operation element
5. Countdown values
6. Type: UHR CU
Step 8 – NL80211_CMD_START_MLD_BSS_UPDATE
This would include:
1. The affected link ID
2. Current beacon templates for all links, along with
the offsets where counters need to be updated
3. Cookie X – { offset_A }
4. Post‑beacon templates for all links
5. Countdown values
6. Type: CSA
Step 14
The driver/firmware can update the current beacon template with
the post‑UHR operation element. Until the advance notification interval
completes, the UHR operation element would not yet be updated.
Step 12 & Step 22
These steps are somewhat duplicate, but keeping the latest modified
state once hostapd receives NL80211_CMD_MLD_BSS_UPDATE_NOTIFY_COMPLETE
seems reasonable.
Step 24
Reporting the TBTT count back to hostapd for attribute types such as
UHR_CU, LINK_REMOVE, etc.
Cookies act as identifiers for both the post‑beacon template and the
associated countdown values.
A few potential concerns to consider:
1. Carrying both the current and post‑beacon templates for all
affiliated links of an MLD might make the NL message fairly large. we
are not sure how well that fits with existing practice, since multipart
handling seems to be used mostly for dump commands. As an alternate, can
we have multiple commands with message id and reassemble it?
2. There may be a small sequencing aspect worth thinking through. For
example, if a UHR_CU operation is close to completion and we are about
to apply its post‑beacon template, but before hostapd processes
EVENT_UHR_CU with CU_POST_NOTIF_COMPLETE it issues a
START_MLD_BSS_UPDATE for CSA, we could potentially end up using an
unexpected version of the current beacon template. This may already be
handled by the existing flow, but it seemed worth calling out for
completeness.
Thanks,
Harshitha
>
> Thanks,
> johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-02-24 11:01 ` Harshitha Prem
@ 2026-03-06 12:43 ` Johannes Berg
2026-03-12 5:49 ` Harshitha Prem
0 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-03-06 12:43 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih,
Manish Dharanenthiran
Hi,
> Thank you very much for your patience, and apologies for the delayed
> response. We spent some time discussing this internally and wanted to
> follow up thoughtfully.
Hey, no worries, I'm clearly also not always able to respond quickly, a
lot of things are going on at any given time ...
> > This could get trickier than I imagined - you now have three periods of
> > time:
> >
> > - now
> > - after CSA but before UHR update
> > - after UHR update
> >
> > and actually all three might need different UHR operation, since the CSA
> > can change the bandwidth and therefore e.g. DBE/NPCA. The intermediate
> > period ("after CSA but before UHR update") can be captured by the CSA
> > operation (given a template/UHR operation for after) easily.
> >
> > But I was imagining we capture all this in the operations already, so I
> > guess to do that we would need a "NL80211_CMD_MODIFY_MLD_BSS_UPDATE"
> > command that takes the cookie and updates the post-operation values, so
> > that the changes due to the CSA could be taken into account in the
> > previously started UHR update.
>
> The idea of introducing an NL80211_CMD_MODIFY_MLD_BSS_UPDATE command
> makes a lot of sense to us. In cases where
> NL80211_CMD_START_MLD_BSS_UPDATE is already in progress, having a modify
> path to update the current beacon seems easier to reason about and
> manage. From that perspective, a pairing such as
> NL80211_CMD_START_MLD_BSS_UPDATE together with
> NL80211_CMD_MODIFY_MLD_BSS_UPDATE feels quite natural.
Yeah, maybe, then it wouldn't ever really go to a normal SET_BEACON any
more, maybe?
I was thinking more for not having to change all the code in hostapd at
a given time, it might be more plausible to still allow SET_BEACON and
just keep giving the counter offsets etc., in case e.g. something "old"
like short-preamble changes.
But clearly hostapd would have to manage those offsets etc. anyway, so
perhaps there's really not going to be any reason to support SET_BEACON
while updates are in progress. But in that case I'd probably argue it
(SET_BEACON) should be disallowed by the kernel, to catch errors.
> Please find below the envisioned design flow for the UHR CU and CSA
> intersection.
>
> Hostapd (User) mac80211 (Kernel) Air / Station
> | | |
> 1 | CMD_START_AP [Adv Notif, | |
> | Post Notif, Upd Int] | |
> |-------------------------->| |
> | | |
> 2 | CMD_START_MLD_BSS_UPDATE | |
> | [Link:0, CurTmpl+Offset | |
> | (All), Timer, PostTmpl | |
> | (All), Type:UHR_CU, | |
> | Post UHR Op element] | |
> |-------------------------->| |
> | | |
> 3 | | [Set Tmpl, Timer: Adv=10, |
> | | Post=10, TIM Update] |
> | | |
> 4 | Cookie X | |
> |<--------------------------| |
> | | |
> 5 | EVENT_UHR_CU (CU_START) | |
> |<--------------------------| |
Not sure what the event does really, at this point? It kind of starts
immediately anyway, no? But it also doesn't matter for this high-level
discussion.
> | | |
> 6 | | Beacons: 10, 9, 8... |
> | |-------------------------->|
> | | |
> 7 | [CSA Triggered: Link0, | |
> | Count 10. Sees Cookie X] | |
> | | |
> 8 | CMD_START_MLD_BSS_UPDATE | |
> | [Type:CSA, Link:0, Tmpls, | |
> | Cookie X + Offset, | |
> | Post Tmpl (No UHR ele)] | |
> |-------------------------->| |
Not sure I understand the "No UHR ele" part - surely the post template
still has UHR? Or did you mean "UHR parameter update"?
Not that there's a race here - "Sees Cookie X", but who knows the cookie
X is even still valid?
But I think we can pretty much solve that racy by marking a cookie X
invalid in the kernel (or even FW?) and rejecting the new
CMD_START_MLD_BSS_UPDATE operation that still refers to Cookie X -
hostapd would just have to know about that specific rejection reason (I
guess netlink extended status would point to the wrong cookie attr or
so) and then rebuild the templates without taking the update with cookie
X into account.
But with that race aside, yeah, seems reasonable.
> | | |
> 9 | Cookie Y | |
> |<--------------------------| |
> | | |
> 10 | CMD_CH_SWITCH_STARTED_ | |
> | NOTIFY | |
> |<--------------------------| |
I don't know - this was started by CMD_START_MLD_BSS_UPDATE too, so
probably should be some generic notification about it, or like I said
above, maybe isn't even needed at all?
(I think a good chunk of the channel switch notification is code
unification between client and AP, does the AP even use the start
notification?)
> | | |
> 11 | | Beacons: X=7, Y=10 |
> | |-------------------------->|
> | | |
> 12 | CMD_MODIFY_MLD_BSS_UPDATE | |
> | (Cookie X Post Tmpl w/ | |
> | Chan Info, 3 Links) | |
> |-------------------------->| |
> | | |
> 13 | | Beacons: X=1, Y=4 |
> | |-------------------------->|
> | | |
> 14 | | [X=0: Modify Cur Tmpl |
> | | w/ Post UHR Op element] |
> | | |
> 15 | EVENT_UHR_CU | |
> | (CU_ADVANCE_COMPLETE) | |
> |<--------------------------| |
That should have Cookie X somehow as an attribute, of course :)
Really all the events should have, but here clearly you meant X since
the other operation isn't done yet.
> | | |
> 16 | CMD_MODIFY_MLD_BSS_UPDATE | |
> | (Cookie Y Post Tmpl w/ | |
> | UHR Op + Param elements) | |
> |-------------------------->| |
What's this doing? Didn't we have post-Y templates already in the prior
command? Otherwise isn't this quite a bit racy? Though I guess we have a
whole beacon interval for hostapd to update everything, which really
ought to be sufficient (though hostapd may need to stop being single-
threaded ...)
> | | |
> 17 | | Beacons: X=127, Y=3 |
> | |-------------------------->|
> | | |
> 18 | | Beacons: X=128/129, |
> | | Y=2/1 (CSA done) |
> | |-------------------------->|
> | | |
> 19 | | [Y=0: Set Post Tmpl |
> | | cookie Y, UHR Param |
> | | Off, X=130] |
> | | |
> 20 | CMD_CH_SWITCH_NOTIFY | |
> |<--------------------------| |
(similar comment as above wrt. what notifications are needed)
> | | |
> 21 | CMD_MLD_BSS_UPDATE_NOTIFY | |
> | (Complete Cookie Y) | |
> |<--------------------------| |
Right.
> | | |
> 22 | CMD_MODIFY_MLD_BSS_UPDATE | |
> | (Cookie X Post Tmpl w/ | |
> | UpdatedChan Info) | |
> |-------------------------->| |
"X post" is a bit misleading ... it's still ongoing, because it's
changed now but not really _after_ the update X, it's still advertising
the update X has happened. So in some way the flow is still there.
But it does point out that we need basically three (additional)
templates for a UHR update:
- announcing the upcoming update
- announcing the update happened
- back to normal state afterwards
Which, perhaps, is indeed a bit too much to offload all to the kernel
even in terms of the API, because if you mix another operation in (here
in your example the channel switch Y), you end up with even more
templates ...
So I'm coming around to the idea that you have a notification and
hostapd has to update the templates at that point.
> | | |
> 23 | | Beacons Continue... |
> | |-------------------------->|
> | | |
> 24 | | Probe Request |
> | |<--------------------------|
> | | [Fetch TBTT] |
> | send_mgmt (TBTT) | |
> |<--------------------------| |
> | | |
> 25 | send_mgmt (Probe Resp | |
> | w/ TBTT in UHR Param) | |
> |-------------------------->| |
> | | |
Not sure I follow this part regarding the "TBTT" thing. Are you saying
the RX of the probe request would have a TBTT attached to it? But does
it matter, what matters is the TX? And that's probably impossible to get
right?
> The diagram uses a number of abbreviations, so an explanation is
> provided below for clarity.
Oops, sorry, didn't see that before starting to reply, so maybe I missed
something above. I'd go back and check, but I'll have to pick up my kids
soon.
> Step 14
> The driver/firmware can update the current beacon template with
> the post‑UHR operation element. Until the advance notification interval
> completes, the UHR operation element would not yet be updated.
Not sure I follow, are you envisioning the driver/firmware changing the
beacon contents? I was envisioning it basically always getting the right
template at the right time, and only filling the counters per their
offsets.
> A few potential concerns to consider:
>
> 1. Carrying both the current and post‑beacon templates for all
> affiliated links of an MLD might make the NL message fairly large. we
> are not sure how well that fits with existing practice, since multipart
> handling seems to be used mostly for dump commands. As an alternate, can
> we have multiple commands with message id and reassemble it?
It's input into the kernel, so the size doesn't matter, I think? For
notifications that might be an issue, and dumpit is used for data going
_out_ of the kernel so userspace doesn't have to have arbitrarily large
buffers ready before it knows the data, but on input I don't see how it
matters.
> 2. There may be a small sequencing aspect worth thinking through. For
> example, if a UHR_CU operation is close to completion and we are about
> to apply its post‑beacon template, but before hostapd processes
> EVENT_UHR_CU with CU_POST_NOTIF_COMPLETE it issues a
> START_MLD_BSS_UPDATE for CSA, we could potentially end up using an
> unexpected version of the current beacon template. This may already be
> handled by the existing flow, but it seemed worth calling out for
> completeness.
Yes, I agree, it's something we need to think about. I mentioned that a
bit above, perhaps one way to solve the race is to have a reject built
in based on the cookie(s) being used, that could technically even be
pushed all the way down to the firmware if you really wanted to.
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-03-06 12:43 ` Johannes Berg
@ 2026-03-12 5:49 ` Harshitha Prem
2026-03-12 8:22 ` Johannes Berg
0 siblings, 1 reply; 23+ messages in thread
From: Harshitha Prem @ 2026-03-12 5:49 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih,
Manish Dharanenthiran
On 3/6/2026 6:13 PM, Johannes Berg wrote:
> Hi,
>
>> Thank you very much for your patience, and apologies for the delayed
>> response. We spent some time discussing this internally and wanted to
>> follow up thoughtfully.
>
> Hey, no worries, I'm clearly also not always able to respond quickly, a
> lot of things are going on at any given time ...
>
Thanks for understanding! :)
>>> This could get trickier than I imagined - you now have three periods of
>>> time:
>>>
>>> - now
>>> - after CSA but before UHR update
>>> - after UHR update
>>>
>>> and actually all three might need different UHR operation, since the CSA
>>> can change the bandwidth and therefore e.g. DBE/NPCA. The intermediate
>>> period ("after CSA but before UHR update") can be captured by the CSA
>>> operation (given a template/UHR operation for after) easily.
>>>
>>> But I was imagining we capture all this in the operations already, so I
>>> guess to do that we would need a "NL80211_CMD_MODIFY_MLD_BSS_UPDATE"
>>> command that takes the cookie and updates the post-operation values, so
>>> that the changes due to the CSA could be taken into account in the
>>> previously started UHR update.
>>
>> The idea of introducing an NL80211_CMD_MODIFY_MLD_BSS_UPDATE command
>> makes a lot of sense to us. In cases where
>> NL80211_CMD_START_MLD_BSS_UPDATE is already in progress, having a modify
>> path to update the current beacon seems easier to reason about and
>> manage. From that perspective, a pairing such as
>> NL80211_CMD_START_MLD_BSS_UPDATE together with
>> NL80211_CMD_MODIFY_MLD_BSS_UPDATE feels quite natural.
>
> Yeah, maybe, then it wouldn't ever really go to a normal SET_BEACON any
> more, maybe?
>
> I was thinking more for not having to change all the code in hostapd at
> a given time, it might be more plausible to still allow SET_BEACON and
> just keep giving the counter offsets etc., in case e.g. something "old"
> like short-preamble changes.
>
> But clearly hostapd would have to manage those offsets etc. anyway, so
> perhaps there's really not going to be any reason to support SET_BEACON
> while updates are in progress. But in that case I'd probably argue it
> (SET_BEACON) should be disallowed by the kernel, to catch errors.
Yes, that's the idea when any MLD_BSS_UPDATE is in progress, instead of
sending SET_BEACON rather use MODIFY_MLD_BSS_UPDATE.
>
>> Please find below the envisioned design flow for the UHR CU and CSA
>> intersection.
>>
>> Hostapd (User) mac80211 (Kernel) Air / Station
>> | | |
>> 1 | CMD_START_AP [Adv Notif, | |
>> | Post Notif, Upd Int] | |
>> |-------------------------->| |
>> | | |
>> 2 | CMD_START_MLD_BSS_UPDATE | |
>> | [Link:0, CurTmpl+Offset | |
>> | (All), Timer, PostTmpl | |
>> | (All), Type:UHR_CU, | |
>> | Post UHR Op element] | |
>> |-------------------------->| |
>> | | |
>> 3 | | [Set Tmpl, Timer: Adv=10, |
>> | | Post=10, TIM Update] |
>> | | |
>> 4 | Cookie X | |
>> |<--------------------------| |
>> | | |
>> 5 | EVENT_UHR_CU (CU_START) | |
>> |<--------------------------| |
>
> Not sure what the event does really, at this point? It kind of starts
> immediately anyway, no? But it also doesn't matter for this high-level
> discussion.
Yeah, it would kind of immediate, this is just an indication to user
space applications if someone is interested.
>
>> | | |
>> 6 | | Beacons: 10, 9, 8... |
>> | |-------------------------->|
>> | | |
>> 7 | [CSA Triggered: Link0, | |
>> | Count 10. Sees Cookie X] | |
>> | | |
>> 8 | CMD_START_MLD_BSS_UPDATE | |
>> | [Type:CSA, Link:0, Tmpls, | |
>> | Cookie X + Offset, | |
>> | Post Tmpl (No UHR ele)] | |
>> |-------------------------->| |
>
> Not sure I understand the "No UHR ele" part - surely the post template
> still has UHR? Or did you mean "UHR parameter update"?
>
At this point, the CSA post-beacon template would not include the
updated UHR operation element, since hostapd needs a way to determine
whether that element should be added. The updated UHR operation element
is expected to be reflected only after the advance timer expires.
In a scenario where the CSA completes before the advance timer expires,
the CSA post-beacon may not actually require the updated UHR operation
element.
Because of this, an event-driven approach was considered. For example,
if the UHR advance timer expires before the CSA completes, it could
notify hostapd, which could then update the CSA post-beacon template
with the updated UHR operation element via MODIFY_MLD_BSS_UPDATE.
> Not that there's a race here - "Sees Cookie X", but who knows the cookie
> X is even still valid?
>
> But I think we can pretty much solve that racy by marking a cookie X
> invalid in the kernel (or even FW?) and rejecting the new
> CMD_START_MLD_BSS_UPDATE operation that still refers to Cookie X -
> hostapd would just have to know about that specific rejection reason (I
> guess netlink extended status would point to the wrong cookie attr or
> so) and then rebuild the templates without taking the update with cookie
> X into account.
In this case, cookie X would still need to remain valid for a UHR
critical update, since the countdown associated with cookie X would have
to continue alongside the cookie Y (CSA) countdown. Given that, it’s not
clear whether cookie X can be invalidated at that stage without
impacting the ongoing UHR flow.
>
> But with that race aside, yeah, seems reasonable.
>
>> | | |
>> 9 | Cookie Y | |
>> |<--------------------------| |
>> | | |
>> 10 | CMD_CH_SWITCH_STARTED_ | |
>> | NOTIFY | |
>> |<--------------------------| |
>
> I don't know - this was started by CMD_START_MLD_BSS_UPDATE too, so
> probably should be some generic notification about it, or like I said
> above, maybe isn't even needed at all?
It might be possible to rely on a generic notification when this is
started via CMD_START_MLD_BSS_UPDATE. That said, since CSA has existed
for a long time, I was wondering whether also sending CH_SWITCH_NOTIFY
for backward compatibility could be considered, at least initially.
>
> (I think a good chunk of the channel switch notification is code
> unification between client and AP, does the AP even use the start
> notification?)
>
>> | | |
>> 11 | | Beacons: X=7, Y=10 |
>> | |-------------------------->|
>> | | |
>> 12 | CMD_MODIFY_MLD_BSS_UPDATE | |
>> | (Cookie X Post Tmpl w/ | |
>> | Chan Info, 3 Links) | |
>> |-------------------------->| |
>> | | |
>> 13 | | Beacons: X=1, Y=4 |
>> | |-------------------------->|
>> | | |
>> 14 | | [X=0: Modify Cur Tmpl |
>> | | w/ Post UHR Op element] |
>> | | |
>> 15 | EVENT_UHR_CU | |
>> | (CU_ADVANCE_COMPLETE) | |
>> |<--------------------------| |
>
> That should have Cookie X somehow as an attribute, of course :)
>
> Really all the events should have, but here clearly you meant X since
> the other operation isn't done yet.
Indeed, all the events should have the cookie X.
>
>> | | |
>> 16 | CMD_MODIFY_MLD_BSS_UPDATE | |
>> | (Cookie Y Post Tmpl w/ | |
>> | UHR Op + Param elements) | |
>> |-------------------------->| |
>
> What's this doing? Didn't we have post-Y templates already in the prior
> command? Otherwise isn't this quite a bit racy? Though I guess we have a
> whole beacon interval for hostapd to update everything, which really
> ought to be sufficient (though hostapd may need to stop being single-
> threaded ...)
This is mainly to allow updating the CSA post‑beacon template with the
updated UHR operation element. As you mentioned, yeah, While a full
beacon interval is available, it seemed possible that under heavier
event processing in hostapd, there could still be some short‑lived races
or inconsistencies.
>
>> | | |
>> 17 | | Beacons: X=127, Y=3 |
>> | |-------------------------->|
>> | | |
>> 18 | | Beacons: X=128/129, |
>> | | Y=2/1 (CSA done) |
>> | |-------------------------->|
>> | | |
>> 19 | | [Y=0: Set Post Tmpl |
>> | | cookie Y, UHR Param |
>> | | Off, X=130] |
>> | | |
>> 20 | CMD_CH_SWITCH_NOTIFY | |
>> |<--------------------------| |
>
> (similar comment as above wrt. what notifications are needed)
>
>> | | |
>> 21 | CMD_MLD_BSS_UPDATE_NOTIFY | |
>> | (Complete Cookie Y) | |
>> |<--------------------------| |
>
> Right.
>
>> | | |
>> 22 | CMD_MODIFY_MLD_BSS_UPDATE | |
>> | (Cookie X Post Tmpl w/ | |
>> | UpdatedChan Info) | |
>> |-------------------------->| |
>
> "X post" is a bit misleading ... it's still ongoing, because it's
> changed now but not really _after_ the update X, it's still advertising
> the update X has happened. So in some way the flow is still there.
>
> But it does point out that we need basically three (additional)
> templates for a UHR update:
>
> - announcing the upcoming update
> - announcing the update happened
> - back to normal state afterwards
>
> Which, perhaps, is indeed a bit too much to offload all to the kernel
> even in terms of the API, because if you mix another operation in (here
> in your example the channel switch Y), you end up with even more
> templates ...
That’s a fair point. With multiple operations potentially occurring in
parallel, it does seem possible that this could gradually result in an
increasing number of templates being involved.
>
> So I'm coming around to the idea that you have a notification and
> hostapd has to update the templates at that point.
Yes, that does seem to align with the direction here. This approach
would still partially rely on event‑driven updates (for example,
reacting to a UHR event) to modify the BSS via CMD_MODIFY_MLD_BSS_UPDATE
for csa. At the same time, since such notifications would likely require
the beacon templates to be updated fairly promptly, it does raise some
open questions around prioritization and handling on the hostapd side,
particularly given its single‑threaded nature.
>
>> | | |
>> 23 | | Beacons Continue... |
>> | |-------------------------->|
>> | | |
>> 24 | | Probe Request |
>> | |<--------------------------|
>> | | [Fetch TBTT] |
>> | send_mgmt (TBTT) | |
>> |<--------------------------| |
>> | | |
>> 25 | send_mgmt (Probe Resp | |
>> | w/ TBTT in UHR Param) | |
>> |-------------------------->| |
>> | | |
>
> Not sure I follow this part regarding the "TBTT" thing. Are you saying
> the RX of the probe request would have a TBTT attached to it? But does
> it matter, what matters is the TX? And that's probably impossible to get
> right?
What I was trying to describe is the handling of probe and association
responses during an ongoing UHR CU / EHT CU.
In such cases, when a probe request or association request is received,
the corresponding response may need to reflect the current countdown
state. For example, in the case of UHR, a probe response might need to
include an UHR parameters update element with the appropriate countdown
value.
The idea was that, at the time the response is being constructed, the
current countdown could be obtained from the relevant cookie and made
available to user space. This would allow hostapd to build the probe or
association response accordingly.
>
>> The diagram uses a number of abbreviations, so an explanation is
>> provided below for clarity.
>
> Oops, sorry, didn't see that before starting to reply, so maybe I missed
> something above. I'd go back and check, but I'll have to pick up my kids
> soon.
>
>> Step 14
>> The driver/firmware can update the current beacon template with
>> the post‑UHR operation element. Until the advance notification interval
>> completes, the UHR operation element would not yet be updated.
>
> Not sure I follow, are you envisioning the driver/firmware changing the
> beacon contents? I was envisioning it basically always getting the right
> template at the right time, and only filling the counters per their
> offsets.
>
>> A few potential concerns to consider:
>>
>> 1. Carrying both the current and post‑beacon templates for all
>> affiliated links of an MLD might make the NL message fairly large. we
>> are not sure how well that fits with existing practice, since multipart
>> handling seems to be used mostly for dump commands. As an alternate, can
>> we have multiple commands with message id and reassemble it?
>
> It's input into the kernel, so the size doesn't matter, I think? For
> notifications that might be an issue, and dumpit is used for data going
> _out_ of the kernel so userspace doesn't have to have arbitrarily large
> buffers ready before it knows the data, but on input I don't see how it
> matters.
Based on the earlier discussion in the below thread, I had wondered
whether there might be a potential bottleneck here.
https://lore.kernel.org/all/c7e383a9-c291-426b-a7f1-7845fabbaeeb@oss.qualcomm.com/
We will re‑check this internally to confirm whether that concern is
still applicable.
>
>> 2. There may be a small sequencing aspect worth thinking through. For
>> example, if a UHR_CU operation is close to completion and we are about
>> to apply its post‑beacon template, but before hostapd processes
>> EVENT_UHR_CU with CU_POST_NOTIF_COMPLETE it issues a
>> START_MLD_BSS_UPDATE for CSA, we could potentially end up using an
>> unexpected version of the current beacon template. This may already be
>> handled by the existing flow, but it seemed worth calling out for
>> completeness.
>
> Yes, I agree, it's something we need to think about. I mentioned that a
> bit above, perhaps one way to solve the race is to have a reject built
> in based on the cookie(s) being used, that could technically even be
> pushed all the way down to the firmware if you really wanted to.
>
> johannes
Thanks,
Harshitha
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-03-12 5:49 ` Harshitha Prem
@ 2026-03-12 8:22 ` Johannes Berg
2026-03-12 19:32 ` Johannes Berg
0 siblings, 1 reply; 23+ messages in thread
From: Johannes Berg @ 2026-03-12 8:22 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih,
Manish Dharanenthiran, Jouni Malinen
On Thu, 2026-03-12 at 11:19 +0530, Harshitha Prem wrote:
> > Yeah, maybe, then it wouldn't ever really go to a normal SET_BEACON any
> > more, maybe?
> >
> > I was thinking more for not having to change all the code in hostapd at
> > a given time, it might be more plausible to still allow SET_BEACON and
> > just keep giving the counter offsets etc., in case e.g. something "old"
> > like short-preamble changes.
> >
> > But clearly hostapd would have to manage those offsets etc. anyway, so
> > perhaps there's really not going to be any reason to support SET_BEACON
> > while updates are in progress. But in that case I'd probably argue it
> > (SET_BEACON) should be disallowed by the kernel, to catch errors.
>
> Yes, that's the idea when any MLD_BSS_UPDATE is in progress, instead of
> sending SET_BEACON rather use MODIFY_MLD_BSS_UPDATE.
I was going to say that introduces its own set of races, if it's trying
to modify the BSS update while it _just_ finished, but actually that
race exists anyway and we'd want to just reject updates (regardless of
which command is used) that refer to now-invalid cookies.
> >
> > > | | |
> > > 6 | | Beacons: 10, 9, 8... |
> > > | |-------------------------->|
> > > | | |
> > > 7 | [CSA Triggered: Link0, | |
> > > | Count 10. Sees Cookie X] | |
> > > | | |
> > > 8 | CMD_START_MLD_BSS_UPDATE | |
> > > | [Type:CSA, Link:0, Tmpls, | |
> > > | Cookie X + Offset, | |
> > > | Post Tmpl (No UHR ele)] | |
> > > |-------------------------->| |
> >
> > Not sure I understand the "No UHR ele" part - surely the post template
> > still has UHR? Or did you mean "UHR parameter update"?
> >
>
> At this point, the CSA post-beacon template would not include the
> updated UHR operation element, since hostapd needs a way to determine
> whether that element should be added. The updated UHR operation element
> is expected to be reflected only after the advance timer expires.
> In a scenario where the CSA completes before the advance timer expires,
> the CSA post-beacon may not actually require the updated UHR operation
> element.
Oh, you were thinking of the _updated_ element only? I read it as "no
UHR operation at all", which seems nonsensical since it's still UHR.
> For example, if the UHR advance timer expires before the CSA completes,
(taking this out of the paragraph)
This seems like a bit of a strange "if" though - hostapd set the timers
in beacon countdown for both, so it can trivially predict which one is
going to expire first, there's nothing that can stop that from happening
the way it was predicted.
It obviously has dependencies between them, but I don't see a way to
avoid them.
> Because of this, an event-driven approach was considered.
[...]
> it could
> notify hostapd, which could then update the CSA post-beacon template
> with the updated UHR operation element via MODIFY_MLD_BSS_UPDATE.
So I think all this is an interesting question we should really decide
on first, before we continue with the details of the design.
We have been talking a lot about templates here, and associated latency
issues, and the question of hostapd being single-threaded comes up again
later in your email, etc.
Each UHR update operation will have three pending templates:
- announcing the update will happen (counting down)
- announcing the update has happened (counting up)
- steady state after the operation
Each additional overlapping operation adds another template (another UHR
update operation is [currently] not permitted by the spec, so there's no
second "counting up"):
time
|
v
|
x <-- beacon transmission
|
| UHR update starts
| - template I: announcing update will happen
| - template II: announcing update has happened
| - template III: steady state after UHR update
|
x <-- switch current template to I, two more pending
|
...
| CSA starts
| - update current beacon I to I': include CSA
| - update template II to II': include CSA
| - update template III to III': include CSA
| (assuming CSA finishes after UHR parameter update)
| - add template IV: post UHR update and post CSA
|
x <-- current template is I' now
|
| (If now something else happens that should be reflected in
| the beacon _immediately_, all four I', II', III', IV templates
| need to be updated.)
|
...
|
x <-- UHR update takes effect, switch to II'
|
...
|
x <-- UHR update is no longer announced, switch to III'
|
...
| channel switch actually happens
|
x <-- CSA has happened, is no longer announced, template IV
|
...
(and you could have more of these overlapping, say link removal happens
at the same time, BSS color change, etc., but each new operation only
updates all the templates and adds at most a single new one, UHR updates
two new ones.)
And if that seems complex already, I haven't drawn this up for multi-
link! That would actually have an effect over all the links of the AP
MLD, since they interact with each other too, and carry some information
with counters from the other APs.
So if only _one_ link is doing updates, the number of templates just
multiplies by the number of links, and if multiple links are doing
overlapping updates you add all of those together; three links doing two
overlapping updates each would have four templates each and thus require
a total of 18 templates! (But that's "only" six overlapping operations.)
Oh and each of those templates would have a whole bunch of counter
offsets, since the counter for each operation may need to be set in
multiple places.
This is clearly a *lot* of complexity, and we haven't even really talked
about sending Probe Response, (Re)Association Response, EPP Capabilities
and Operation Parameter Response (and perhaps other) frames, some of
which may have to carry all of those (six) counters in various places.
My original thought was that yes, indeed we can manage this complexity
in the kernel - each set of these operations gets a set of counter
offsets, and we can either do that in mac80211 or have API to let
firmware fill in the counters, and even extend that so that each
operation's cookie can be given for other frames (mentioned in the
previous paragraph). It does get tedious though ...
(I was going to write something about event-driven updates here, but
another thing came to mind first.)
Considering multi-link, event-driven updates, and all the response
frames that I mentioned above, made me realise that we also need to
think about the design in terms of beacon TBTTs for multiple links. I'd
think that the spec allows having different beacon intervals for
different links, and there are TSF offsets, but there's a question here
if we actually want/need that in the implementation.
If not, and the TBTTs for multiple links are aligned, that could
simplify things quite significantly. If yes, perhaps due to the multi-HW
architecture you (Qualcomm) have, it would require a more complex design
to get it right, especially considering that you could have say 4-5
links max and need to send the response frames.
If beacon intervals are the same and TBTT aligned, then basically you
just have to update all the link beacons once each beacon interval, and
could send (handwaving a bit) the response frames with an indication
that they are expected to go out during a specific period of time, and
if that time is mispredicted by hostapd the frame would get dropped by
the driver/FW, and hostapd could send another response instead with the
updated counters etc.
In this case, you could also make the whole beacon template update
handling event-driven, and just have a single "beacons transmitted"
event to hostapd while any operations are ongoing, and hostapd would
just update all the beacon templates for all the links at that point,
including even writing all the counters itself even, it has plenty of
time (a whole beacon interval) to do that.
However, if they're not aligned, you'd need an event per link, and then
handling it on time is really only plausible if we can require the
beacons to either be at the same time or have a "minimum distance",
because otherwise you could end up with really small time intervals.
It's still maybe possible in that case, because hostapd would know which
link(s) got the beacon TX event, but it'd have to update all links for
each event, and if there are beacons close to each other that could
easily become impossible, because after any beacon TX event it has to
update _all_ links before _any_ link transmits a new beacon, and so it
doesn't work if they're only a short time apart.
I'll stop here, I think there are a couple of system design questions in
this that we need answers on first, particularly the question about
beacon alignment across multiple links. But it also affects how we send
the response frames, and it _would_ be nice to not have to offload the
counter filling in those to the kernel/driver/firmware, since that would
make it far more extensible (e.g. for the EPP frame that doesn't seem to
have been considered so far.)
> It might be possible to rely on a generic notification when this is
> started via CMD_START_MLD_BSS_UPDATE. That said, since CSA has existed
> for a long time, I was wondering whether also sending CH_SWITCH_NOTIFY
> for backward compatibility could be considered, at least initially.
Fair. I think probably easier to _not_ have it, since hostapd would
otherwise have to figure out which one to handle, but I guess TBD.
> That’s a fair point. With multiple operations potentially occurring in
> parallel, it does seem possible that this could gradually result in an
> increasing number of templates being involved.
Yes. I spelled it out more above :)
> > So I'm coming around to the idea that you have a notification and
> > hostapd has to update the templates at that point.
>
> Yes, that does seem to align with the direction here. This approach
> would still partially rely on event‑driven updates (for example,
> reacting to a UHR event) to modify the BSS via CMD_MODIFY_MLD_BSS_UPDATE
> for csa. At the same time, since such notifications would likely require
> the beacon templates to be updated fairly promptly, it does raise some
> open questions around prioritization and handling on the hostapd side,
> particularly given its single‑threaded nature.
Sure, but I think we're reaching a point where the single-threaded
nature will need to be reconsidered anyway, if only to offload longer-
running operations (such as SAE/EPPKE calculations) to another
thread/CPU.
> > > | | |
> > > 23 | | Beacons Continue... |
> > > | |-------------------------->|
> > > | | |
> > > 24 | | Probe Request |
> > > | |<--------------------------|
> > > | | [Fetch TBTT] |
> > > | send_mgmt (TBTT) | |
> > > |<--------------------------| |
> > > | | |
> > > 25 | send_mgmt (Probe Resp | |
> > > | w/ TBTT in UHR Param) | |
> > > |-------------------------->| |
> > > | | |
> >
> > Not sure I follow this part regarding the "TBTT" thing. Are you saying
> > the RX of the probe request would have a TBTT attached to it? But does
> > it matter, what matters is the TX? And that's probably impossible to get
> > right?
>
> What I was trying to describe is the handling of probe and association
> responses during an ongoing UHR CU / EHT CU.
Ah yes, see some thoughts about that above. I think the "fetch TBTT"
basically inherently leaves a race - if we want to fully solve that we
either have to have some "drop frame if it was wrong" thing like I
described above. Perhaps that could be expressed in "don't send this
frame after this TBTT" or something instead of linking to the
operations.
> In such cases, when a probe request or association request is received,
> the corresponding response may need to reflect the current countdown
> state. For example, in the case of UHR, a probe response might need to
> include an UHR parameters update element with the appropriate countdown
> value.
Right, across all the links.
> The idea was that, at the time the response is being constructed, the
> current countdown could be obtained from the relevant cookie and made
> available to user space. This would allow hostapd to build the probe or
> association response accordingly.
Yeah but it's racy anyway, and if there's anything event driven hostapd
knows the counter already. I described some thoughts above, so not going
to elaborate more here for now.
> > It's input into the kernel, so the size doesn't matter, I think? For
> > notifications that might be an issue, and dumpit is used for data going
> > _out_ of the kernel so userspace doesn't have to have arbitrarily large
> > buffers ready before it knows the data, but on input I don't see how it
> > matters.
>
> Based on the earlier discussion in the below thread, I had wondered
> whether there might be a potential bottleneck here.
>
> https://lore.kernel.org/all/c7e383a9-c291-426b-a7f1-7845fabbaeeb@oss.qualcomm.com/
>
> We will re‑check this internally to confirm whether that concern is
> still applicable.
Looks like I never replied to that point there, but I don't think it's
an issue on commands sent to the kernel - the in-kernel socket isn't
limited by the size of any command like a userspace read() or recvmsg()
call is limited to the buffer size it has allocated a priori.
There's no such huge event going to userspace in the design, but I
suppose even if there _was_ we could just mandate that hostapd increase
the recvmsg() buffer size according to the number of links or such that
it expects to handle, or even just use a huge size (a few dozen KiB) for
each call, it's not really a big deal either way IMHO.
We don't want to have such huge events (we really can't have them as
multicast events), and we generally strive to keep dump message size
reasonably small, but I don't think it's going to be a show-stopper.
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support
2026-03-12 8:22 ` Johannes Berg
@ 2026-03-12 19:32 ` Johannes Berg
0 siblings, 0 replies; 23+ messages in thread
From: Johannes Berg @ 2026-03-12 19:32 UTC (permalink / raw)
To: Harshitha Prem, linux-wireless
Cc: Karthikeyan Kathirvel, vasanthakumar.thiagarajan,
Lorenzo Bianconi, ath12k, Jeff Johnson, Ping-Ke Shih,
Manish Dharanenthiran, Jouni Malinen, Benjamin Berg
> > Because of this, an event-driven approach was considered.
So - starting this again from scratch. Benjamin and I spent some time
discussing this today too, and hashed out a (mostly?) workable solution
that should address most of the issues. I'll try to summarise that
below.
As will become obvious - and that's why I quoted only the line _you_
wrote before - this means we (including myself :)) need to stop being
afraid of hostapd doing (soft?) real-time [1] tasks...
[1] I'm using that word in the (formal) sense of having a deadline, not
of having to be particularly fast.
Let's assume the following constraints:
- preparing a beacon template as a real-time task can be done by
hostapd, given enough heads-up time
- no periodic events in a steady state when the AP is operating
normally
- TSF drift between links is correctly handled (maintaining <=30us
offset at any time)
We evidently already make these assumptions:
- if beacon intervals are not the same, the TBTT offset in RNR is
filled in by firmware (I see no way around this)
- either firmware fills in TSF offset, or it's just zero, and not
really accounting for slight drifts (but that's probably OK since it
never adds up given the <=30us requirement)
And also let's introduce some new operations to driver/firmware:
- the firmware can drop a frame that it's not able to transmit before
a given (as frame metadata) TSF value on the link, and indicate to
the driver that this is the reason the frame was dropped
- the firmware can create events at/after beacon TBTT (or beacon
transmission), this can be controlled by the driver; these events
contain the next TBTT's timestamp value
- the TSF offsets between links can be known to the driver, if they can
change (I suspect CSA could do that?) this can somehow be noticed by
or given to the driver
With that, it seems we can redesign this whole thing to be event-driven
and (mostly?) race-free.
In steady state, basically nothing would change from what hostapd is
doing today. It simply configures beacon templates, occasionally updates
them if elements need to change, and sends probe responses,
(re)association responses etc. as usual.
During any sort of update (CSA, color change, EHT updates, UHR updates)
things operate a bit differently:
1) hostapd enables TBTT / beacon transmit events, these events would be
generated by firmware and passed up, for each link, containing also the
TBTT timestamp of the _next_ beacon to be transmitted
2) hostapd waits for the TBTT event for the link that it wants to do the
update on, ignoring events for other links
3) starting from that TBTT event, on each TBTT event hostapd generates a
new beacon template for the link the event was for, and configures it to
the driver/firmware. Since that's a future beacon, it has to predict the
content of that beacon using
- the TBTT of the first beacon carrying the update announcement
- the TSF offsets between the links
- the beacon intervals of all the links
(a bit more on this later)
4) After applying the updates (a bit more on this later) and noticing
that the announcements are finished, hostapd waits for one more TBTT
event for each link and configures the beacons back to steady state,
after which it turns off the events.
If, at any time during this, hostapd needs to send a probe response,
(re)association response, EPP Capa/Operation response (or others?) which
holds information about the updates with the current counter values,
hostapd will create the frame per the current counters that it
maintains, and will transmit this frame with a TSF cut-off value
indicating that it must be transmitted before the next TBTT (over all
links), or dropped.
If this frame ends up being dropped by firmware because it didn't get
out before the indicated TSF, hostapd gets a specific notification for
this and then simply re-generates it and sends it again. This could
possibly repeat if TBTTs are close together on multiple links, but I
think it's not worth optimising for this case, though it could be done
by deferring the response slightly based on timers, or at the expense of
a more complex API ("defer until X and don't send after Y" vs. "don't
send after Y"), neither seems really worthwhile.
I said I'd give more information for (3) and (4) above, so:
For (3), also consider that it already has to effectively be able to do
this for the templates thing we discussed, it has to predict what each
link is going to look like in the future. I think this isn't too much of
an issue, but care must be taken especially if beacon intervals differ.
For (4), I think the way how the updates are done may depend on what the
update is. If, for example, it's DBE increasing the bandwidth, then
could just do the update _before_ the 0 beacon is transmitted, and if
it's decreasing bandwidth could do it _after_ the 0 beacon is
transmitted. Some of these may potentially require management by the
kernel or even driver/firmware (how do you switch NPCA parameters at the
exact right point if not in FW?), and perhaps (especially for CSA?)
there will be some considerations regarding multiple interfaces.
I mostly think this question is orthogonal, since armed with a TBTT
hostapd could also request that this update be done at a given TBTT.
We haven't really been able to poke significant holes into this, but
maybe that doesn't mean much. Couple of thoughts on that:
* For each link, hostapd has roughly the whole beacon interval to build
the next beacon's template, which seems reasonable.
* There's a really weird corner case where an assoc response is
attempted to transmit just before a beacon, doesn't get an ACK, but a
retransmission isn't possible until after the beacon and it's dropped
due to the TSF cut-off. Doesn't seem worth worrying about.
* If the TBTTs for two links are at the same time, and the events to
userspace for them are not coming "updated link first", then the
beacon transmitted at the same time on the unchanged link may not yet
be announcing the update, depending on the event order, given that
hostapd waited for the affected link's first TBTT event. This doesn't
really seem like a problem, but I think could be addressed by
updating all the links on the first event immediately or so, or
(Benjamin prefers this I think) adding the first beacon's TBTT to the
response to the event enable command, I just worry that would cause
other races that would need to be addressed.
That's it for now :) Let me know what you think.
johannes
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-03-12 19:32 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-30 15:21 [PATCH wireless-next v8 0/3] wifi: initial UHR support Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 1/3] wifi: ieee80211: add some initial UHR definitions Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 2/3] wifi: cfg80211: add initial UHR support Johannes Berg
2026-02-11 14:19 ` Johannes Berg
2026-02-12 11:08 ` Harshitha Prem
2026-02-13 10:11 ` Johannes Berg
2026-02-13 10:26 ` Johannes Berg
2026-02-16 17:39 ` Harshitha Prem
2026-02-16 17:17 ` Harshitha Prem
2026-02-17 10:05 ` Johannes Berg
2026-02-24 11:01 ` Harshitha Prem
2026-03-06 12:43 ` Johannes Berg
2026-03-12 5:49 ` Harshitha Prem
2026-03-12 8:22 ` Johannes Berg
2026-03-12 19:32 ` Johannes Berg
2026-01-30 15:21 ` [PATCH wireless-next v8 3/3] wifi: mac80211: " Johannes Berg
2026-01-30 18:29 ` Pablo MARTIN-GOMEZ
2026-02-02 8:27 ` Johannes Berg
2026-02-02 10:27 ` [PATCH wireless-next v8 0/3] wifi: " Pablo MARTIN-GOMEZ
2026-02-02 11:13 ` Johannes Berg
2026-02-02 10:50 ` Karthikeyan Kathirvel
2026-02-02 11:12 ` Johannes Berg
2026-02-05 8:38 ` Karthikeyan Kathirvel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox