wifi: mac80211: implement link switching
authorJohannes Berg <johannes.berg@intel.com>
Fri, 2 Sep 2022 14:12:56 +0000 (16:12 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 6 Sep 2022 08:17:20 +0000 (10:17 +0200)
Implement an API function and debugfs file to switch
active links.

Also provide an async version of the API so drivers
can call it in arbitrary contexts, e.g. while in the
authorized callback.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/debugfs_netdev.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/key.c
net/mac80211/key.h
net/mac80211/link.c

index 873e81a45a973775fa3a4c637b68876840591b7b..ac2bad57933f866dfad6c6cace1e5d1d3f08a93f 100644 (file)
@@ -7184,4 +7184,45 @@ static inline bool ieee80211_is_tx_data(struct sk_buff *skb)
               ieee80211_is_data(hdr->frame_control);
 }
 
+/**
+ * ieee80211_set_active_links - set active links in client mode
+ * @vif: interface to set active links on
+ * @active_links: the new active links bitmap
+ *
+ * This changes the active links on an interface. The interface
+ * must be in client mode (in AP mode, all links are always active),
+ * and @active_links must be a subset of the vif's valid_links.
+ *
+ * If a link is switched off and another is switched on at the same
+ * time (e.g. active_links going from 0x1 to 0x10) then you will get
+ * a sequence of calls like
+ *  - change_vif_links(0x11)
+ *  - unassign_vif_chanctx(link_id=0)
+ *  - change_sta_links(0x11) for each affected STA (the AP)
+ *    (TDLS connections on now inactive links should be torn down)
+ *  - remove group keys on the old link (link_id 0)
+ *  - add new group keys (GTK/IGTK/BIGTK) on the new link (link_id 4)
+ *  - change_sta_links(0x10) for each affected STA (the AP)
+ *  - assign_vif_chanctx(link_id=4)
+ *  - change_vif_links(0x10)
+ *
+ * Note: This function acquires some mac80211 locks and must not
+ *      be called with any driver locks held that could cause a
+ *      lock dependency inversion. Best call it without locks.
+ */
+int ieee80211_set_active_links(struct ieee80211_vif *vif, u16 active_links);
+
+/**
+ * ieee80211_set_active_links_async - asynchronously set active links
+ * @vif: interface to set active links on
+ * @active_links: the new active links bitmap
+ *
+ * See ieee80211_set_active_links() for more information, the only
+ * difference here is that the link change is triggered async and
+ * can be called in any context, but the link switch will only be
+ * completed after it returns.
+ */
+void ieee80211_set_active_links_async(struct ieee80211_vif *vif,
+                                     u16 active_links);
+
 #endif /* MAC80211_H */
index 1e5b041a5cea53e754661fa6be6c7de52f38570a..5b014786fd2d0a8735221cc3d81075b3c3e11a7c 100644 (file)
@@ -570,6 +570,30 @@ static ssize_t ieee80211_if_parse_tsf(
 }
 IEEE80211_IF_FILE_RW(tsf);
 
+static ssize_t ieee80211_if_fmt_valid_links(const struct ieee80211_sub_if_data *sdata,
+                                           char *buf, int buflen)
+{
+       return snprintf(buf, buflen, "0x%x\n", sdata->vif.valid_links);
+}
+IEEE80211_IF_FILE_R(valid_links);
+
+static ssize_t ieee80211_if_fmt_active_links(const struct ieee80211_sub_if_data *sdata,
+                                            char *buf, int buflen)
+{
+       return snprintf(buf, buflen, "0x%x\n", sdata->vif.active_links);
+}
+
+static ssize_t ieee80211_if_parse_active_links(struct ieee80211_sub_if_data *sdata,
+                                              const char *buf, int buflen)
+{
+       u16 active_links;
+
+       if (kstrtou16(buf, 0, &active_links))
+               return -EINVAL;
+
+       return ieee80211_set_active_links(&sdata->vif, active_links) ?: buflen;
+}
+IEEE80211_IF_FILE_RW(active_links);
 
 #ifdef CONFIG_MAC80211_MESH
 IEEE80211_IF_FILE(estab_plinks, u.mesh.estab_plinks, ATOMIC);
@@ -670,6 +694,8 @@ static void add_sta_files(struct ieee80211_sub_if_data *sdata)
        DEBUGFS_ADD_MODE(uapsd_queues, 0600);
        DEBUGFS_ADD_MODE(uapsd_max_sp_len, 0600);
        DEBUGFS_ADD_MODE(tdls_wider_bw, 0600);
+       DEBUGFS_ADD_MODE(valid_links, 0200);
+       DEBUGFS_ADD_MODE(active_links, 0600);
 }
 
 static void add_ap_files(struct ieee80211_sub_if_data *sdata)
index 977aea4467e04c5c45c8cc280a8713bc35e52eb9..4e1d4c339f2de362eac9d804e9deb088c9052639 100644 (file)
@@ -1081,6 +1081,10 @@ struct ieee80211_sub_if_data {
        struct ieee80211_link_data deflink;
        struct ieee80211_link_data __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
 
+       /* for ieee80211_set_active_links_async() */
+       struct work_struct activate_links_work;
+       u16 desired_active_links;
+
 #ifdef CONFIG_MAC80211_DEBUGFS
        struct {
                struct dentry *subdir_stations;
index f99685e2d63330b453b98b479bdd7ce959604ff0..572254366a0f80049b63115f7924b26f15a3bb32 100644 (file)
@@ -754,6 +754,8 @@ static int ieee80211_stop(struct net_device *dev)
                ieee80211_stop_mbssid(sdata);
        }
 
+       cancel_work_sync(&sdata->activate_links_work);
+
        wiphy_lock(sdata->local->hw.wiphy);
        ieee80211_do_stop(sdata, true);
        wiphy_unlock(sdata->local->hw.wiphy);
@@ -1724,6 +1726,15 @@ static void ieee80211_recalc_smps_work(struct work_struct *work)
        ieee80211_recalc_smps(sdata, &sdata->deflink);
 }
 
