]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath12k: update channel list in worker when wait flag is set
authorKang Yang <kang.yang@oss.qualcomm.com>
Thu, 5 Jun 2025 08:25:28 +0000 (16:25 +0800)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 23 Jun 2025 14:28:32 +0000 (07:28 -0700)
With previous patch [1], ath12k_reg_update_chan_list() will be called
during reg_process_self_managed_hint().

reg_process_self_managed_hint() will hold rtnl_lock all the time.
But ath12k_reg_update_chan_list() may increase the occupation time of
rtnl_lock, because when wait flag is set, wait_for_completion_timeout()
will be called during 11d/hw scan.

Should minimize the occupation time of rtnl_lock as much as possible
to avoid interfering with rest of the system. So move the update channel
list operation to a new worker, so that wait_for_completion_timeout()
won't be called with the rtnl_lock held.

Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3

Fixes: f335295aa29c ("wifi: ath12k: avoid deadlock during regulatory update in ath12k_regd_update()") #[1]
Signed-off-by: Kang Yang <kang.yang@oss.qualcomm.com>
Reviewed-by: Aditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com>
Link: https://patch.msgid.link/20250605082528.701-1-kang.yang@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/core.c
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/reg.c
drivers/net/wireless/ath/ath12k/reg.h
drivers/net/wireless/ath/ath12k/wmi.h

index 89ae80934b3048f850939c25c160af0e2d05b8e2..cd58ab9c232239eb344bd693f210084a975c98f3 100644 (file)
@@ -1409,6 +1409,7 @@ void ath12k_core_halt(struct ath12k *ar)
        ath12k_mac_peer_cleanup_all(ar);
        cancel_delayed_work_sync(&ar->scan.timeout);
        cancel_work_sync(&ar->regd_update_work);
+       cancel_work_sync(&ar->regd_channel_update_work);
        cancel_work_sync(&ab->rfkill_work);
        cancel_work_sync(&ab->update_11d_work);
 
index 6b5e6fab9cccc38927cb33f131b6fb2db22bc03e..46fc14dce4c7c77349efa6c59195f8c6f0bdc550 100644 (file)
@@ -717,7 +717,7 @@ struct ath12k {
 
        /* protects the radio specific data like debug stats, ppdu_stats_info stats,
         * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info,
-        * channel context data, survey info, test mode data.
+        * channel context data, survey info, test mode data, regd_channel_update_queue.
         */
        spinlock_t data_lock;
 
@@ -776,6 +776,8 @@ struct ath12k {
        struct completion bss_survey_done;
 
        struct work_struct regd_update_work;
+       struct work_struct regd_channel_update_work;
+       struct list_head regd_channel_update_queue;
 
        struct wiphy_work wmi_mgmt_tx_work;
        struct sk_buff_head wmi_mgmt_tx_queue;
index fa069ac30e0d922a92c2cc272f4ff584ef9ecab5..b9c8854786b4f9218b1294914976aa61b3734316 100644 (file)
@@ -8406,6 +8406,7 @@ static void ath12k_mac_stop(struct ath12k *ar)
 {
        struct ath12k_hw *ah = ar->ah;
        struct htt_ppdu_stats_info *ppdu_stats, *tmp;
+       struct ath12k_wmi_scan_chan_list_arg *arg;
        int ret;
 
        lockdep_assert_held(&ah->hw_mutex);
@@ -8420,6 +8421,7 @@ static void ath12k_mac_stop(struct ath12k *ar)
 
        cancel_delayed_work_sync(&ar->scan.timeout);
        wiphy_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &ar->scan.vdev_clean_wk);
+       cancel_work_sync(&ar->regd_channel_update_work);
        cancel_work_sync(&ar->regd_update_work);
        cancel_work_sync(&ar->ab->rfkill_work);
        cancel_work_sync(&ar->ab->update_11d_work);
@@ -8427,10 +8429,18 @@ static void ath12k_mac_stop(struct ath12k *ar)
        complete(&ar->completed_11d_scan);
 
        spin_lock_bh(&ar->data_lock);
+
        list_for_each_entry_safe(ppdu_stats, tmp, &ar->ppdu_stats_info, list) {
                list_del(&ppdu_stats->list);
                kfree(ppdu_stats);
        }
+
+       while ((arg = list_first_entry_or_null(&ar->regd_channel_update_queue,
+                                              struct ath12k_wmi_scan_chan_list_arg,
+                                              list))) {
+               list_del(&arg->list);
+               kfree(arg);
+       }
        spin_unlock_bh(&ar->data_lock);
 
        rcu_assign_pointer(ar->ab->pdevs_active[ar->pdev_idx], NULL);
@@ -12411,6 +12421,7 @@ static void ath12k_mac_hw_unregister(struct ath12k_hw *ah)
        int i;
 
        for_each_ar(ah, ar, i) {
+               cancel_work_sync(&ar->regd_channel_update_work);
                cancel_work_sync(&ar->regd_update_work);
                ath12k_debugfs_unregister(ar);
                ath12k_fw_stats_reset(ar);
@@ -12771,6 +12782,8 @@ static void ath12k_mac_setup(struct ath12k *ar)
 
        INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
        wiphy_work_init(&ar->scan.vdev_clean_wk, ath12k_scan_vdev_clean_work);
+       INIT_WORK(&ar->regd_channel_update_work, ath12k_regd_update_chan_list_work);
+       INIT_LIST_HEAD(&ar->regd_channel_update_queue);
        INIT_WORK(&ar->regd_update_work, ath12k_regd_update_work);
 
        wiphy_work_init(&ar->wmi_mgmt_tx_work, ath12k_mgmt_over_wmi_tx_work);
index a7b9ecc14c391a7fef39e9491ca87a3cceafc0d3..7c997c9dad4d8bcb5294b3a7a79bde34fc5724fc 100644 (file)
@@ -137,32 +137,7 @@ int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait)
        struct ath12k_wmi_channel_arg *ch;
        enum nl80211_band band;
        int num_channels = 0;
-       int i, ret, left;
-
-       if (wait && ar->state_11d == ATH12K_11D_RUNNING) {
-               left = wait_for_completion_timeout(&ar->completed_11d_scan,
-                                                  ATH12K_SCAN_TIMEOUT_HZ);
-               if (!left) {
-                       ath12k_dbg(ar->ab, ATH12K_DBG_REG,
-                                  "failed to receive 11d scan complete: timed out\n");
-                       ar->state_11d = ATH12K_11D_IDLE;
-               }
-               ath12k_dbg(ar->ab, ATH12K_DBG_REG,
-                          "reg 11d scan wait left time %d\n", left);
-       }
-
-       if (wait &&
-           (ar->scan.state == ATH12K_SCAN_STARTING ||
-           ar->scan.state == ATH12K_SCAN_RUNNING)) {
-               left = wait_for_completion_timeout(&ar->scan.completed,
-                                                  ATH12K_SCAN_TIMEOUT_HZ);
-               if (!left)
-                       ath12k_dbg(ar->ab, ATH12K_DBG_REG,
-                                  "failed to receive hw scan complete: timed out\n");
-
-               ath12k_dbg(ar->ab, ATH12K_DBG_REG,
-                          "reg hw scan wait left time %d\n", left);
-       }
+       int i, ret = 0;
 
        if (ar->ah->state == ATH12K_HW_STATE_RESTARTING)
                return 0;
@@ -260,6 +235,16 @@ int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait)
                }
        }
 
+       if (wait) {
+               spin_lock_bh(&ar->data_lock);
+               list_add_tail(&arg->list, &ar->regd_channel_update_queue);
+               spin_unlock_bh(&ar->data_lock);
+
+               queue_work(ar->ab->workqueue, &ar->regd_channel_update_work);
+
+               return 0;
+       }
+
        ret = ath12k_wmi_send_scan_chan_list_cmd(ar, arg);
        kfree(arg);
 
@@ -780,6 +765,54 @@ ret:
        return new_regd;
 }
 
+void ath12k_regd_update_chan_list_work(struct work_struct *work)
+{
+       struct ath12k *ar = container_of(work, struct ath12k,
+                                        regd_channel_update_work);
+       struct ath12k_wmi_scan_chan_list_arg *arg;
+       struct list_head local_update_list;
+       int left;
+
+       INIT_LIST_HEAD(&local_update_list);
+
+       spin_lock_bh(&ar->data_lock);
+       list_splice_tail_init(&ar->regd_channel_update_queue, &local_update_list);
+       spin_unlock_bh(&ar->data_lock);
+
+       while ((arg = list_first_entry_or_null(&local_update_list,
+                                              struct ath12k_wmi_scan_chan_list_arg,
+                                              list))) {
+               if (ar->state_11d != ATH12K_11D_IDLE) {
+                       left = wait_for_completion_timeout(&ar->completed_11d_scan,
+                                                          ATH12K_SCAN_TIMEOUT_HZ);
+                       if (!left) {
+                               ath12k_dbg(ar->ab, ATH12K_DBG_REG,
+                                          "failed to receive 11d scan complete: timed out\n");
+                               ar->state_11d = ATH12K_11D_IDLE;
+                       }
+
+                       ath12k_dbg(ar->ab, ATH12K_DBG_REG,
+                                  "reg 11d scan wait left time %d\n", left);
+               }
+
+               if ((ar->scan.state == ATH12K_SCAN_STARTING ||
+                    ar->scan.state == ATH12K_SCAN_RUNNING)) {
+                       left = wait_for_completion_timeout(&ar->scan.completed,
+                                                          ATH12K_SCAN_TIMEOUT_HZ);
+                       if (!left)
+                               ath12k_dbg(ar->ab, ATH12K_DBG_REG,
+                                          "failed to receive hw scan complete: timed out\n");
+
+                       ath12k_dbg(ar->ab, ATH12K_DBG_REG,
+                                  "reg hw scan wait left time %d\n", left);
+               }
+
+               ath12k_wmi_send_scan_chan_list_cmd(ar, arg);
+               list_del(&arg->list);
+               kfree(arg);
+       }
+}
+
 void ath12k_regd_update_work(struct work_struct *work)
 {
        struct ath12k *ar = container_of(work, struct ath12k,
index 8af8e9ba462e90db3eb137885d0acd4b1cb2286e..0aeba06182c50ce4e19202f6f0e60b9c6938c6f0 100644 (file)
@@ -113,6 +113,7 @@ int ath12k_reg_handle_chan_list(struct ath12k_base *ab,
                                struct ath12k_reg_info *reg_info,
                                enum wmi_vdev_type vdev_type,
                                enum ieee80211_ap_reg_power power_type);
+void ath12k_regd_update_chan_list_work(struct work_struct *work);
 enum wmi_reg_6g_ap_type
 ath12k_reg_ap_pwr_convert(enum ieee80211_ap_reg_power power_type);
 enum ath12k_reg_status ath12k_reg_validate_reg_info(struct ath12k_base *ab,
index c640ffa180c88b75d49e5c96a6ff3625f36a6d37..117150220b997bf03fe2ea882f899208ffc4b046 100644 (file)
@@ -3948,6 +3948,7 @@ struct wmi_stop_scan_cmd {
 } __packed;
 
 struct ath12k_wmi_scan_chan_list_arg {
+       struct list_head list;
        u32 pdev_id;
        u16 nallchans;
        struct ath12k_wmi_channel_arg channel[];