wifi: mac80211: Support link removal using Reconfiguration ML element

Add support for handling link removal indicated by the
Reconfiguration Multi-Link element.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Gregory Greenman <gregory.greenman@intel.com>
Link: https://lore.kernel.org/r/20230618214436.d8a046dc0c1a.I4dcf794da2a2d9f4e5f63a4b32158075d27c0660@changeid
[use cfg80211_links_removed() API instead]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Ilan Peer 2023-06-18 21:49:55 +03:00 committed by Johannes Berg
parent 79973d5cfd
commit 8eb8dd2ffb
3 changed files with 205 additions and 0 deletions

View file

@ -4891,6 +4891,39 @@ static inline bool ieee80211_mle_basic_sta_prof_size_ok(const u8 *data,
fixed + prof->sta_info_len <= len;
}
#define IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID 0x000f
#define IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE 0x0010
#define IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT 0x0020
#define IEEE80211_MLE_STA_RECONF_CONTROL_DELETE_TIMER_PRESENT 0x0040
/**
* ieee80211_mle_reconf_sta_prof_size_ok - validate reconfiguration multi-link
* element sta profile size.
* @data: pointer to the sub element data
* @len: length of the containing sub element
*/
static inline bool ieee80211_mle_reconf_sta_prof_size_ok(const u8 *data,
size_t len)
{
const struct ieee80211_mle_per_sta_profile *prof = (const void *)data;
u16 control;
u8 fixed = sizeof(*prof);
u8 info_len = 1;
if (len < fixed)
return false;
control = le16_to_cpu(prof->control);
if (control & IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT)
info_len += ETH_ALEN;
if (control & IEEE80211_MLE_STA_RECONF_CONTROL_DELETE_TIMER_PRESENT)
info_len += 2;
return prof->sta_info_len >= info_len &&
fixed + prof->sta_info_len - 1 <= len;
}
#define for_each_mle_subelement(_elem, _data, _len) \
if (ieee80211_mle_size_ok(_data, _len)) \
for_each_element(_elem, \

View file

@ -551,6 +551,9 @@ struct ieee80211_if_managed {
*/
u8 *assoc_req_ies;
size_t assoc_req_ies_len;
struct wiphy_delayed_work ml_reconf_work;
u16 removed_links;
};
struct ieee80211_if_ibss {

View file

@ -5608,6 +5608,169 @@ static bool ieee80211_config_puncturing(struct ieee80211_link_data *link,
return true;
}
static void ieee80211_ml_reconf_work(struct wiphy *wiphy,
struct wiphy_work *work)
{
struct ieee80211_sub_if_data *sdata =
container_of(work, struct ieee80211_sub_if_data,
u.mgd.ml_reconf_work.work);
u16 new_valid_links, new_active_links, new_dormant_links;
int ret;
sdata_lock(sdata);
if (!sdata->u.mgd.removed_links) {
sdata_unlock(sdata);
return;
}
sdata_info(sdata,
"MLO Reconfiguration: work: valid=0x%x, removed=0x%x\n",
sdata->vif.valid_links, sdata->u.mgd.removed_links);
new_valid_links = sdata->vif.valid_links & ~sdata->u.mgd.removed_links;
if (new_valid_links == sdata->vif.valid_links) {
sdata_unlock(sdata);
return;
}
if (!new_valid_links ||
!(new_valid_links & ~sdata->vif.dormant_links)) {
sdata_info(sdata, "No valid links after reconfiguration\n");
ret = -EINVAL;
goto out;
}
new_active_links = sdata->vif.active_links & ~sdata->u.mgd.removed_links;
if (new_active_links != sdata->vif.active_links) {
if (!new_active_links)
new_active_links =
BIT(ffs(new_valid_links &
~sdata->vif.dormant_links) - 1);
ret = __ieee80211_set_active_links(&sdata->vif,
new_active_links);
if (ret) {
sdata_info(sdata,
"Failed setting active links\n");
goto out;
}
}
new_dormant_links = sdata->vif.dormant_links & ~sdata->u.mgd.removed_links;
ret = ieee80211_vif_set_links(sdata, new_valid_links,
new_dormant_links);
if (ret)
sdata_info(sdata, "Failed setting valid links\n");
out:
if (!ret)
cfg80211_links_removed(sdata->dev, sdata->u.mgd.removed_links);
else
___ieee80211_disconnect(sdata);
sdata->u.mgd.removed_links = 0;
sdata_unlock(sdata);
}
static void ieee80211_ml_reconfiguration(struct ieee80211_sub_if_data *sdata,
struct ieee802_11_elems *elems)
{
const struct ieee80211_multi_link_elem *ml;
const struct element *sub;
size_t ml_len;
unsigned long removed_links = 0;
u16 link_removal_timeout[IEEE80211_MLD_MAX_NUM_LINKS] = {};
u8 link_id;
u32 delay;
if (!ieee80211_vif_is_mld(&sdata->vif) || !elems->ml_reconf)
return;
ml_len = cfg80211_defragment_element(elems->ml_reconf_elem,
elems->ie_start,
elems->total_len,
elems->scratch_pos,
elems->scratch + elems->scratch_len -
elems->scratch_pos,
WLAN_EID_FRAGMENT);
elems->ml_reconf = (const void *)elems->scratch_pos;
elems->ml_reconf_len = ml_len;
ml = elems->ml_reconf;
/* Directly parse the sub elements as the common information doesn't
* hold any useful information.
*/
for_each_mle_subelement(sub, (u8 *)ml, ml_len) {
struct ieee80211_mle_per_sta_profile *prof = (void *)sub->data;
u8 *pos = prof->variable;
u16 control;
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;
removed_links |= BIT(link_id);
/* the MAC address should not be included, but handle it */
if (control &
IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT)
pos += 6;
/* According to Draft P802.11be_D3.0, the control should
* include the AP Removal Timer present. If the AP Removal Timer
* is not present assume immediate removal.
*/
if (control &
IEEE80211_MLE_STA_RECONF_CONTROL_DELETE_TIMER_PRESENT)
link_removal_timeout[link_id] = le16_to_cpu(*(__le16 *)pos);
}
removed_links &= sdata->vif.valid_links;
if (!removed_links) {
/* In case the removal was cancelled, abort it */
if (sdata->u.mgd.removed_links) {
sdata->u.mgd.removed_links = 0;
wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
&sdata->u.mgd.ml_reconf_work);
}
return;
}
delay = 0;
for_each_set_bit(link_id, &removed_links, IEEE80211_MLD_MAX_NUM_LINKS) {
struct ieee80211_bss_conf *link_conf =
sdata_dereference(sdata->vif.link_conf[link_id], sdata);
u32 link_delay;
if (!link_conf) {
removed_links &= ~BIT(link_id);
continue;
}
link_delay = link_conf->beacon_int *
link_removal_timeout[link_id];
if (!delay)
delay = link_delay;
else
delay = min(delay, link_delay);
}
sdata->u.mgd.removed_links = removed_links;
wiphy_delayed_work_queue(sdata->local->hw.wiphy,
&sdata->u.mgd.ml_reconf_work,
TU_TO_JIFFIES(delay));
}
static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
struct ieee80211_hdr *hdr, size_t len,
struct ieee80211_rx_status *rx_status)
@ -5937,6 +6100,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_link_data *link,
}
}
ieee80211_ml_reconfiguration(sdata, elems);
ieee80211_link_info_change_notify(sdata, link, changed);
free:
kfree(elems);
@ -6563,6 +6728,8 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata)
ieee80211_csa_connection_drop_work);
INIT_DELAYED_WORK(&ifmgd->tdls_peer_del_work,
ieee80211_tdls_peer_del_work);
wiphy_delayed_work_init(&ifmgd->ml_reconf_work,
ieee80211_ml_reconf_work);
timer_setup(&ifmgd->timer, ieee80211_sta_timer, 0);
timer_setup(&ifmgd->bcn_mon_timer, ieee80211_sta_bcn_mon_timer, 0);
timer_setup(&ifmgd->conn_mon_timer, ieee80211_sta_conn_mon_timer, 0);
@ -7575,6 +7742,8 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata)
wiphy_work_cancel(sdata->local->hw.wiphy,
&ifmgd->csa_connection_drop_work);
cancel_delayed_work_sync(&ifmgd->tdls_peer_del_work);
wiphy_delayed_work_cancel(sdata->local->hw.wiphy,
&ifmgd->ml_reconf_work);
sdata_lock(sdata);
if (ifmgd->assoc_data)