]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
wifi: mac80211: AP: handle DBE for clients
authorJohannes Berg <johannes.berg@intel.com>
Fri, 29 May 2026 08:25:08 +0000 (10:25 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 3 Jun 2026 12:11:58 +0000 (14:11 +0200)
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>
include/linux/ieee80211-eht.h
include/linux/ieee80211-uhr.h
net/mac80211/ap.c
net/mac80211/cfg.c
net/mac80211/ieee80211_i.h
net/mac80211/rx.c
net/mac80211/sta_info.c
net/mac80211/sta_info.h

index 73e97fe307247b6556ef15eb09df63cab55e52c3..18f9c662cf4cbe2cc90542e83b7d6ebe7e7bb06f 100644 (file)
@@ -393,14 +393,24 @@ ieee80211_eht_oper_size_ok(const u8 *data, u8 len)
        return len >= needed;
 }
 
+/* must validate ieee80211_eht_oper_size_ok() first */
+static inline const struct ieee80211_eht_operation_info *
+ieee80211_eht_oper_info(const struct ieee80211_eht_operation *eht_oper)
+{
+       if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT))
+               return NULL;
+
+       return (const void *)eht_oper->optional;
+}
+
 /* must validate ieee80211_eht_oper_size_ok() first */
 static inline u16
 ieee80211_eht_oper_dis_subchan_bitmap(const struct ieee80211_eht_operation *eht_oper)
 {
-       const struct ieee80211_eht_operation_info *info =
-               (const void *)eht_oper->optional;
+       const struct ieee80211_eht_operation_info *info;
 
-       if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT))
+       info = ieee80211_eht_oper_info(eht_oper);
+       if (!info)
                return 0;
 
        if (!(eht_oper->params & IEEE80211_EHT_OPER_DISABLED_SUBCHANNEL_BITMAP_PRESENT))
index 1dd53c5f295472ae0d979ecd5ce6ba4a43311c77..597c9e559261b964f77e0c6affd1809315c6a13b 100644 (file)
@@ -628,4 +628,20 @@ struct ieee80211_uhr_mode_change_tuple {
        u8 variable[];
 } __packed;
 
+static inline int
+ieee80211_uhr_mode_change_tuple_size(const struct ieee80211_uhr_mode_change_tuple *tuple)
+{
+       return sizeof(*tuple) +
+              le16_get_bits(tuple->control,
+                            IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_LENGTH);
+}
+
+#define for_each_uhr_mode_change_tuple(data, len, tuple)               \
+       for (tuple = (const void *)(data);                              \
+            (len) - ((const u8 *)tuple - (data)) >= sizeof(*tuple) &&  \
+            (len) - ((const u8 *)tuple - (data)) >=                    \
+               ieee80211_uhr_mode_change_tuple_size(tuple);            \
+            tuple = (const void *)((const u8 *)tuple +                 \
+                                   ieee80211_uhr_mode_change_tuple_size(tuple)))
+
 #endif /* LINUX_IEEE80211_UHR_H */
index 6c7d2d51a372ae9404fde79064612774dfb3fa92..e7ac99c5a22ebee54b0dd4c9d9b49391ea7266d8 100644 (file)
@@ -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);
 }
index 1554e31f002947ec550a627475924e09d4efe5d0..3b58af59f7e4295cad37fc61a0b14d63f9f712eb 100644 (file)
@@ -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);
index 29bdfd2a39bd9aada6ad106491014e5beae7d09b..34a9ea8b6f857e90d673f7137bab3320521ac319 100644 (file)
@@ -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);
index 858c3f65911969881c7ebf40fa1c0bdf2edf9ec9..db421edfd54c40eb58319e4067ca5d1d14c82ca4 100644 (file)
@@ -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;
index 2787be556034ef0e53ed8fd0e188b69efe0d128a..02b587ff850461eab8c8e51b9a0ef200eb691dc2 100644 (file)
@@ -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
index 0f56a6e536297b35c93465151bffa42b5ec66fe5..26138934b72e78e510b2a6fd61727c7a79924972 100644 (file)
@@ -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