@@ -1407,6 +1407,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);
@@ -712,7 +712,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;
@@ -771,6 +771,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;
@@ -7940,6 +7940,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);
@@ -7954,6 +7955,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);
@@ -7961,10 +7963,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);
@@ -11858,6 +11868,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);
@@ -12218,6 +12229,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);
@@ -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;
@@ -244,6 +219,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);
@@ -764,6 +749,54 @@ ath12k_reg_build_regd(struct ath12k_base *ab,
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,
@@ -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,
@@ -3934,6 +3934,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[];
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> --- drivers/net/wireless/ath/ath12k/core.c | 1 + drivers/net/wireless/ath/ath12k/core.h | 4 +- drivers/net/wireless/ath/ath12k/mac.c | 13 ++++ drivers/net/wireless/ath/ath12k/reg.c | 85 ++++++++++++++++++-------- drivers/net/wireless/ath/ath12k/reg.h | 1 + drivers/net/wireless/ath/ath12k/wmi.h | 1 + 6 files changed, 78 insertions(+), 27 deletions(-) base-commit: c3910de7bab78afbc106206aed5ec8e79458fbee