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))
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 */
#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,
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)
{
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);
}
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)
{
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);
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);
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
/* 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);
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;
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
* @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
*/
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