]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: iwlwifi: mld: evacuate NAN channels on link switch
authorMiri Korenblit <miriam.rachel.korenblit@intel.com>
Fri, 15 May 2026 12:09:38 +0000 (15:09 +0300)
committerMiri Korenblit <miriam.rachel.korenblit@intel.com>
Tue, 26 May 2026 12:17:12 +0000 (15:17 +0300)
The FW API doesn't allow a station (of certain types, including
STATION_TYPE_PEER) to not be correlated to any link. Therefore,
when switching links, mac80211 first adds the new link and then
removes the old one.

In case we have a NAN interface that operates on 3 links and a
BSS interface that operates on the 4th link, there won't be any
room to perform a link switch that temporarily needs two links
for the BSS interface, but the firmware only has four total.

To mitigate this, try to evacuate a NAN channel in this scenario:
First try to evacuate a NAN channel using the same phy as the BSS
link being deactivated, since we expect NAN to follow the BSS
channel configuration. If that doesn't work, try to evacuate any
NAN channel.

Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Link: https://patch.msgid.link/20260515150751.a193c0f41b1f.I1d56c8d8d61d110422271971843b71a93f5ec354@changeid
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
drivers/net/wireless/intel/iwlwifi/mld/mac80211.c

index 9dc540720ecd9d00d2feeda4f11671bd41c14c33..92858b8f7395c35c565e2e911c8f8d5b59178193 100644 (file)
@@ -2585,15 +2585,108 @@ iwl_mld_mac80211_mgd_protect_tdls_discover(struct ieee80211_hw *hw,
                        ret);
 }
 
+static int iwl_mld_count_free_link_ids(struct iwl_mld *mld)
+{
+       int free_count = 0;
+
+       for (int i = 0; i < mld->fw->ucode_capa.num_links; i++) {
+               if (!rcu_access_pointer(mld->fw_id_to_bss_conf[i]))
+                       free_count++;
+       }
+
+       return free_count;
+}
+
+static bool
+iwl_mld_chanctx_used_by_other_vif(struct ieee80211_hw *hw,
+                                 struct ieee80211_vif *vif,
+                                 struct ieee80211_chanctx_conf *chanctx_conf)
+{
+       struct ieee80211_bss_conf *iter_link;
+       struct ieee80211_vif *iter_vif;
+       int link_id;
+
+       for_each_active_interface(iter_vif, hw) {
+               if (vif == iter_vif)
+                       continue;
+
+               /* NAN doesn't have active links, so we don't count NAN users */
+               for_each_vif_active_link(iter_vif, iter_link, link_id) {
+                       if (rcu_access_pointer(iter_link->chanctx_conf) ==
+                           chanctx_conf)
+                               return true;
+               }
+       }
+
+       return false;
+}
+
 static bool iwl_mld_can_activate_links(struct ieee80211_hw *hw,
                                       struct ieee80211_vif *vif,
                                       u16 desired_links)
 {
        struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
        int n_links = hweight16(desired_links);
+       int n_add = hweight16(desired_links & ~vif->active_links);
+       unsigned long to_deactivate = vif->active_links & ~desired_links;
+       int free_link_ids;
+       int i;
 
        /* Check if HW supports the wanted number of links */
-       return n_links <= iwl_mld_max_active_links(mld, vif);
+       if (n_links > iwl_mld_max_active_links(mld, vif))
+               return false;
+
+       /*
+        * During link switch, mac80211 first adds the new links, then removes
+        * the old ones. This means we temporarily need extra link objects
+        * during the transition. Check if we have enough free link IDs.
+        */
+
+       free_link_ids = iwl_mld_count_free_link_ids(mld);
+
+       if (free_link_ids >= n_add)
+               return true;
+
+       if (!mld->nan_device_vif)
+               return false;
+
+       /*
+        * Not enough free link IDs. First try to evacuate NAN from the
+        * channel context of a link that is going to be deactivated.
+        */
+       for_each_set_bit(i, &to_deactivate, IEEE80211_MLD_MAX_NUM_LINKS) {
+               struct ieee80211_bss_conf *link_conf;
+               struct ieee80211_chanctx_conf *chanctx_conf;
+
+               link_conf = link_conf_dereference_protected(vif, i);
+               if (!link_conf)
+                       continue;
+
+               chanctx_conf = wiphy_dereference(mld->wiphy, link_conf->chanctx_conf);
+               if (!chanctx_conf)
+                       continue;
+
+               if (iwl_mld_chanctx_used_by_other_vif(hw, vif, chanctx_conf))
+                       continue;
+
+               if (ieee80211_nan_try_evacuate(hw, chanctx_conf)) {
+                       free_link_ids = iwl_mld_count_free_link_ids(mld);
+                       /*
+                        * Evacuation of one channel should do the job. If not,
+                        * something bad is happening. Don't try to evacuate more
+                        */
+                       return free_link_ids >= n_add;
+               }
+       }
+
+       /* Couldn't find/evacuate any channel going to go unused, try any */
+       if (ieee80211_nan_try_evacuate(hw, NULL)) {
+               free_link_ids = iwl_mld_count_free_link_ids(mld);
+               if (free_link_ids >= n_add)
+                       return true;
+       }
+
+       return false;
 }
 
 static int