From: Johannes Berg 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.) Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 16 +++- include/linux/ieee80211-uhr.h | 16 ++++ net/mac80211/ap.c | 146 ++++++++++++++++++++++++++++++++++ net/mac80211/cfg.c | 35 ++++++++ net/mac80211/ieee80211_i.h | 2 + net/mac80211/rx.c | 9 +++ net/mac80211/sta_info.c | 10 ++- net/mac80211/sta_info.h | 4 + 8 files changed, 232 insertions(+), 6 deletions(-) diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 73e97fe30724..18f9c662cf4c 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -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)) diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index f9592ffae3eb..bbfcb53f5987 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -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 */ 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 fb4c1c298159..19550ed3b908 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 4579ebdebdf5..e4bd58c02c8f 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 6b44030659fc..873077dbd65b 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -3691,10 +3691,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 diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 6974fccb839f..ec0bf56bfb05 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 -- 2.53.0