summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2026-05-29 10:25:08 +0200
committerJohannes Berg <johannes.berg@intel.com>2026-06-03 14:11:58 +0200
commit14b1a85518a17c01a8d52a8d3332596b68b4f201 (patch)
treee53aa4ae0f574a9f140f4e4590a838c1d574ee03 /net
parentd3ea22270d7c02fe08606f609545cbe583e53060 (diff)
downloadlinux-next-14b1a85518a17c01a8d52a8d3332596b68b4f201.tar.gz
linux-next-14b1a85518a17c01a8d52a8d3332596b68b4f201.zip
wifi: mac80211: AP: handle DBE for clients
In AP mode, track the BSS non-DBE bandwidth and apply that to all non-DBE clients, then track OMP updates from the clients and enable/disable DBE accordingly. For now don't send a response, clients need to have a timer anyway (it's up to the driver to set the right timeout in UHR capabilities.) Link: https://patch.msgid.link/20260529102644.be84f2b055cc.I4d2c067dfe54c47621d5a872ca07a0e754d6c20f@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net')
-rw-r--r--net/mac80211/ap.c146
-rw-r--r--net/mac80211/cfg.c35
-rw-r--r--net/mac80211/ieee80211_i.h2
-rw-r--r--net/mac80211/rx.c9
-rw-r--r--net/mac80211/sta_info.c10
-rw-r--r--net/mac80211/sta_info.h4
6 files changed, 203 insertions, 3 deletions
diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c
index 6c7d2d51a372..e7ac99c5a22e 100644
--- a/net/mac80211/ap.c
+++ b/net/mac80211/ap.c
@@ -8,6 +8,7 @@
#include "driver-ops.h"
#include "ieee80211_i.h"
+#include "rate.h"
static void
ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
@@ -186,6 +187,113 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len);
}
+static void
+ieee80211_rx_uhr_link_reconfig_req(struct ieee80211_sub_if_data *sdata,
+ struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ const struct element *sub;
+ struct sta_info *sta;
+
+ /*
+ * rx.c only accepts IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST
+ * which is valid, so no need to check the frame type/format/etc.
+ */
+
+ sta = sta_info_get_bss(sdata, mgmt->sa);
+ if (!sta)
+ return;
+
+ struct ieee802_11_elems *elems __free(kfree) =
+ ieee802_11_parse_elems(mgmt->u.action.uhr_link_reconf_req.variable,
+ skb->len - IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req),
+ IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION,
+ NULL);
+ /* STA will assume we processed it, not good */
+ if (!elems)
+ return;
+
+ if (!elems->ml_reconf)
+ return;
+
+ for_each_mle_subelement(sub, (u8 *)elems->ml_reconf,
+ elems->ml_reconf_len) {
+ const struct ieee80211_mle_per_sta_profile *prof =
+ (const void *)sub->data;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+ struct ieee80211_link_data *link;
+ struct link_sta_info *link_sta;
+ const struct element *chg;
+ u16 control;
+ u8 link_id;
+
+ if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
+ continue;
+
+ if (!ieee80211_mle_reconf_sta_prof_size_ok(sub->data,
+ sub->datalen))
+ return;
+
+ control = le16_to_cpu(prof->control);
+ link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID;
+
+ if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS)
+ return;
+
+ link = sdata_dereference(sdata->link[link_id], sdata);
+ if (!link)
+ continue;
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf,
+ sdata);
+ if (!chanctx_conf)
+ continue;
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx,
+ conf);
+
+ link_sta = sdata_dereference(sta->link[link_id], sdata);
+ if (!link_sta)
+ continue;
+
+ /* do we need to handle any other bits? */
+ if (control & ~(IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID |
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE))
+ continue;
+
+ if (u16_get_bits(control, IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE) !=
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD)
+ continue;
+
+ for_each_element_extid(chg, WLAN_EID_EXT_UHR_MODE_CHG,
+ prof->variable + prof->sta_info_len - 1,
+ sub->datalen - sizeof(*prof) -
+ prof->sta_info_len + 1) {
+ const struct ieee80211_uhr_mode_change_tuple *tuple;
+
+ for_each_uhr_mode_change_tuple(chg->data + 1,
+ chg->datalen - 1,
+ tuple) {
+ u8 id = le16_get_bits(tuple->control,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID);
+ bool enabled = le16_get_bits(tuple->control,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE);
+
+ /* only handle DBE (for now?) */
+ if (id != IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE)
+ continue;
+
+ link_sta->uhr_dbe_enabled = enabled;
+ /* also recalculates and updates per-STA bw */
+ ieee80211_recalc_chanctx_min_def(sdata->local,
+ chanctx);
+ }
+ }
+ }
+
+ /* TODO: send a response */
+}
+
void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb)
{
@@ -203,5 +311,43 @@ void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
break;
}
break;
+ case WLAN_CATEGORY_PROTECTED_UHR:
+ switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST:
+ ieee80211_rx_uhr_link_reconfig_req(sdata, skb);
+ break;
+ }
+ break;
+ }
+}
+
+void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link)
+{
+ struct ieee80211_sub_if_data *sdata = link->sdata;
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+ int link_id = link->link_id;
+ struct sta_info *sta;
+
+ chanctx_conf = sdata_dereference(link->conf->chanctx_conf, sdata);
+ if (!chanctx_conf)
+ return;
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+
+ list_for_each_entry(sta, &local->sta_list, list) {
+ struct link_sta_info *link_sta;
+
+ if (sta->sdata->bss != sdata->bss)
+ continue;
+
+ link_sta = sdata_dereference(sta->link[link_id], sdata);
+ if (!link_sta)
+ continue;
+
+ link_sta->uhr_dbe_enabled = false;
}
+
+ /* also recalculates and updates per-STA bw */
+ ieee80211_recalc_chanctx_min_def(local, chanctx);
}
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 1554e31f0029..3b58af59f7e4 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1391,6 +1391,36 @@ ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
return IEEE80211_STA_RX_BW_20;
}
+static enum ieee80211_sta_rx_bandwidth
+ieee80211_calc_ap_eht_bw(struct cfg80211_beacon_data *params,
+ enum ieee80211_sta_rx_bandwidth he_and_lower)
+{
+ const struct ieee80211_eht_operation_info *info;
+
+ if (!params->eht_oper)
+ return he_and_lower;
+
+ info = ieee80211_eht_oper_info(params->eht_oper);
+ if (!info)
+ return he_and_lower;
+
+ switch (u8_get_bits(info->control, IEEE80211_EHT_OPER_CHAN_WIDTH)) {
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_20MHZ:
+ return IEEE80211_STA_RX_BW_20;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_40MHZ:
+ return IEEE80211_STA_RX_BW_40;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ:
+ return IEEE80211_STA_RX_BW_80;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_160MHZ:
+ return IEEE80211_STA_RX_BW_160;
+ case IEEE80211_EHT_OPER_CHAN_WIDTH_320MHZ:
+ return IEEE80211_STA_RX_BW_320;
+ }
+
+ /* invalid setting, assume 20 MHz */
+ return IEEE80211_STA_RX_BW_20;
+}
+
static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
struct cfg80211_beacon_data *params)
{
@@ -1415,6 +1445,8 @@ static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
return;
link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params);
+ link->bss_bw.eht = ieee80211_calc_ap_eht_bw(params,
+ link->bss_bw.he_and_lower);
chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
@@ -4469,6 +4501,9 @@ static int __ieee80211_csa_finalize(struct ieee80211_link_data *link_data)
ieee80211_link_info_change_notify(sdata, link_data, changed);
+ if (sdata->vif.type == NL80211_IFTYPE_AP)
+ ieee80211_uhr_disable_dbe_all_stas(link_data);
+
ieee80211_vif_unblock_queues_csa(sdata);
err = drv_post_channel_switch(link_data);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 29bdfd2a39bd..34a9ea8b6f85 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1146,6 +1146,7 @@ struct ieee80211_link_data {
struct {
enum ieee80211_sta_rx_bandwidth he_and_lower;
+ enum ieee80211_sta_rx_bandwidth eht; /* and UHR non-DBE */
} bss_bw;
#ifdef CONFIG_MAC80211_DEBUGFS
@@ -2003,6 +2004,7 @@ bool ieee80211_is_our_addr(struct ieee80211_sub_if_data *sdata,
/* AP code */
void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb);
+void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link);
/* STA code */
void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 858c3f659119..db421edfd54c 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -3952,6 +3952,15 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
break;
switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST:
+ if (sdata->vif.type != NL80211_IFTYPE_AP)
+ break;
+ if (len < IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req))
+ goto invalid;
+ if (mgmt->u.action.uhr_link_reconf_req.type !=
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST)
+ break;
+ goto queue;
case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY:
if (sdata->vif.type != NL80211_IFTYPE_STATION)
break;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 2787be556034..02b587ff8504 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3701,10 +3701,14 @@ ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
if (WARN_ON(!link))
return IEEE80211_STA_RX_BW_20;
- if (link_sta->pub->eht_cap.has_eht)
- return bw;
+ if (!link_sta->pub->eht_cap.has_eht)
+ return min(bw, link->bss_bw.he_and_lower);
+
+ if (!link_sta->pub->uhr_cap.has_uhr ||
+ !link_sta->uhr_dbe_enabled)
+ return min(bw, link->bss_bw.eht);
- return min(bw, link->bss_bw.he_and_lower);
+ return bw;
}
static enum ieee80211_sta_rx_bandwidth
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 0f56a6e53629..26138934b72e 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -514,6 +514,8 @@ struct ieee80211_fragment_cache {
* @uhr_usable_tx_width: bandwidth restriction for UHR for TX, only when
* the link_sta is an AP, to restrict TX to BSS width during DBE
* enablement
+ * @uhr_dbe_enabled: for STAs as clients to an AP interface indicates
+ * DBE is enabled by the STA
* @debugfs_dir: debug filesystem directory dentry
* @pub: public (driver visible) link STA data
*/
@@ -567,6 +569,8 @@ struct link_sta_info {
rx_omi_bw_staging;
enum ieee80211_sta_rx_bandwidth uhr_usable_tx_width;
+ bool uhr_dbe_enabled;
+
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif