]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: iwlwifi: mld: support channel survey collection for ACS scans
authorBenjamin Berg <benjamin.berg@intel.com>
Wed, 23 Jul 2025 06:45:03 +0000 (09:45 +0300)
committerMiri Korenblit <miriam.rachel.korenblit@intel.com>
Wed, 23 Jul 2025 11:10:24 +0000 (14:10 +0300)
The firmware is able to collect channel statistics when doing passive
scans. Enable the flag when doing a passive scan on an AP interface and
collect the survey information.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20250723094230.a659ef1b1fd8.I24a9a0383327c231f36be170968bc7bac801f9f2@changeid
drivers/net/wireless/intel/iwlwifi/mld/mac80211.c
drivers/net/wireless/intel/iwlwifi/mld/mld.c
drivers/net/wireless/intel/iwlwifi/mld/mld.h
drivers/net/wireless/intel/iwlwifi/mld/notif.c
drivers/net/wireless/intel/iwlwifi/mld/scan.c
drivers/net/wireless/intel/iwlwifi/mld/scan.h

index d929cf2e73fddf03cfe8988f9cd2a638c9567fdf..c6e61c843f770a0d2b1a1cd2133a3f8b5abba3c3 100644 (file)
@@ -2675,6 +2675,7 @@ const struct ieee80211_ops iwl_mld_hw_ops = {
        .mgd_complete_tx = iwl_mld_mac_mgd_complete_tx,
        .sta_state = iwl_mld_mac80211_sta_state,
        .sta_statistics = iwl_mld_mac80211_sta_statistics,
+       .get_survey = iwl_mld_mac80211_get_survey,
        .flush = iwl_mld_mac80211_flush,
        .flush_sta = iwl_mld_mac80211_flush_sta,
        .ampdu_action = iwl_mld_mac80211_ampdu_action,
index e7cbfb9009af454b678340e4c96d146681f5f6ab..e78e7a4f39d100dd32a20010fc2ff410d2c3618f 100644 (file)
@@ -259,6 +259,13 @@ static const struct iwl_hcmd_names iwl_mld_data_path_names[] = {
        HCMD_NAME(MU_GROUP_MGMT_NOTIF),
 };
 
+/* Please keep this array *SORTED* by hex value.
+ * Access is done through binary search
+ */
+static const struct iwl_hcmd_names iwl_mld_scan_names[] = {
+       HCMD_NAME(CHANNEL_SURVEY_NOTIF),
+};
+
 /* Please keep this array *SORTED* by hex value.
  * Access is done through binary search
  */
@@ -310,6 +317,7 @@ const struct iwl_hcmd_arr iwl_mld_groups[] = {
        [SYSTEM_GROUP] = HCMD_ARR(iwl_mld_system_names),
        [MAC_CONF_GROUP] = HCMD_ARR(iwl_mld_mac_conf_names),
        [DATA_PATH_GROUP] = HCMD_ARR(iwl_mld_data_path_names),
+       [SCAN_GROUP] = HCMD_ARR(iwl_mld_scan_names),
        [LOCATION_GROUP] = HCMD_ARR(iwl_mld_location_names),
        [REGULATORY_AND_NVM_GROUP] = HCMD_ARR(iwl_mld_reg_and_nvm_names),
        [DEBUG_GROUP] = HCMD_ARR(iwl_mld_debug_names),
@@ -507,6 +515,7 @@ iwl_op_mode_mld_stop(struct iwl_op_mode *op_mode)
 
        kfree(mld->nvm_data);
        kfree(mld->scan.cmd);
+       kfree(mld->channel_survey);
        kfree(mld->error_recovery_buf);
        kfree(mld->mcast_filter_cmd);
 
index 8bc4749599cad03679f13b69584254a84d4e31c9..94dc9da6360dc6040aab1211e0037ade24853f7a 100644 (file)
  *     device
  * @addresses: device MAC addresses.
  * @scan: instance of the scan object
+ * @channel_survey: channel survey information collected during scan
  * @wowlan: WoWLAN support data.
  * @debug_max_sleep: maximum sleep time in D3 (for debug purposes)
  * @led: the led device
@@ -253,6 +254,7 @@ struct iwl_mld {
 
        struct mac_address addresses[IWL_MLD_MAX_ADDRESSES];
        struct iwl_mld_scan scan;
+       struct iwl_mld_survey *channel_survey;
 #ifdef CONFIG_PM_SLEEP
        struct wiphy_wowlan_support wowlan;
        u32 debug_max_sleep;
index 262d8e25e62a898cc231eeb9bebce3e73b3c3c3f..3cb700a9708e3f7c4352f1279f962e974f814332 100644 (file)
@@ -295,6 +295,8 @@ CMD_VERSIONS(scan_complete_notif,
             CMD_VER_ENTRY(1, iwl_umac_scan_complete))
 CMD_VERSIONS(scan_iter_complete_notif,
             CMD_VER_ENTRY(2, iwl_umac_scan_iter_complete_notif))
+CMD_VERSIONS(channel_survey_notif,
+            CMD_VER_ENTRY(1, iwl_umac_scan_channel_survey_notif))
 CMD_VERSIONS(mfuart_notif,
             CMD_VER_ENTRY(2, iwl_mfuart_load_notif))
 CMD_VERSIONS(update_mcc,
@@ -415,6 +417,10 @@ const struct iwl_rx_handler iwl_mld_rx_handlers[] = {
        RX_HANDLER_NO_VAL(LEGACY_GROUP, MATCH_FOUND_NOTIFICATION,
                          match_found_notif, RX_HANDLER_SYNC)
 
+       RX_HANDLER_NO_OBJECT(SCAN_GROUP, CHANNEL_SURVEY_NOTIF,
+                            channel_survey_notif,
+                            RX_HANDLER_ASYNC)
+
        RX_HANDLER_NO_OBJECT(STATISTICS_GROUP, STATISTICS_OPER_NOTIF,
                             stats_oper_notif, RX_HANDLER_ASYNC)
        RX_HANDLER_NO_OBJECT(STATISTICS_GROUP, STATISTICS_OPER_PART1_NOTIF,
index 479a76a94aa80c65641b9d2a0c02fc6789cd6e21..62f97a18a16c1cb0eba8c86661030dda6cdd14e3 100644 (file)
@@ -4,6 +4,8 @@
  */
 #include <linux/crc32.h>
 
+#include "iwl-utils.h"
+
 #include "mld.h"
 #include "scan.h"
 #include "hcmd.h"
@@ -482,7 +484,9 @@ iwl_mld_scan_get_cmd_gen_flags(struct iwl_mld *mld,
 static u8
 iwl_mld_scan_get_cmd_gen_flags2(struct iwl_mld *mld,
                                struct iwl_mld_scan_params *params,
-                               struct ieee80211_vif *vif, u16 gen_flags)
+                               struct ieee80211_vif *vif,
+                               enum iwl_mld_scan_status scan_status,
+                               u16 gen_flags)
 {
        u8 flags = 0;
 
@@ -494,6 +498,17 @@ iwl_mld_scan_get_cmd_gen_flags2(struct iwl_mld *mld,
        if (params->scan_6ghz)
                flags |= IWL_UMAC_SCAN_GEN_PARAMS_FLAGS2_DONT_TOGGLE_ANT;
 
+       /* For AP interfaces, request survey data for regular scans and if
+        * it is supported. For non-AP interfaces, EBS will be enabled and
+        * the results may be missing information for some channels.
+        */
+       if (scan_status == IWL_MLD_SCAN_REGULAR &&
+           ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_AP &&
+           gen_flags & IWL_UMAC_SCAN_GEN_FLAGS_V2_FORCE_PASSIVE &&
+           iwl_fw_lookup_notif_ver(mld->fw, SCAN_GROUP,
+                                   CHANNEL_SURVEY_NOTIF, 0) >= 1)
+               flags |= IWL_UMAC_SCAN_GEN_FLAGS2_COLLECT_CHANNEL_STATS;
+
        return flags;
 }
 
@@ -544,6 +559,7 @@ iwl_mld_scan_cmd_set_gen_params(struct iwl_mld *mld,
        u16 gen_flags = iwl_mld_scan_get_cmd_gen_flags(mld, params, vif,
                                                       scan_status);
        u8 gen_flags2 = iwl_mld_scan_get_cmd_gen_flags2(mld, params, vif,
+                                                       scan_status,
                                                        gen_flags);
 
        IWL_DEBUG_SCAN(mld, "General: flags=0x%x, flags2=0x%x\n",
@@ -1752,6 +1768,11 @@ int iwl_mld_regular_scan_start(struct iwl_mld *mld, struct ieee80211_vif *vif,
                               struct cfg80211_scan_request *req,
                               struct ieee80211_scan_ies *ies)
 {
+       /* Clear survey data when starting the first part of a regular scan */
+       if (req->first_part && mld->channel_survey)
+               memset(mld->channel_survey->channels, 0,
+                      sizeof(mld->channel_survey->channels[0]) *
+                      mld->channel_survey->n_channels);
 
        if (vif->type == NL80211_IFTYPE_P2P_DEVICE)
                iwl_mld_emlsr_block_tmp_non_bss(mld);
@@ -2025,3 +2046,136 @@ int iwl_mld_alloc_scan_cmd(struct iwl_mld *mld)
 
        return 0;
 }
+
+static int iwl_mld_chanidx_from_phy(struct iwl_mld *mld,
+                                   enum nl80211_band band,
+                                   u16 phy_chan_num)
+{
+       struct ieee80211_supported_band *sband = mld->wiphy->bands[band];
+
+       if (WARN_ON_ONCE(!sband))
+               return -EINVAL;
+
+       for (int chan_idx = 0; chan_idx < sband->n_channels; chan_idx++) {
+               struct ieee80211_channel *channel = &sband->channels[chan_idx];
+
+               if (channel->hw_value == phy_chan_num)
+                       return chan_idx;
+       }
+
+       return -EINVAL;
+}
+
+void iwl_mld_handle_channel_survey_notif(struct iwl_mld *mld,
+                                        struct iwl_rx_packet *pkt)
+{
+       const struct iwl_umac_scan_channel_survey_notif *notif =
+               (void *)pkt->data;
+       struct iwl_mld_survey_channel *info;
+       enum nl80211_band band;
+       int chan_idx;
+
+       if (!mld->channel_survey) {
+               size_t n_channels = 0;
+
+               for (band = 0; band < NUM_NL80211_BANDS; band++) {
+                       if (!mld->wiphy->bands[band])
+                               continue;
+
+                       n_channels += mld->wiphy->bands[band]->n_channels;
+               }
+
+               mld->channel_survey = kzalloc(struct_size(mld->channel_survey,
+                                                         channels, n_channels),
+                                                         GFP_KERNEL);
+
+               if (!mld->channel_survey)
+                       return;
+
+               mld->channel_survey->n_channels = n_channels;
+               n_channels = 0;
+               for (band = 0; band < NUM_NL80211_BANDS; band++) {
+                       if (!mld->wiphy->bands[band])
+                               continue;
+
+                       mld->channel_survey->bands[band] =
+                               &mld->channel_survey->channels[n_channels];
+                       n_channels += mld->wiphy->bands[band]->n_channels;
+               }
+       }
+
+       band = iwl_mld_phy_band_to_nl80211(le32_to_cpu(notif->band));
+       chan_idx = iwl_mld_chanidx_from_phy(mld, band,
+                                           le32_to_cpu(notif->channel));
+       if (WARN_ON_ONCE(chan_idx < 0))
+               return;
+
+       IWL_DEBUG_SCAN(mld, "channel survey received for freq %d\n",
+                      mld->wiphy->bands[band]->channels[chan_idx].center_freq);
+
+       info = &mld->channel_survey->bands[band][chan_idx];
+
+       /* Times are all in ms */
+       info->time = le32_to_cpu(notif->active_time);
+       info->time_busy = le32_to_cpu(notif->busy_time);
+       info->noise =
+               iwl_average_neg_dbm(notif->noise, ARRAY_SIZE(notif->noise));
+}
+
+int iwl_mld_mac80211_get_survey(struct ieee80211_hw *hw, int idx,
+                               struct survey_info *survey)
+{
+       struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
+       int curr_idx = 0;
+
+       if (!mld->channel_survey)
+               return -ENOENT;
+
+       /* Iterate bands/channels to find the requested index.
+        * Logically this returns the entry with index "idx" from a flattened
+        * survey result array that only contains channels with information.
+        * The current index into this flattened array is tracked in curr_idx.
+        */
+       for (enum nl80211_band band = 0; band < NUM_NL80211_BANDS; band++) {
+               struct ieee80211_supported_band *sband =
+                       mld->wiphy->bands[band];
+
+               if (!sband)
+                       continue;
+
+               for (int per_band_idx = 0;
+                    per_band_idx < sband->n_channels;
+                    per_band_idx++) {
+                       struct iwl_mld_survey_channel *info =
+                               &mld->channel_survey->bands[band][per_band_idx];
+
+                       /* Skip entry entirely, it was not reported/scanned,
+                        * do not increase curr_idx for this entry.
+                        */
+                       if (!info->time)
+                               continue;
+
+                       /* Search did not reach the requested entry yet,
+                        * increment curr_idx and continue.
+                        */
+                       if (idx != curr_idx) {
+                               curr_idx++;
+                               continue;
+                       }
+
+                       /* Found (the next) channel to report */
+                       survey->channel = &sband->channels[per_band_idx];
+                       survey->filled = SURVEY_INFO_TIME |
+                                        SURVEY_INFO_TIME_BUSY;
+                       survey->time = info->time;
+                       survey->time_busy = info->time_busy;
+                       survey->noise = info->noise;
+                       if (survey->noise < 0)
+                               survey->filled |= SURVEY_INFO_NOISE_DBM;
+
+                       return 0;
+               }
+       }
+
+       return -ENOENT;
+}
index 4044cac3f086bdcd3159565385fbddd711c23b82..69110f0cfc8e2ad17ea1ffc09f67a2dd14168de4 100644 (file)
@@ -30,6 +30,12 @@ void iwl_mld_handle_match_found_notif(struct iwl_mld *mld,
 void iwl_mld_handle_scan_complete_notif(struct iwl_mld *mld,
                                        struct iwl_rx_packet *pkt);
 
+int iwl_mld_mac80211_get_survey(struct ieee80211_hw *hw, int idx,
+                               struct survey_info *survey);
+
+void iwl_mld_handle_channel_survey_notif(struct iwl_mld *mld,
+                                        struct iwl_rx_packet *pkt);
+
 #define WFA_TPC_IE_LEN 9
 
 static inline int iwl_mld_scan_max_template_size(void)
@@ -133,4 +139,35 @@ struct iwl_mld_scan {
        u64 last_mlo_scan_time;
 };
 
+/**
+ * struct iwl_mld_survey_channel - per-channel survey information
+ *
+ * Driver version of &struct survey_info with just the data we want to report.
+ *
+ * @time: time in ms the radio was on the channel
+ * @time_busy: time in ms the channel was sensed busy
+ * @noise: channel noise in dBm
+ */
+struct iwl_mld_survey_channel {
+       u32 time;
+       u32 time_busy;
+       s8 noise;
+};
+
+/**
+ * struct iwl_mld_survey - survey information
+ *
+ * Survey information for all available channels.
+ *
+ * @bands: per-band array for per-channel survey data, points into @channels
+ * @n_channels: Number of @channels entries that are allocated
+ * @channels: per-channel information
+ */
+struct iwl_mld_survey {
+       struct iwl_mld_survey_channel *bands[NUM_NL80211_BANDS];
+
+       int n_channels;
+       struct iwl_mld_survey_channel channels[] __counted_by(n_channels);
+};
+
 #endif /* __iwl_mld_scan_h__ */