linux/net/mac80211/link.c
Aditya Kumar Singh d74380ee99 wifi: mac80211: handle DFS per link
In order to support DFS with MLO, handle the link ID now passed from
cfg80211, adjust the code to do everything per link and call the
notifications to cfg80211 correctly.

Signed-off-by: Aditya Kumar Singh <quic_adisi@quicinc.com>
Link: https://patch.msgid.link/20240906064426.2101315-7-quic_adisi@quicinc.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2024-09-06 13:01:05 +02:00

543 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* MLO link handling
*
* Copyright (C) 2022-2024 Intel Corporation
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <net/mac80211.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "key.h"
#include "debugfs_netdev.h"
void ieee80211_link_setup(struct ieee80211_link_data *link)
{
if (link->sdata->vif.type == NL80211_IFTYPE_STATION)
ieee80211_mgd_setup_link(link);
}
void ieee80211_link_init(struct ieee80211_sub_if_data *sdata,
int link_id,
struct ieee80211_link_data *link,
struct ieee80211_bss_conf *link_conf)
{
bool deflink = link_id < 0;
if (link_id < 0)
link_id = 0;
rcu_assign_pointer(sdata->vif.link_conf[link_id], link_conf);
rcu_assign_pointer(sdata->link[link_id], link);
link->sdata = sdata;
link->link_id = link_id;
link->conf = link_conf;
link_conf->link_id = link_id;
link_conf->vif = &sdata->vif;
wiphy_work_init(&link->csa.finalize_work,
ieee80211_csa_finalize_work);
wiphy_work_init(&link->color_change_finalize_work,
ieee80211_color_change_finalize_work);
INIT_DELAYED_WORK(&link->color_collision_detect_work,
ieee80211_color_collision_detection_work);
INIT_LIST_HEAD(&link->assigned_chanctx_list);
INIT_LIST_HEAD(&link->reserved_chanctx_list);
wiphy_delayed_work_init(&link->dfs_cac_timer_work,
ieee80211_dfs_cac_timer_work);
if (!deflink) {
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP:
ether_addr_copy(link_conf->addr,
sdata->wdev.links[link_id].addr);
link_conf->bssid = link_conf->addr;
WARN_ON(!(sdata->wdev.valid_links & BIT(link_id)));
break;
case NL80211_IFTYPE_STATION:
/* station sets the bssid in ieee80211_mgd_setup_link */
break;
default:
WARN_ON(1);
}
ieee80211_link_debugfs_add(link);
}
}
void ieee80211_link_stop(struct ieee80211_link_data *link)
{
if (link->sdata->vif.type == NL80211_IFTYPE_STATION)
ieee80211_mgd_stop_link(link);
cancel_delayed_work_sync(&link->color_collision_detect_work);
wiphy_work_cancel(link->sdata->local->hw.wiphy,
&link->color_change_finalize_work);
wiphy_work_cancel(link->sdata->local->hw.wiphy,
&link->csa.finalize_work);
if (link->sdata->wdev.links[link->link_id].cac_started) {
wiphy_delayed_work_cancel(link->sdata->local->hw.wiphy,
&link->dfs_cac_timer_work);
cfg80211_cac_event(link->sdata->dev,
&link->conf->chanreq.oper,
NL80211_RADAR_CAC_ABORTED,
GFP_KERNEL, link->link_id);
}
ieee80211_link_release_channel(link);
}
struct link_container {
struct ieee80211_link_data data;
struct ieee80211_bss_conf conf;
};
static void ieee80211_tear_down_links(struct ieee80211_sub_if_data *sdata,
struct link_container **links, u16 mask)
{
struct ieee80211_link_data *link;
LIST_HEAD(keys);
unsigned int link_id;
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
if (!(mask & BIT(link_id)))
continue;
link = &links[link_id]->data;
if (link_id == 0 && !link)
link = &sdata->deflink;
if (WARN_ON(!link))
continue;
ieee80211_remove_link_keys(link, &keys);
ieee80211_link_debugfs_remove(link);
ieee80211_link_stop(link);
}
synchronize_rcu();
ieee80211_free_key_list(sdata->local, &keys);
}
static void ieee80211_free_links(struct ieee80211_sub_if_data *sdata,
struct link_container **links)
{
unsigned int link_id;
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++)
kfree(links[link_id]);
}
static int ieee80211_check_dup_link_addrs(struct ieee80211_sub_if_data *sdata)
{
unsigned int i, j;
for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) {
struct ieee80211_link_data *link1;
link1 = sdata_dereference(sdata->link[i], sdata);
if (!link1)
continue;
for (j = i + 1; j < IEEE80211_MLD_MAX_NUM_LINKS; j++) {
struct ieee80211_link_data *link2;
link2 = sdata_dereference(sdata->link[j], sdata);
if (!link2)
continue;
if (ether_addr_equal(link1->conf->addr,
link2->conf->addr))
return -EALREADY;
}
}
return 0;
}
static void ieee80211_set_vif_links_bitmaps(struct ieee80211_sub_if_data *sdata,
u16 valid_links, u16 dormant_links)
{
sdata->vif.valid_links = valid_links;
sdata->vif.dormant_links = dormant_links;
if (!valid_links ||
WARN((~valid_links & dormant_links) ||
!(valid_links & ~dormant_links),
"Invalid links: valid=0x%x, dormant=0x%x",
valid_links, dormant_links)) {
sdata->vif.active_links = 0;
sdata->vif.dormant_links = 0;
return;
}
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP:
/* in an AP all links are always active */
sdata->vif.active_links = valid_links;
/* AP links are not expected to be disabled */
WARN_ON(dormant_links);
break;
case NL80211_IFTYPE_STATION:
if (sdata->vif.active_links)
break;
sdata->vif.active_links = valid_links & ~dormant_links;
WARN_ON(hweight16(sdata->vif.active_links) > 1);
break;
default:
WARN_ON(1);
}
}
static int ieee80211_vif_update_links(struct ieee80211_sub_if_data *sdata,
struct link_container **to_free,
u16 new_links, u16 dormant_links)
{
u16 old_links = sdata->vif.valid_links;
u16 old_active = sdata->vif.active_links;
unsigned long add = new_links & ~old_links;
unsigned long rem = old_links & ~new_links;
unsigned int link_id;
int ret;
struct link_container *links[IEEE80211_MLD_MAX_NUM_LINKS] = {}, *link;
struct ieee80211_bss_conf *old[IEEE80211_MLD_MAX_NUM_LINKS];
struct ieee80211_link_data *old_data[IEEE80211_MLD_MAX_NUM_LINKS];
bool use_deflink = old_links == 0; /* set for error case */
lockdep_assert_wiphy(sdata->local->hw.wiphy);
memset(to_free, 0, sizeof(links));
if (old_links == new_links && dormant_links == sdata->vif.dormant_links)
return 0;
/* if there were no old links, need to clear the pointers to deflink */
if (!old_links)
rem |= BIT(0);
/* allocate new link structures first */
for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
link = kzalloc(sizeof(*link), GFP_KERNEL);
if (!link) {
ret = -ENOMEM;
goto free;
}
links[link_id] = link;
}
/* keep track of the old pointers for the driver */
BUILD_BUG_ON(sizeof(old) != sizeof(sdata->vif.link_conf));
memcpy(old, sdata->vif.link_conf, sizeof(old));
/* and for us in error cases */
BUILD_BUG_ON(sizeof(old_data) != sizeof(sdata->link));
memcpy(old_data, sdata->link, sizeof(old_data));
/* grab old links to free later */
for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
if (rcu_access_pointer(sdata->link[link_id]) != &sdata->deflink) {
/*
* we must have allocated the data through this path so
* we know we can free both at the same time
*/
to_free[link_id] = container_of(rcu_access_pointer(sdata->link[link_id]),
typeof(*links[link_id]),
data);
}
RCU_INIT_POINTER(sdata->link[link_id], NULL);
RCU_INIT_POINTER(sdata->vif.link_conf[link_id], NULL);
}
if (!old_links)
ieee80211_debugfs_recreate_netdev(sdata, true);
/* link them into data structures */
for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
WARN_ON(!use_deflink &&
rcu_access_pointer(sdata->link[link_id]) == &sdata->deflink);
link = links[link_id];
ieee80211_link_init(sdata, link_id, &link->data, &link->conf);
ieee80211_link_setup(&link->data);
}
if (new_links == 0)
ieee80211_link_init(sdata, -1, &sdata->deflink,
&sdata->vif.bss_conf);
ret = ieee80211_check_dup_link_addrs(sdata);
if (!ret) {
/* for keys we will not be able to undo this */
ieee80211_tear_down_links(sdata, to_free, rem);
ieee80211_set_vif_links_bitmaps(sdata, new_links, dormant_links);
/* tell the driver */
ret = drv_change_vif_links(sdata->local, sdata,
old_links & old_active,
new_links & sdata->vif.active_links,
old);
if (!new_links)
ieee80211_debugfs_recreate_netdev(sdata, false);
}
if (ret) {
/* restore config */
memcpy(sdata->link, old_data, sizeof(old_data));
memcpy(sdata->vif.link_conf, old, sizeof(old));
ieee80211_set_vif_links_bitmaps(sdata, old_links, dormant_links);
/* and free (only) the newly allocated links */
memset(to_free, 0, sizeof(links));
goto free;
}
/* use deflink/bss_conf again if and only if there are no more links */
use_deflink = new_links == 0;
goto deinit;
free:
/* if we failed during allocation, only free all */
for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) {
kfree(links[link_id]);
links[link_id] = NULL;
}
deinit:
if (use_deflink)
ieee80211_link_init(sdata, -1, &sdata->deflink,
&sdata->vif.bss_conf);
return ret;
}
int ieee80211_vif_set_links(struct ieee80211_sub_if_data *sdata,
u16 new_links, u16 dormant_links)
{
struct link_container *links[IEEE80211_MLD_MAX_NUM_LINKS];
int ret;
ret = ieee80211_vif_update_links(sdata, links, new_links,
dormant_links);
ieee80211_free_links(sdata, links);
return ret;
}
static int _ieee80211_set_active_links(struct ieee80211_sub_if_data *sdata,
u16 active_links)
{
struct ieee80211_bss_conf *link_confs[IEEE80211_MLD_MAX_NUM_LINKS];
struct ieee80211_local *local = sdata->local;
u16 old_active = sdata->vif.active_links;
unsigned long rem = old_active & ~active_links;
unsigned long add = active_links & ~old_active;
struct sta_info *sta;
unsigned int link_id;
int ret, i;
if (!ieee80211_sdata_running(sdata))
return -ENETDOWN;
if (sdata->vif.type != NL80211_IFTYPE_STATION)
return -EINVAL;
if (active_links & ~ieee80211_vif_usable_links(&sdata->vif))
return -EINVAL;
/* nothing to do */
if (old_active == active_links)
return 0;
for (i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++)
link_confs[i] = sdata_dereference(sdata->vif.link_conf[i],
sdata);
if (add) {
sdata->vif.active_links |= active_links;
ret = drv_change_vif_links(local, sdata,
old_active,
sdata->vif.active_links,
link_confs);
if (ret) {
sdata->vif.active_links = old_active;
return ret;
}
}
for_each_set_bit(link_id, &rem, IEEE80211_MLD_MAX_NUM_LINKS) {
struct ieee80211_link_data *link;
link = sdata_dereference(sdata->link[link_id], sdata);
ieee80211_teardown_tdls_peers(link);
__ieee80211_link_release_channel(link, true);
/*
* If CSA is (still) active while the link is deactivated,
* just schedule the channel switch work for the time we
* had previously calculated, and we'll take the process
* from there.
*/
if (link->conf->csa_active)
wiphy_delayed_work_queue(local->hw.wiphy,
&link->u.mgd.csa.switch_work,
link->u.mgd.csa.time -
jiffies);
}
list_for_each_entry(sta, &local->sta_list, list) {
if (sdata != sta->sdata)
continue;
/* this is very temporary, but do it anyway */
__ieee80211_sta_recalc_aggregates(sta,
old_active | active_links);
ret = drv_change_sta_links(local, sdata, &sta->sta,
old_active,
old_active | active_links);
WARN_ON_ONCE(ret);
}
ret = ieee80211_key_switch_links(sdata, rem, add);
WARN_ON_ONCE(ret);
list_for_each_entry(sta, &local->sta_list, list) {
if (sdata != sta->sdata)
continue;
__ieee80211_sta_recalc_aggregates(sta, active_links);
ret = drv_change_sta_links(local, sdata, &sta->sta,
old_active | active_links,
active_links);
WARN_ON_ONCE(ret);
/*
* Do it again, just in case - the driver might very
* well have called ieee80211_sta_recalc_aggregates()
* from there when filling in the new links, which
* would set it wrong since the vif's active links are
* not switched yet...
*/
__ieee80211_sta_recalc_aggregates(sta, active_links);
}
for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) {
struct ieee80211_link_data *link;
link = sdata_dereference(sdata->link[link_id], sdata);
/*
* This call really should not fail. Unfortunately, it appears
* that this may happen occasionally with some drivers. Should
* it happen, we are stuck in a bad place as going backwards is
* not really feasible.
*
* So lets just tell link_use_channel that it must not fail to
* assign the channel context (from mac80211's perspective) and
* assume the driver is going to trigger a recovery flow if it
* had a failure.
* That really is not great nor guaranteed to work. But at least
* the internal mac80211 state remains consistent and there is
* a chance that we can recover.
*/
ret = _ieee80211_link_use_channel(link,
&link->conf->chanreq,
IEEE80211_CHANCTX_SHARED,
true);
WARN_ON_ONCE(ret);
ieee80211_mgd_set_link_qos_params(link);
ieee80211_link_info_change_notify(sdata, link,
BSS_CHANGED_ERP_CTS_PROT |
BSS_CHANGED_ERP_PREAMBLE |
BSS_CHANGED_ERP_SLOT |
BSS_CHANGED_HT |
BSS_CHANGED_BASIC_RATES |
BSS_CHANGED_BSSID |
BSS_CHANGED_CQM |
BSS_CHANGED_QOS |
BSS_CHANGED_TXPOWER |
BSS_CHANGED_BANDWIDTH |
BSS_CHANGED_TWT |
BSS_CHANGED_HE_OBSS_PD |
BSS_CHANGED_HE_BSS_COLOR);
}
old_active = sdata->vif.active_links;
sdata->vif.active_links = active_links;
if (rem) {
ret = drv_change_vif_links(local, sdata, old_active,
active_links, link_confs);
WARN_ON_ONCE(ret);
}
return 0;
}
int ieee80211_set_active_links(struct ieee80211_vif *vif, u16 active_links)
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
struct ieee80211_local *local = sdata->local;
u16 old_active;
int ret;
lockdep_assert_wiphy(local->hw.wiphy);
if (WARN_ON(!active_links))
return -EINVAL;
old_active = sdata->vif.active_links;
if (old_active == active_links)
return 0;
if (!drv_can_activate_links(local, sdata, active_links))
return -EINVAL;
if (old_active & active_links) {
/*
* if there's at least one link that stays active across
* the change then switch to it (to those) first, and
* then enable the additional links
*/
ret = _ieee80211_set_active_links(sdata,
old_active & active_links);
if (!ret)
ret = _ieee80211_set_active_links(sdata, active_links);
} else {
/* otherwise switch directly */
ret = _ieee80211_set_active_links(sdata, active_links);
}
return ret;
}
EXPORT_SYMBOL_GPL(ieee80211_set_active_links);
void ieee80211_set_active_links_async(struct ieee80211_vif *vif,
u16 active_links)
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
if (WARN_ON(!active_links))
return;
if (!ieee80211_sdata_running(sdata))
return;
if (sdata->vif.type != NL80211_IFTYPE_STATION)
return;
if (active_links & ~ieee80211_vif_usable_links(&sdata->vif))
return;
/* nothing to do */
if (sdata->vif.active_links == active_links)
return;
sdata->desired_active_links = active_links;
wiphy_work_queue(sdata->local->hw.wiphy, &sdata->activate_links_work);
}
EXPORT_SYMBOL_GPL(ieee80211_set_active_links_async);