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>
This commit is contained in:
Johannes Berg 2026-05-29 10:25:08 +02:00
parent d3ea22270d
commit 14b1a85518
8 changed files with 232 additions and 6 deletions

View 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))

View 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 */

View 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);
}

View 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);

View 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);

View 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;

View 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);
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 bw;
}
static enum ieee80211_sta_rx_bandwidth

View 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