const struct ieee80211_conn_settings *conn;
u32 vht_cap_info;
bool ignore_ht_channel_mismatch;
+ const struct cfg80211_chan_def *cur_chandef;
+ bool cur_dbe_used;
/* target chandef is filled in */
struct cfg80211_chan_def *chandef;
};
+struct ieee80211_determine_ap_chan_output {
+ /* filled to indicate UHR DBE was used */
+ bool dbe_used;
+ /* and need to know non-DBE width */
+ enum nl80211_chan_width non_dbe_width;
+};
+
static enum ieee80211_conn_mode
ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_determine_ap_chan_data *data)
+ const struct ieee80211_determine_ap_chan_data *data,
+ struct ieee80211_determine_ap_chan_output *out)
{
bool ignore_ht_channel_mismatch = data->ignore_ht_channel_mismatch;
const struct ieee802_11_elems *elems = data->elems;
bool no_vht = false;
u32 ht_cfreq;
+ memset(out, 0, sizeof(*out));
+
if (ieee80211_hw_check(&sdata->local->hw, STRICT))
ignore_ht_channel_mismatch = false;
struct cfg80211_chan_def npca_chandef = *chandef;
const struct ieee80211_sta_uhr_cap *uhr_cap;
const struct ieee80211_uhr_npca_info *npca;
+ const struct ieee80211_uhr_dbe_info *dbe;
+ struct cfg80211_chan_def dbe_chandef;
/* frames other than beacons carry UHR capability too */
if (!elems->uhr_cap)
if (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP)
*chandef = npca_chandef;
+
+ dbe = ieee80211_uhr_oper_dbe_info(uhr_oper);
+ if (dbe) {
+ const struct ieee80211_uhr_cap_dbe *dbe_cap;
+ u8 dbe_bw_oper;
+ u8 dbe_bw_cap;
+
+ dbe_cap = ieee80211_uhr_dbe_cap(elems->uhr_cap);
+
+ if (!dbe_cap) {
+ sdata_info(sdata,
+ "AP without UHR DBE capability uses it, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ dbe_bw_oper = u8_get_bits(dbe->params,
+ IEEE80211_UHR_DBE_OPER_BANDWIDTH);
+
+ if (le16_get_bits(uhr_oper->params,
+ IEEE80211_UHR_OPER_PARAMS_DBE_BW) != dbe_bw_oper) {
+ sdata_info(sdata,
+ "AP UHR DBE settings mismatch, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (ieee80211_uhr_dbe_bw_mhz(dbe_bw_oper) < 0) {
+ sdata_info(sdata,
+ "AP UHR DBE bandwidth invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ dbe_bw_cap = u8_get_bits(dbe_cap->cap,
+ IEEE80211_UHR_MAC_CAP_DBE_MAX_BW);
+
+ switch (dbe_bw_cap) {
+ case IEEE80211_UHR_DBE_MAX_BW_40:
+ case IEEE80211_UHR_DBE_MAX_BW_80:
+ case IEEE80211_UHR_DBE_MAX_BW_160:
+ case IEEE80211_UHR_DBE_MAX_BW_320:
+ break;
+ default:
+ sdata_info(sdata,
+ "AP UHR DBE capability invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ /* 1-4 are same in DBE capabilities, map 320-2 to 320 */
+ if (dbe_bw_oper == IEEE80211_UHR_DBE_OPER_BW_320_2)
+ dbe_bw_oper = IEEE80211_UHR_DBE_MAX_BW_320;
+ if (dbe_bw_oper > dbe_bw_cap) {
+ sdata_info(sdata,
+ "AP UHR DBE wider than capability, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+ }
+
+ dbe_chandef = *chandef;
+
+ if (cfg80211_chandef_add_dbe(&dbe_chandef, dbe)) {
+ sdata_info(sdata,
+ "AP UHR DBE settings invalid, disabling UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (dbe &&
+ /* maybe driver would like to never use DBE */
+ uhr_cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP &&
+ ieee80211_chandef_usable(sdata, &dbe_chandef,
+ IEEE80211_CHAN_DISABLED)) {
+ out->non_dbe_width = chandef->width;
+ *chandef = dbe_chandef;
+ out->dbe_used = true;
+ }
+ } else if (data->cur_chandef && data->cur_dbe_used &&
+ cfg80211_chandef_compatible(chandef, data->cur_chandef)) {
+ u8 dbe_bw = le16_get_bits(uhr_oper->params,
+ IEEE80211_UHR_OPER_PARAMS_DBE_BW);
+ int dbe_bw_mhz;
+
+ dbe_bw_mhz = ieee80211_uhr_dbe_bw_mhz(dbe_bw);
+ if (dbe_bw_mhz < 0) {
+ sdata_info(sdata,
+ "AP UHR DBE bandwidth invalid, drop UHR\n");
+ return IEEE80211_CONN_MODE_EHT;
+ }
+
+ if (cfg80211_chandef_get_width(data->cur_chandef) == dbe_bw_mhz) {
+ *chandef = *data->cur_chandef;
+ out->dbe_used = true;
+ }
}
return IEEE80211_CONN_MODE_UHR;
VISIBLE_IF_MAC80211_KUNIT struct ieee802_11_elems *
ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata,
struct ieee80211_conn_settings *conn,
- struct cfg80211_bss *cbss, int link_id,
+ struct cfg80211_bss *cbss,
+ struct link_sta_info *link_sta, int link_id,
struct ieee80211_chan_req *chanreq,
struct cfg80211_chan_def *ap_chandef,
unsigned long *userspace_selectors)
enum ieee80211_conn_mode ap_mode;
unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {};
unsigned long sta_selectors[BITS_TO_LONGS(128)] = {};
+ struct ieee80211_determine_ap_chan_output ap_chan_out;
struct ieee80211_determine_ap_chan_data ap_chan_data = {
.channel = channel,
.vht_cap_info = bss->vht_cap_info,
return ERR_PTR(-ENOMEM);
ap_chan_data.elems = elems;
- ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data,
+ &ap_chan_out);
+ conn->dbe_enabled = ap_chan_out.dbe_used;
/* this should be impossible since parsing depends on our mode */
if (WARN_ON(ap_mode > conn->mode)) {
goto free;
}
+ if (conn->dbe_enabled && link_sta)
+ link_sta->uhr_usable_tx_width =
+ ieee80211_chan_width_to_rx_bw(ap_chan_out.non_dbe_width);
+
return elems;
free:
kfree(elems);
}
EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode);
+static void ieee80211_send_uhr_omp_req_dbe(struct ieee80211_sub_if_data *sdata,
+ u16 link_mask, bool initial)
+{
+ struct ieee80211_mle_basic_common_info *common;
+ struct ieee80211_mle_per_sta_profile *per_sta;
+ struct ieee80211_uhr_mode_change_tuple *tuple;
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_multi_link_elem *mle;
+ struct ieee80211_link_data *link;
+ struct ieee80211_tx_info *info;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+ u8 *ml_elem_len;
+ size_t size;
+
+ if (initial) {
+ bool enabled = false;
+
+ for_each_link_data(sdata, link) {
+ if (!(link_mask & BIT(link->link_id)))
+ continue;
+ if (link->u.mgd.conn.dbe_enabled) {
+ enabled = true;
+ break;
+ }
+ }
+
+ if (!enabled)
+ return;
+ }
+
+ if (sdata->u.mgd.uhr_omp.links) {
+ if (initial)
+ sdata->u.mgd.uhr_omp.pending_init |= link_mask;
+ else
+ sdata->u.mgd.uhr_omp.pending |= link_mask;
+ return;
+ }
+
+ size = local->hw.extra_tx_headroom +
+ IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req) +
+ 3 + sizeof(*mle) + sizeof(*common) +
+ IEEE80211_MLD_MAX_NUM_LINKS *
+ (2 + sizeof(*per_sta) +
+ 3 + sizeof(*tuple) /* single tuple for each link */);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, local->hw.extra_tx_headroom);
+
+ mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req));
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_ACTION);
+ memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN);
+ memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+ memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN);
+
+ mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_UHR;
+ mgmt->u.action.action_code =
+ IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST;
+
+ sdata->u.mgd.dialog_token_alloc++;
+ /*
+ * NOTE:
+ * Driver and FW might both send these frames, and iwlwifi
+ * decided that the driver uses odd numbers, FW uses even
+ * numbers. For now, hardcode that here, until it matters
+ * to some other driver.
+ *
+ * Note also that there's currently no real synchronisation
+ * in this case, but it's not valid that both send such a
+ * frame at the same time, i.e. while waiting for a response
+ * there can't be another frame sent. This needs addressing
+ * in the future.
+ */
+ if (sdata->u.mgd.dialog_token_alloc % 2 == 0)
+ sdata->u.mgd.dialog_token_alloc++;
+
+ sdata->u.mgd.uhr_omp.dialog_token = sdata->u.mgd.dialog_token_alloc;
+ mgmt->u.action.uhr_link_reconf_req.dialog_token =
+ sdata->u.mgd.uhr_omp.dialog_token;
+ mgmt->u.action.uhr_link_reconf_req.type =
+ IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST;
+
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ ml_elem_len = skb_put(skb, 1);
+ skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK);
+ mle = skb_put_zero(skb, sizeof(*mle));
+ mle->control = cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_RECONF |
+ IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR);
+
+ common = skb_put(skb, sizeof(*common));
+ common->len = sizeof(*common);
+ memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN);
+
+ for_each_link_data(sdata, link) {
+ u8 *subelem_len;
+
+ if (!(link_mask & BIT(link->link_id)))
+ continue;
+
+ sdata->u.mgd.uhr_omp.links |= BIT(link->link_id);
+
+ skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE);
+ subelem_len = skb_put(skb, 1);
+ per_sta = skb_put_zero(skb, sizeof(*per_sta));
+ per_sta->control =
+ le16_encode_bits(link->link_id,
+ IEEE80211_MLE_STA_CONTROL_LINK_ID) |
+ le16_encode_bits(IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD,
+ IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE);
+ per_sta->sta_info_len = 1; /* includes itself */
+
+ skb_put_u8(skb, WLAN_EID_EXTENSION);
+ skb_put_u8(skb, 1 + sizeof(*tuple));
+ skb_put_u8(skb, WLAN_EID_EXT_UHR_MODE_CHG);
+ tuple = skb_put_zero(skb, sizeof(*tuple));
+ tuple->control =
+ le16_encode_bits(IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE,
+ IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID);
+
+ if (link->u.mgd.conn.dbe_enabled)
+ tuple->control |=
+ cpu_to_le16(IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE);
+
+ ieee80211_fragment_element(skb, subelem_len,
+ IEEE80211_MLE_SUBELEM_FRAGMENT);
+ }
+
+ ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT);
+
+ info = IEEE80211_SKB_CB(skb);
+ info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
+ info->status_data = IEEE80211_STATUS_TYPE_UHR_OMP;
+ ieee80211_tx_skb(sdata, skb);
+}
+
static int ieee80211_config_bw(struct ieee80211_link_data *link,
struct ieee802_11_elems *elems,
bool update, u64 *changed, u16 stype)
{
struct ieee80211_channel *channel = link->conf->chanreq.oper.chan;
struct cfg80211_chan_def ap_chandef;
+ struct ieee80211_determine_ap_chan_output ap_chan_out;
struct ieee80211_determine_ap_chan_data ap_chan_data = {
.channel = channel,
.vht_cap_info = 0,
.chandef = &ap_chandef,
.elems = elems,
.conn = &link->u.mgd.conn,
+ .cur_chandef = &link->conf->chanreq.oper,
+ .cur_dbe_used = link->u.mgd.conn.dbe_enabled,
};
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_chanctx_conf *chanctx_conf;
ap_chan_data.vht_cap_info =
le32_to_cpu(elems->vht_cap_elem->vht_cap_info);
- ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data);
+ ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data,
+ &ap_chan_out);
+ link->u.mgd.conn.dbe_enabled = ap_chan_out.dbe_used;
if (ap_mode != link->u.mgd.conn.mode) {
link_info(link,
cfg80211_ch_switch_notify(sdata->dev, &link->csa.chanreq.oper,
link->link_id);
link->conf->csa_active = false;
+ link->u.mgd.conn.dbe_enabled = false;
ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
if (WARN_ON(!ap_sta))
if (WARN_ON(!link_sta))
return;
+ /*
+ * If the link was somehow deactivated in the middle of enabling
+ * DBE while waiting for a response, this could be stuck, reset.
+ */
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+
link_sta->pub->bandwidth =
ieee80211_sta_current_bw(link_sta,
&link->csa.chanreq.oper,
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+ struct link_sta_info *link_sta;
+ struct sta_info *ap_sta;
int ret;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
ieee80211_vif_unblock_queues_csa(sdata);
link->conf->csa_active = false;
+ link->u.mgd.conn.dbe_enabled = false;
link->u.mgd.csa.blocked_tx = false;
link->u.mgd.csa.waiting_bcn = false;
return;
}
+ ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!ap_sta))
+ return;
+
+ link_sta = sdata_dereference(ap_sta->link[link->link_id], sdata);
+ if (WARN_ON(!link_sta))
+ return;
+
+ /*
+ * If DBE was being activated and CSA happened, this could be
+ * on a wrong value. Reset it.
+ */
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+
cfg80211_ch_switch_notify(sdata->dev, &link->conf->chanreq.oper,
link->link_id);
}
memset(ifmgd->userspace_selectors, 0,
sizeof(ifmgd->userspace_selectors));
+
+ ifmgd->uhr_omp.pending = 0;
+ ifmgd->uhr_omp.pending_init = 0;
}
static void ieee80211_reset_ap_probe(struct ieee80211_sub_if_data *sdata)
ifmgd->reconnect = false;
}
+static void ieee80211_uhr_omp_req_status(struct ieee80211_sub_if_data *sdata)
+{
+ bool acked = sdata->u.mgd.uhr_omp.acked;
+ u16 links = sdata->u.mgd.uhr_omp.links;
+ struct ieee80211_link_data *link;
+ struct sta_info *ap;
+
+ /* timer and queued RX could overlap */
+ if (!links)
+ return;
+
+ sdata->u.mgd.uhr_omp.links = 0;
+ sdata->u.mgd.uhr_omp.acked = false;
+
+ if (!acked) {
+ sdata_dbg(sdata, "UHR OMP frame not ACKed - disconnect\n");
+ __ieee80211_disconnect(sdata);
+ return;
+ }
+
+ ap = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (!ap)
+ return;
+
+ for_each_link_data(sdata, link) {
+ struct link_sta_info *link_sta;
+
+ if (!(links & BIT(link->link_id)))
+ continue;
+
+ /* only handle transition to enabled for now */
+ if (!link->u.mgd.conn.dbe_enabled)
+ continue;
+
+ link_sta = sdata_dereference(ap->link[link->link_id], sdata);
+ if (WARN_ON(!link_sta))
+ continue;
+
+ link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX;
+ ieee80211_link_sta_update_rc_bw(link, link_sta);
+ }
+
+ /* next round - send pending frames if needed */
+
+ if (sdata->u.mgd.uhr_omp.pending_init) {
+ links = sdata->u.mgd.uhr_omp.pending_init;
+
+ sdata->u.mgd.uhr_omp.pending_init = 0;
+ ieee80211_send_uhr_omp_req_dbe(sdata, links, true);
+ return;
+ }
+
+ if (sdata->u.mgd.uhr_omp.pending) {
+ links = sdata->u.mgd.uhr_omp.pending;
+
+ sdata->u.mgd.uhr_omp.pending = 0;
+ ieee80211_send_uhr_omp_req_dbe(sdata, links, false);
+ return;
+ }
+}
+
+static void ieee80211_uhr_omp_req_status_wk(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct ieee80211_sub_if_data *sdata =
+ container_of(work, struct ieee80211_sub_if_data,
+ u.mgd.uhr_omp.status_work.work);
+
+ ieee80211_uhr_omp_req_status(sdata);
+}
+
+static void ieee80211_process_uhr_omp_resp(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt)
+{
+ if (mgmt->u.action.uhr_link_reconf_notif.dialog_token !=
+ sdata->u.mgd.uhr_omp.dialog_token)
+ return;
+
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
+ &sdata->u.mgd.uhr_omp.status_work);
+ ieee80211_uhr_omp_req_status(sdata);
+}
+
+static void
+ieee80211_process_uhr_link_reconf_notif(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_mgmt *mgmt,
+ size_t len)
+{
+ switch (mgmt->u.action.uhr_link_reconf_notif.type) {
+ case IEEE80211_UHR_LINK_RECONFIG_NOTIFY_OMP_RESPONSE:
+ ieee80211_process_uhr_omp_resp(sdata, mgmt);
+ break;
+ }
+}
+
static void ieee80211_beacon_connection_loss_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
if (elems->uhr_operation && elems->uhr_cap &&
link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) {
+ int omp_to_us;
+
ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband,
elems->uhr_cap,
elems->uhr_cap_len,
link_sta);
bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr;
+
+ /*
+ * This assumes that the timeout is the same across all links,
+ * maybe we should actually validate that.
+ */
+ omp_to_us = ieee80211_uhr_capa_get_om_pu_to_us(elems->uhr_cap);
+ if (omp_to_us < 0) {
+ ret = false;
+ link_info(link, "Invalid UHR OMP timeout\n");
+ goto out;
+ }
+
+ sdata->u.mgd.uhr_omp.timeout_us = omp_to_us;
} else {
bss_conf->uhr_support = false;
}
static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
+ struct link_sta_info *link_sta,
int link_id,
struct cfg80211_bss *cbss, bool mlo,
struct ieee80211_conn_settings *conn,
lockdep_assert_wiphy(local->hw.wiphy);
rcu_read_lock();
- elems = ieee80211_determine_chan_mode(sdata, conn, cbss, link_id,
+ elems = ieee80211_determine_chan_mode(sdata, conn, cbss,
+ link_sta, link_id,
&chanreq, &ap_chandef,
userspace_selectors);
if (link_id != assoc_data->assoc_link_id) {
link->u.mgd.conn = assoc_data->link[link_id].conn;
- err = ieee80211_prep_channel(sdata, link, link_id, cbss,
+ err = ieee80211_prep_channel(sdata, link, link_sta,
+ link_id, cbss,
true, &link->u.mgd.conn,
sdata->u.mgd.userspace_selectors);
if (err) {
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);
+ ieee80211_send_uhr_omp_req_dbe(sdata, ~0, true);
+
return true;
out_err:
eth_zero_addr(sdata->vif.cfg.ap_addr);
ieee80211_neg_ttlm_timeout_work);
wiphy_work_init(&ifmgd->teardown_ttlm_work,
ieee80211_teardown_ttlm_work);
+ wiphy_hrtimer_work_init(&ifmgd->uhr_omp.status_work,
+ ieee80211_uhr_omp_req_status_wk);
ifmgd->flags = 0;
ifmgd->powersave = sdata->wdev.ps;
}
if (new_sta || override) {
+ struct link_sta_info *link_sta;
+ struct sta_info *ap;
+
/*
* Only set this if we're also going to calculate the AP
* settings etc., otherwise this was set before in a
* if the settings were changed.
*/
link->u.mgd.conn = *conn;
- err = ieee80211_prep_channel(sdata, link, link->link_id, cbss,
+
+ ap = new_sta ?: have_sta;
+ link_sta = sdata_dereference(ap->link[link->link_id], sdata);
+ if (!link_sta) {
+ err = -EINVAL;
+ if (new_sta)
+ sta_info_free(local, new_sta);
+ goto out_err;
+ }
+
+ err = ieee80211_prep_channel(sdata, link, link_sta,
+ link->link_id, cbss,
mlo, &link->u.mgd.conn,
userspace_selectors);
if (err) {
continue;
if (i == assoc_data->assoc_link_id)
continue;
- /* only calculate the mode, hence link == NULL */
- err = ieee80211_prep_channel(sdata, NULL, i,
+ /* only calculate the mode, hence link/link_sta == NULL */
+ err = ieee80211_prep_channel(sdata, NULL, NULL, i,
assoc_data->link[i].bss, true,
&assoc_data->link[i].conn,
sdata->u.mgd.userspace_selectors);
&ifmgd->csa_connection_drop_work);
wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
&ifmgd->tdls_peer_del_work);
+ wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy,
+ &ifmgd->uhr_omp.status_work);
if (ifmgd->assoc_data)
ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT);
link->conf->dtim_period = link->u.mgd.dtim_period ?: 1;
link->u.mgd.conn = add_links_data->link[link_id].conn;
- if (ieee80211_prep_channel(sdata, link, link_id, cbss,
+ if (ieee80211_prep_channel(sdata, link, link_sta, link_id, cbss,
true, &link->u.mgd.conn,
sdata->u.mgd.userspace_selectors)) {
link_info(link, "mlo: reconf: prep_channel failed\n");
ieee80211_recalc_ps(local);
ieee80211_recalc_ps_vif(sdata);
+ ieee80211_send_uhr_omp_req_dbe(sdata, link_mask, true);
+
done_data.buf = (const u8 *)mgmt;
done_data.len = orig_len;
done_data.added_links = link_mask;
continue;
/* only used to verify the mode, nothing is allocated */
- err = ieee80211_prep_channel(sdata, NULL, link_id,
+ err = ieee80211_prep_channel(sdata, NULL, NULL, link_id,
data->link[link_id].bss,
true,
&data->link[link_id].conn,
break;
}
break;
+ case WLAN_CATEGORY_PROTECTED_UHR:
+ switch (mgmt->u.action.action_code) {
+ case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY:
+ ieee80211_process_uhr_link_reconf_notif(sdata,
+ mgmt,
+ skb->len);
+ break;
+ }
+ break;
}
break;
}