+static void ieee80211_activate_links_work(struct work_struct *work)
+{
+       struct ieee80211_sub_if_data *sdata =
+               container_of(work, struct ieee80211_sub_if_data,
+                            activate_links_work);
+
+       ieee80211_set_active_links(&sdata->vif, sdata->desired_active_links);
+}
+
 /*
  * Helper function to initialise an interface to a specific type.
  */
@@ -1761,6 +1772,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
        skb_queue_head_init(&sdata->status_queue);
        INIT_WORK(&sdata->work, ieee80211_iface_work);
        INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
+       INIT_WORK(&sdata->activate_links_work, ieee80211_activate_links_work);
 
        switch (type) {
        case NL80211_IFTYPE_P2P_GO:
index f6f0f65fb2557daa9e74807d7d506b0cacdc00ee..e8f6c1e5eabfc76db97255a7622ec67032ef6fa7 100644 (file)
@@ -1445,3 +1445,37 @@ void ieee80211_key_replay(struct ieee80211_key_conf *keyconf)
        }
 }
 EXPORT_SYMBOL_GPL(ieee80211_key_replay);
+
+int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
+                              unsigned long del_links_mask,
+                              unsigned long add_links_mask)
+{
+       struct ieee80211_key *key;
+       int ret;
+
+       list_for_each_entry(key, &sdata->key_list, list) {
+               if (key->conf.link_id < 0 ||
+                   !(del_links_mask & BIT(key->conf.link_id)))
+                       continue;
+
+               /* shouldn't happen for per-link keys */
+               WARN_ON(key->sta);
+
+               ieee80211_key_disable_hw_accel(key);
+       }
+
+       list_for_each_entry(key, &sdata->key_list, list) {
+               if (key->conf.link_id < 0 ||
+                   !(add_links_mask & BIT(key->conf.link_id)))
+                       continue;
+
+               /* shouldn't happen for per-link keys */
+               WARN_ON(key->sta);
+
+               ret = ieee80211_key_enable_hw_accel(key);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
index 518af24aab56e6149e4e66c7296f602683cf30e3..f3df97df4b72b7b9e6b46907fa91bef64e186d55 100644 (file)
@@ -165,6 +165,9 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
 void ieee80211_free_sta_keys(struct ieee80211_local *local,
                             struct sta_info *sta);
 void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata);
+int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
+                              unsigned long del_links_mask,
+                              unsigned long add_links_mask);
 
 #define key_mtx_dereference(local, ref) \
        rcu_dereference_protected(ref, lockdep_is_held(&((local)->key_mtx)))
index 8df348a5edce7ca64b9774403eeff5f75dc314dd..e309708abae8b2e708b2608d26e11b86c0476ec2 100644 (file)
@@ -9,6 +9,7 @@
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
 #include "driver-ops.h"
+#include "key.h"
 
 void ieee80211_link_setup(struct ieee80211_link_data *link)
 {
@@ -300,3 +301,173 @@ void ieee80211_vif_clear_links(struct ieee80211_sub_if_data *sdata)
 
        ieee80211_free_links(sdata, links);
 }
+
+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;
+
+       /* cannot activate links that don't exist */
+       if (active_links & ~sdata->vif.valid_links)
+               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);
+
+               /* FIXME: kill TDLS connections on the link */
+
+               ieee80211_link_release_channel(link);
+       }
+
+       list_for_each_entry(sta, &local->sta_list, list) {
+               if (sdata != sta->sdata)
+                       continue;
+               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;
+               ret = drv_change_sta_links(local, sdata, &sta->sta,
+                                          old_active | active_links,
+                                          active_links);
+               WARN_ON_ONCE(ret);
+       }
+
+       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);
+
+               ret = ieee80211_link_use_channel(link, &link->conf->chandef,
+                                                IEEE80211_CHANCTX_SHARED);
+               WARN_ON_ONCE(ret);
+
+               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);
+               ieee80211_mgd_set_link_qos_params(link);
+       }
+
+       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;
+
+       sdata_lock(sdata);
+       mutex_lock(&local->sta_mtx);
+       mutex_lock(&local->mtx);
+       mutex_lock(&local->key_mtx);
+       old_active = sdata->vif.active_links;
+       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);
+       }
+       mutex_unlock(&local->key_mtx);
+       mutex_unlock(&local->mtx);
+       mutex_unlock(&local->sta_mtx);
+       sdata_unlock(sdata);
+
+       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 (!ieee80211_sdata_running(sdata))
+               return;
+
+       if (sdata->vif.type != NL80211_IFTYPE_STATION)
+               return;
+
+       /* cannot activate links that don't exist */
+       if (active_links & ~sdata->vif.valid_links)
+               return;
+
+       /* nothing to do */
+       if (sdata->vif.active_links == active_links)
+               return;
+
+       sdata->desired_active_links = active_links;
+       schedule_work(&sdata->activate_links_work);
+}
+EXPORT_SYMBOL_GPL(ieee80211_set_active_links_async);