* [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work
@ 2026-07-17 2:56 Runyu Xiao
2026-07-17 14:40 ` Jeff Johnson
0 siblings, 1 reply; 5+ messages in thread
From: Runyu Xiao @ 2026-07-17 2:56 UTC (permalink / raw)
To: Jeff Johnson
Cc: linux-wireless, ath12k, linux-kernel, Johannes Berg, Jianhao Xu,
Runyu Xiao, stable
ath12k_mac_op_stop() is called with the wiphy mutex held and calls
ath12k_mac_stop(), which cancels ar->scan.timeout. The timeout worker
also takes the wiphy mutex before aborting the scan, so synchronously
cancelling it from the stop path can deadlock if the worker has started
and is waiting for the same mutex.
Do not drop the wiphy mutex inside the mac80211 stop callback. Convert
ar->scan.timeout to wiphy_delayed_work instead, so the timeout callback
runs in wiphy work context with the wiphy mutex held. This matches the
locking model used by the scan vdev cleanup work and lets stop/cancel
paths use wiphy_delayed_work_cancel() while they already hold the wiphy
mutex.
The old scan-finish path could cancel the delayed work directly from WMI
event context. With wiphy_delayed_work that cancellation must happen from
wiphy context, so keep it in scan.vdev_clean_wk. Mark scans whose cleanup
work has been queued so a timeout work item that was already queued before
cleanup runs does not abort a scan that is already finishing.
Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices")
Suggested-by: Johannes Berg <johannes@sipsolutions.net>
Cc: stable@vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
---
Changes in v3:
- Convert scan.timeout to wiphy_delayed_work instead of dropping the
mac80211-owned wiphy lock in ath12k_mac_op_stop().
- Cancel the timeout from scan.vdev_clean_wk to preserve the
scan-finish cancellation from wiphy context.
- Add scan.finish_queued so an already queued timeout work item does not
abort a scan that has already queued its cleanup work.
Changes in v2:
- Rebase on ath.git ath-current.
- Use ath-current subject tag as requested.
- Move the synchronous scan timeout drain out of the locked stop path
instead of adding it in the wrong start-path context.
- Update Fixes to the ath12k commit that introduced the stop-time
scan.timeout drain.
drivers/net/wireless/ath/ath12k/core.c | 2 +-
drivers/net/wireless/ath/ath12k/core.h | 3 +-
drivers/net/wireless/ath/ath12k/mac.c | 50 ++++++++++++++++++--------
3 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 742d4fd1b598..95673d3be9da 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1463,7 +1463,7 @@ void ath12k_core_halt(struct ath12k *ar)
ath12k_mac_scan_finish(ar);
ath12k_mac_peer_cleanup_all(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &ar->scan.timeout);
cancel_work_sync(&ar->regd_update_work);
cancel_work_sync(&ar->regd_channel_update_work);
cancel_work_sync(&ab->rfkill_work);
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index fc5127b5c1a3..b6fd5e9dfdb7 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -635,11 +635,12 @@ struct ath12k {
struct completion started;
struct completion completed;
struct completion on_channel;
- struct delayed_work timeout;
+ struct wiphy_delayed_work timeout;
enum ath12k_scan_state state;
bool is_roc;
int roc_freq;
bool roc_notify;
+ bool finish_queued;
struct wiphy_work vdev_clean_wk;
struct ath12k_link_vif *arvif;
} scan;
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 51c4df32e716..f53df8d4c808 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -5162,7 +5162,7 @@ void __ath12k_mac_scan_finish(struct ath12k *ar)
ieee80211_remain_on_channel_expired(hw);
fallthrough;
case ATH12K_SCAN_STARTING:
- cancel_delayed_work(&ar->scan.timeout);
+ ar->scan.finish_queued = true;
complete_all(&ar->scan.completed);
wiphy_work_queue(ar->ah->hw->wiphy, &ar->scan.vdev_clean_wk);
break;
@@ -5254,14 +5254,23 @@ static void ath12k_scan_abort(struct ath12k *ar)
spin_unlock_bh(&ar->data_lock);
}
-static void ath12k_scan_timeout_work(struct work_struct *work)
+static void ath12k_scan_timeout_work(struct wiphy *wiphy,
+ struct wiphy_work *work)
{
- struct ath12k *ar = container_of(work, struct ath12k,
- scan.timeout.work);
+ struct wiphy_delayed_work *dwork;
+ struct ath12k *ar;
+
+ dwork = container_of(work, struct wiphy_delayed_work, work);
+ ar = container_of(dwork, struct ath12k, scan.timeout);
+
+ spin_lock_bh(&ar->data_lock);
+ if (ar->scan.finish_queued) {
+ spin_unlock_bh(&ar->data_lock);
+ return;
+ }
+ spin_unlock_bh(&ar->data_lock);
- wiphy_lock(ath12k_ar_to_hw(ar)->wiphy);
ath12k_scan_abort(ar);
- wiphy_unlock(ath12k_ar_to_hw(ar)->wiphy);
}
static void ath12k_mac_scan_send_complete(struct ath12k *ar,
@@ -5292,6 +5301,8 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
arvif = ar->scan.arvif;
+ wiphy_delayed_work_cancel(wiphy, &ar->scan.timeout);
+
/* The scan vdev has already been deleted. This can occur when a
* new scan request is made on the same vif with a different
* frequency, causing the scan arvif to move from one radio to
@@ -5323,6 +5334,7 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
}
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -5609,6 +5621,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
reinit_completion(&ar->scan.completed);
ar->scan.state = ATH12K_SCAN_STARTING;
ar->scan.is_roc = false;
+ ar->scan.finish_queued = false;
ar->scan.arvif = arvif;
ret = 0;
break;
@@ -5665,6 +5678,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
spin_unlock_bh(&ar->data_lock);
goto exit;
}
@@ -5672,9 +5686,10 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac scan started");
/* Add a margin to account for event/command processing */
- ieee80211_queue_delayed_work(ath12k_ar_to_hw(ar), &ar->scan.timeout,
- msecs_to_jiffies(arg->max_scan_time +
- ATH12K_MAC_SCAN_TIMEOUT_MSECS));
+ wiphy_delayed_work_queue(ath12k_ar_to_hw(ar)->wiphy,
+ &ar->scan.timeout,
+ msecs_to_jiffies(arg->max_scan_time +
+ ATH12K_MAC_SCAN_TIMEOUT_MSECS));
exit:
if (arg) {
@@ -5757,6 +5772,7 @@ int ath12k_mac_op_hw_scan(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.arvif = NULL;
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -5792,7 +5808,7 @@ void ath12k_mac_op_cancel_hw_scan(struct ieee80211_hw *hw,
ath12k_scan_abort(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
}
}
EXPORT_SYMBOL(ath12k_mac_op_cancel_hw_scan);
@@ -9935,7 +9951,7 @@ static void ath12k_mac_stop(struct ath12k *ar)
clear_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &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);
@@ -10962,6 +10978,7 @@ void ath12k_mac_op_remove_interface(struct ieee80211_hw *hw,
}
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -13876,7 +13893,7 @@ int ath12k_mac_op_cancel_remain_on_channel(struct ieee80211_hw *hw,
ath12k_scan_abort(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
wiphy_work_flush(hw->wiphy, &ar->scan.vdev_clean_wk);
return 0;
@@ -13956,6 +13973,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
reinit_completion(&ar->scan.on_channel);
ar->scan.state = ATH12K_SCAN_STARTING;
ar->scan.is_roc = true;
+ ar->scan.finish_queued = false;
ar->scan.arvif = arvif;
ar->scan.roc_freq = chan->center_freq;
ar->scan.roc_notify = true;
@@ -13998,6 +14016,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
spin_unlock_bh(&ar->data_lock);
return ret;
}
@@ -14011,8 +14030,8 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
return -ETIMEDOUT;
}
- ieee80211_queue_delayed_work(hw, &ar->scan.timeout,
- msecs_to_jiffies(duration));
+ wiphy_delayed_work_queue(hw->wiphy, &ar->scan.timeout,
+ msecs_to_jiffies(duration));
return 0;
}
@@ -15052,6 +15071,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
ar->num_tx_chains = hweight32(pdev->cap.tx_chain_mask);
ar->num_rx_chains = hweight32(pdev->cap.rx_chain_mask);
ar->scan.arvif = NULL;
+ ar->scan.finish_queued = false;
ar->vdev_id_11d_scan = ATH12K_11D_INVALID_VDEV_ID;
spin_lock_init(&ar->data_lock);
@@ -15077,7 +15097,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
ar->thermal.temperature = 0;
ar->thermal.hwmon_dev = NULL;
- INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
+ wiphy_delayed_work_init(&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);
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work
2026-07-17 2:56 [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work Runyu Xiao
@ 2026-07-17 14:40 ` Jeff Johnson
2026-09-11 20:36 ` Jeff Johnson
0 siblings, 1 reply; 5+ messages in thread
From: Jeff Johnson @ 2026-07-17 14:40 UTC (permalink / raw)
To: Runyu Xiao, Jeff Johnson
Cc: linux-wireless, ath12k, linux-kernel, Johannes Berg, Jianhao Xu, stable
On 7/16/2026 7:56 PM, Runyu Xiao wrote:
> ath12k_mac_op_stop() is called with the wiphy mutex held and calls
> ath12k_mac_stop(), which cancels ar->scan.timeout. The timeout worker
you are missing a very important detail, namely it is a synchronous cancel
> also takes the wiphy mutex before aborting the scan, so synchronously
> cancelling it from the stop path can deadlock if the worker has started
> and is waiting for the same mutex.
>
> Do not drop the wiphy mutex inside the mac80211 stop callback. Convert
What does this mean? Where are we currently dropping the wiphy mutex?
You should describe the patch in terms of the current code, not in terms of
any prior versions of your patch.
> ar->scan.timeout to wiphy_delayed_work instead, so the timeout callback
> runs in wiphy work context with the wiphy mutex held. This matches the
> locking model used by the scan vdev cleanup work and lets stop/cancel
> paths use wiphy_delayed_work_cancel() while they already hold the wiphy
> mutex.
>
> The old scan-finish path could cancel the delayed work directly from WMI
what is "old"? There is the current code and the new proposed code.
> event context. With wiphy_delayed_work that cancellation must happen from
> wiphy context, so keep it in scan.vdev_clean_wk. Mark scans whose cleanup
> work has been queued so a timeout work item that was already queued before
> cleanup runs does not abort a scan that is already finishing.
My AI review agent thinks this logic is unnecessary, but I haven't looked at
this in detail.
"summary": "finish_queued flag is a redundant substitute for
wiphy_delayed_work_cancel: __ath12k_mac_scan_finish is always called under
wiphy lock, making direct cancellation safe",
"failure_scenario": "Every caller of __ath12k_mac_scan_finish holds the wiphy
lock (directly or via wiphy_work dispatch), so wiphy_delayed_work_cancel could
be called there directly — as the old cancel_delayed_work was. The new
finish_queued flag instead defers suppression to the timeout work itself.
Since ath12k_scan_abort already has an explicit ATH12K_SCAN_IDLE bail-out path
(line 5232) with a comment covering the timeout-vs-completion race, the
finish_queued guard in ath12k_scan_timeout_work provides no additional safety.
The cost is 8 reset sites (lines 5337, 5624, 5681, 5775, 10971, 13969, 14012,
15061) where a future error path that forgets the reset leaves
finish_queued=true, silently suppressing the next legitimate timeout abort and
leaving the driver wedged in SCAN_RUNNING with no recovery."
>
> Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices")
> Suggested-by: Johannes Berg <johannes@sipsolutions.net>
> Cc: stable@vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
> ---
> Changes in v3:
> - Convert scan.timeout to wiphy_delayed_work instead of dropping the
> mac80211-owned wiphy lock in ath12k_mac_op_stop().
> - Cancel the timeout from scan.vdev_clean_wk to preserve the
> scan-finish cancellation from wiphy context.
> - Add scan.finish_queued so an already queued timeout work item does not
> abort a scan that has already queued its cleanup work.
>
> Changes in v2:
> - Rebase on ath.git ath-current.
> - Use ath-current subject tag as requested.
> - Move the synchronous scan timeout drain out of the locked stop path
> instead of adding it in the wrong start-path context.
> - Update Fixes to the ath12k commit that introduced the stop-time
> scan.timeout drain.
FWIW I'd suggest looking into using the 'b4' tool for future contributions.
It will help you with patch history, including providing links to the prior
versions
https://b4.docs.kernel.org/en/latest/#for-developers
>
> drivers/net/wireless/ath/ath12k/core.c | 2 +-
> drivers/net/wireless/ath/ath12k/core.h | 3 +-
> drivers/net/wireless/ath/ath12k/mac.c | 50 ++++++++++++++++++--------
> 3 files changed, 38 insertions(+), 17 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
> index 742d4fd1b598..95673d3be9da 100644
> --- a/drivers/net/wireless/ath/ath12k/core.c
> +++ b/drivers/net/wireless/ath/ath12k/core.c
> @@ -1463,7 +1463,7 @@ void ath12k_core_halt(struct ath12k *ar)
>
> ath12k_mac_scan_finish(ar);
> ath12k_mac_peer_cleanup_all(ar);
> - cancel_delayed_work_sync(&ar->scan.timeout);
> + wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &ar->scan.timeout);
> cancel_work_sync(&ar->regd_update_work);
> cancel_work_sync(&ar->regd_channel_update_work);
> cancel_work_sync(&ab->rfkill_work);
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index fc5127b5c1a3..b6fd5e9dfdb7 100644
> --- a/drivers/net/wireless/ath/ath12k/core.h
> +++ b/drivers/net/wireless/ath/ath12k/core.h
> @@ -635,11 +635,12 @@ struct ath12k {
> struct completion started;
> struct completion completed;
> struct completion on_channel;
> - struct delayed_work timeout;
> + struct wiphy_delayed_work timeout;
> enum ath12k_scan_state state;
> bool is_roc;
> int roc_freq;
> bool roc_notify;
> + bool finish_queued;
> struct wiphy_work vdev_clean_wk;
> struct ath12k_link_vif *arvif;
> } scan;
> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> index 51c4df32e716..f53df8d4c808 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -5162,7 +5162,7 @@ void __ath12k_mac_scan_finish(struct ath12k *ar)
> ieee80211_remain_on_channel_expired(hw);
> fallthrough;
> case ATH12K_SCAN_STARTING:
> - cancel_delayed_work(&ar->scan.timeout);
> + ar->scan.finish_queued = true;
> complete_all(&ar->scan.completed);
> wiphy_work_queue(ar->ah->hw->wiphy, &ar->scan.vdev_clean_wk);
> break;
> @@ -5254,14 +5254,23 @@ static void ath12k_scan_abort(struct ath12k *ar)
> spin_unlock_bh(&ar->data_lock);
> }
>
> -static void ath12k_scan_timeout_work(struct work_struct *work)
> +static void ath12k_scan_timeout_work(struct wiphy *wiphy,
> + struct wiphy_work *work)
> {
> - struct ath12k *ar = container_of(work, struct ath12k,
> - scan.timeout.work);
> + struct wiphy_delayed_work *dwork;
> + struct ath12k *ar;
> +
> + dwork = container_of(work, struct wiphy_delayed_work, work);
> + ar = container_of(dwork, struct ath12k, scan.timeout);
> +
> + spin_lock_bh(&ar->data_lock);
> + if (ar->scan.finish_queued) {
> + spin_unlock_bh(&ar->data_lock);
> + return;
> + }
> + spin_unlock_bh(&ar->data_lock);
if the flag is necessary, all of that could become:
scoped_guard(spinlock_bh, &ar->data_lock)
if (ar->scan.finish_queued)
return;
>
> - wiphy_lock(ath12k_ar_to_hw(ar)->wiphy);
> ath12k_scan_abort(ar);
> - wiphy_unlock(ath12k_ar_to_hw(ar)->wiphy);
> }
>
> static void ath12k_mac_scan_send_complete(struct ath12k *ar,
> @@ -5292,6 +5301,8 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
>
> arvif = ar->scan.arvif;
>
> + wiphy_delayed_work_cancel(wiphy, &ar->scan.timeout);
> +
> /* The scan vdev has already been deleted. This can occur when a
> * new scan request is made on the same vif with a different
> * frequency, causing the scan arvif to move from one radio to
> @@ -5323,6 +5334,7 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
> }
>
> ar->scan.state = ATH12K_SCAN_IDLE;
> + ar->scan.finish_queued = false;
> ar->scan_channel = NULL;
> ar->scan.roc_freq = 0;
> spin_unlock_bh(&ar->data_lock);
> @@ -5609,6 +5621,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
> reinit_completion(&ar->scan.completed);
> ar->scan.state = ATH12K_SCAN_STARTING;
> ar->scan.is_roc = false;
> + ar->scan.finish_queued = false;
> ar->scan.arvif = arvif;
> ret = 0;
> break;
> @@ -5665,6 +5678,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
>
> spin_lock_bh(&ar->data_lock);
> ar->scan.state = ATH12K_SCAN_IDLE;
> + ar->scan.finish_queued = false;
> spin_unlock_bh(&ar->data_lock);
> goto exit;
> }
> @@ -5672,9 +5686,10 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
> ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac scan started");
>
> /* Add a margin to account for event/command processing */
> - ieee80211_queue_delayed_work(ath12k_ar_to_hw(ar), &ar->scan.timeout,
> - msecs_to_jiffies(arg->max_scan_time +
> - ATH12K_MAC_SCAN_TIMEOUT_MSECS));
> + wiphy_delayed_work_queue(ath12k_ar_to_hw(ar)->wiphy,
> + &ar->scan.timeout,
> + msecs_to_jiffies(arg->max_scan_time +
> + ATH12K_MAC_SCAN_TIMEOUT_MSECS));
>
> exit:
> if (arg) {
> @@ -5757,6 +5772,7 @@ int ath12k_mac_op_hw_scan(struct ieee80211_hw *hw,
> spin_lock_bh(&ar->data_lock);
> ar->scan.arvif = NULL;
> ar->scan.state = ATH12K_SCAN_IDLE;
> + ar->scan.finish_queued = false;
> ar->scan_channel = NULL;
> ar->scan.roc_freq = 0;
> spin_unlock_bh(&ar->data_lock);
> @@ -5792,7 +5808,7 @@ void ath12k_mac_op_cancel_hw_scan(struct ieee80211_hw *hw,
>
> ath12k_scan_abort(ar);
>
> - cancel_delayed_work_sync(&ar->scan.timeout);
> + wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
> }
> }
> EXPORT_SYMBOL(ath12k_mac_op_cancel_hw_scan);
> @@ -9935,7 +9951,7 @@ static void ath12k_mac_stop(struct ath12k *ar)
>
> clear_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags);
>
> - cancel_delayed_work_sync(&ar->scan.timeout);
> + wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &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);
> @@ -10962,6 +10978,7 @@ void ath12k_mac_op_remove_interface(struct ieee80211_hw *hw,
> }
>
> ar->scan.state = ATH12K_SCAN_IDLE;
> + ar->scan.finish_queued = false;
> ar->scan_channel = NULL;
> ar->scan.roc_freq = 0;
> spin_unlock_bh(&ar->data_lock);
> @@ -13876,7 +13893,7 @@ int ath12k_mac_op_cancel_remain_on_channel(struct ieee80211_hw *hw,
>
> ath12k_scan_abort(ar);
>
> - cancel_delayed_work_sync(&ar->scan.timeout);
> + wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
> wiphy_work_flush(hw->wiphy, &ar->scan.vdev_clean_wk);
>
> return 0;
> @@ -13956,6 +13973,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
> reinit_completion(&ar->scan.on_channel);
> ar->scan.state = ATH12K_SCAN_STARTING;
> ar->scan.is_roc = true;
> + ar->scan.finish_queued = false;
> ar->scan.arvif = arvif;
> ar->scan.roc_freq = chan->center_freq;
> ar->scan.roc_notify = true;
> @@ -13998,6 +14016,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
>
> spin_lock_bh(&ar->data_lock);
> ar->scan.state = ATH12K_SCAN_IDLE;
> + ar->scan.finish_queued = false;
> spin_unlock_bh(&ar->data_lock);
> return ret;
> }
> @@ -14011,8 +14030,8 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
> return -ETIMEDOUT;
> }
>
> - ieee80211_queue_delayed_work(hw, &ar->scan.timeout,
> - msecs_to_jiffies(duration));
> + wiphy_delayed_work_queue(hw->wiphy, &ar->scan.timeout,
> + msecs_to_jiffies(duration));
>
> return 0;
> }
> @@ -15052,6 +15071,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
> ar->num_tx_chains = hweight32(pdev->cap.tx_chain_mask);
> ar->num_rx_chains = hweight32(pdev->cap.rx_chain_mask);
> ar->scan.arvif = NULL;
> + ar->scan.finish_queued = false;
> ar->vdev_id_11d_scan = ATH12K_11D_INVALID_VDEV_ID;
>
> spin_lock_init(&ar->data_lock);
> @@ -15077,7 +15097,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
> ar->thermal.temperature = 0;
> ar->thermal.hwmon_dev = NULL;
>
> - INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
> + wiphy_delayed_work_init(&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);
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work
2026-07-17 14:40 ` Jeff Johnson
@ 2026-09-11 20:36 ` Jeff Johnson
2026-09-12 8:04 ` Runyu Xiao
2026-09-15 6:57 ` [PATCH v4 " Runyu Xiao
0 siblings, 2 replies; 5+ messages in thread
From: Jeff Johnson @ 2026-09-11 20:36 UTC (permalink / raw)
To: Runyu Xiao, Jeff Johnson
Cc: linux-wireless, ath12k, linux-kernel, Johannes Berg, Jianhao Xu, stable
On 7/17/2026 7:40 AM, Jeff Johnson wrote:
> On 7/16/2026 7:56 PM, Runyu Xiao wrote:
>> ath12k_mac_op_stop() is called with the wiphy mutex held and calls
>> ath12k_mac_stop(), which cancels ar->scan.timeout. The timeout worker
>
> you are missing a very important detail, namely it is a synchronous cancel
>
>> also takes the wiphy mutex before aborting the scan, so synchronously
>> cancelling it from the stop path can deadlock if the worker has started
>> and is waiting for the same mutex.
>>
>> Do not drop the wiphy mutex inside the mac80211 stop callback. Convert
>
> What does this mean? Where are we currently dropping the wiphy mutex?
>
> You should describe the patch in terms of the current code, not in terms of
> any prior versions of your patch.
>
>> ar->scan.timeout to wiphy_delayed_work instead, so the timeout callback
>> runs in wiphy work context with the wiphy mutex held. This matches the
>> locking model used by the scan vdev cleanup work and lets stop/cancel
>> paths use wiphy_delayed_work_cancel() while they already hold the wiphy
>> mutex.
>>
>> The old scan-finish path could cancel the delayed work directly from WMI
>
> what is "old"? There is the current code and the new proposed code.
>
>> event context. With wiphy_delayed_work that cancellation must happen from
>> wiphy context, so keep it in scan.vdev_clean_wk. Mark scans whose cleanup
>> work has been queued so a timeout work item that was already queued before
>> cleanup runs does not abort a scan that is already finishing.
>
> My AI review agent thinks this logic is unnecessary, but I haven't looked at
> this in detail.
>
> "summary": "finish_queued flag is a redundant substitute for
> wiphy_delayed_work_cancel: __ath12k_mac_scan_finish is always called under
> wiphy lock, making direct cancellation safe",
>
> "failure_scenario": "Every caller of __ath12k_mac_scan_finish holds the wiphy
> lock (directly or via wiphy_work dispatch), so wiphy_delayed_work_cancel could
> be called there directly — as the old cancel_delayed_work was. The new
> finish_queued flag instead defers suppression to the timeout work itself.
> Since ath12k_scan_abort already has an explicit ATH12K_SCAN_IDLE bail-out path
> (line 5232) with a comment covering the timeout-vs-completion race, the
> finish_queued guard in ath12k_scan_timeout_work provides no additional safety.
> The cost is 8 reset sites (lines 5337, 5624, 5681, 5775, 10971, 13969, 14012,
> 15061) where a future error path that forgets the reset leaves
> finish_queued=true, silently suppressing the next legitimate timeout abort and
> leaving the driver wedged in SCAN_RUNNING with no recovery."
You never responded to my response. However, since this seems to be a
legitimate issue I did a little more research and now see why the
finish_queued flag is needed. A better commit text should explain that since
the current commit text is confusing
I propose something like the following. Yes it is AI-assisted, but IMO it does
a good job of describing both the existing problem and the reason the new flag
is needed in the modified version.
ath12k_mac_op_stop() is called with the wiphy mutex held and calls
ath12k_mac_stop(), which synchronously cancels ar->scan.timeout via
cancel_delayed_work_sync(). The timeout worker takes the wiphy mutex
before aborting the scan, so if the worker has already started and is
waiting for that mutex, the stop path blocks indefinitely: stop holds
the wiphy mutex waiting for the worker to finish, and the worker waits
for the wiphy mutex to proceed.
In order to avoid this deadlock, convert ar->scan.timeout from a
plain delayed_work to a wiphy_delayed_work. The timeout callback then
runs in wiphy work context with the wiphy mutex already held,
matching the locking model used by ar->scan.vdev_clean_wk. Stop and
cancel paths can use wiphy_delayed_work_cancel() while holding the
mutex, which removes the _sync wait that caused the deadlock.
There is, however, one additional issue with the scan complete path.
wiphy_delayed_work_cancel() requires the wiphy mutex, but
__ath12k_mac_scan_finish() is called from WMI event handlers under
data_lock only, without the wiphy mutex. The existing code can call
cancel_delayed_work() there directly, but that is no longer valid when
using wiphy_delayed_work. Instead, set a finish_queued flag and queue
vdev_clean_wk, which runs under the wiphy mutex and performs the
cancel. Add a check in the timeout worker so that if it was already
queued before finish_queued was set, it exits without aborting a scan
that is already finishing.
/jeff
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work
2026-09-11 20:36 ` Jeff Johnson
@ 2026-09-12 8:04 ` Runyu Xiao
2026-09-15 6:57 ` [PATCH v4 " Runyu Xiao
1 sibling, 0 replies; 5+ messages in thread
From: Runyu Xiao @ 2026-09-12 8:04 UTC (permalink / raw)
To: Jeff Johnson
Cc: Runyu Xiao, Jeff Johnson, linux-wireless, ath12k, linux-kernel,
Johannes Berg, Jianhao Xu, stable
On Fri, Sep 11, 2026 at 01:36:47PM -0700, Jeff Johnson wrote:
> You never responded to my response. However, since this seems to be a
> legitimate issue I did a little more research and now see why the
> finish_queued flag is needed. A better commit text should explain that since
> the current commit text is confusing
Sorry for the delay. I was unsure how this part should be resolved, so I
held off on replying.
> There is, however, one additional issue with the scan complete path.
> wiphy_delayed_work_cancel() requires the wiphy mutex, but
> __ath12k_mac_scan_finish() is called from WMI event handlers under
> data_lock only, without the wiphy mutex. The existing code can call
> cancel_delayed_work() there directly, but that is no longer valid when
> using wiphy_delayed_work. Instead, set a finish_queued flag and queue
> vdev_clean_wk, which runs under the wiphy mutex and performs the
> cancel. Add a check in the timeout worker so that if it was already
> queued before finish_queued was set, it exits without aborting a scan
> that is already finishing.
Agreed. Your explanation clarifies why the finish_queued flag is needed.
I will keep the finish_queued logic and update the commit message accordingly
in the next revision.
Thanks for taking another look.
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work
2026-09-11 20:36 ` Jeff Johnson
2026-09-12 8:04 ` Runyu Xiao
@ 2026-09-15 6:57 ` Runyu Xiao
1 sibling, 0 replies; 5+ messages in thread
From: Runyu Xiao @ 2026-09-15 6:57 UTC (permalink / raw)
To: jeff.johnson
Cc: jjohnson, quic_kangyang, kvalo, linux-wireless, ath12k,
linux-kernel, johannes, jianhao.xu, Runyu Xiao, stable
ath12k_mac_op_stop() runs with the wiphy mutex held and calls
ath12k_mac_stop(), which synchronously cancels ar->scan.timeout.
The timeout worker takes the same mutex before aborting the scan. If
the worker has started and is waiting for the mutex, stop waits for the
worker while the worker waits for the mutex, resulting in a deadlock.
Convert ar->scan.timeout to a wiphy_delayed_work. Its callback then runs
as wiphy work with the mutex held, allowing stop and other cancellation
paths to use wiphy_delayed_work_cancel() without waiting for the work
callback. The same conversion is applied to ath12k_core_halt(), which
also runs with the wiphy mutex held.
WMI event handlers call __ath12k_mac_scan_finish() while holding only
data_lock, so they cannot cancel the wiphy delayed work directly. Set
finish_queued and rely on the existing vdev_clean_wk to cancel the
timeout from wiphy context. Check finish_queued in ath12k_scan_abort()
under data_lock before changing a running scan to aborting, so a timeout
racing with completion does not abort a scan whose completion has already
queued cleanup.
Fixes: b8c67509b91ec ("wifi: ath12k: switch to using wiphy_lock() and remove ar->conf_mutex")
Suggested-by: Johannes Berg <johannes@sipsolutions.net>
Cc: stable@vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
---
Changes in v4:
- Rebase on Linux 7.3-rc2.
- Rewrite the commit message and close the completion/timeout race by
checking scan.finish_queued under data_lock.
drivers/net/wireless/ath/ath12k/core.c | 2 +-
drivers/net/wireless/ath/ath12k/core.h | 3 +-
drivers/net/wireless/ath/ath12k/mac.c | 46 ++++++++++++++++++++++----------
3 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 262a2045309b1f1b85a9ad2fc72cbfcd18bc0586..7290616607795365963f6d4b538575abce918b37 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -1444,7 +1444,7 @@ void ath12k_core_halt(struct ath12k *ar)
ath12k_mac_scan_finish(ar);
ath12k_mac_peer_cleanup_all(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &ar->scan.timeout);
cancel_work_sync(&ar->regd_update_work);
cancel_work_sync(&ar->regd_channel_update_work);
cancel_work_sync(&ab->rfkill_work);
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index a98fc6e0699d7678ddd86bc44471fc6ff46aa22c..1f6b5547f4d90b6d5299dc2c400c0cbd89a4608d 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -628,11 +628,12 @@ struct ath12k {
struct completion started;
struct completion completed;
struct completion on_channel;
- struct delayed_work timeout;
+ struct wiphy_delayed_work timeout;
enum ath12k_scan_state state;
bool is_roc;
int roc_freq;
bool roc_notify;
+ bool finish_queued;
struct wiphy_work vdev_clean_wk;
struct ath12k_link_vif *arvif;
} scan;
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 99bf5cf79d10286e9959191536aecacbbf343287..0e1e9117b79fb4532ef2117715e21e1a14d3b887 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -5231,7 +5231,7 @@ void __ath12k_mac_scan_finish(struct ath12k *ar)
ieee80211_remain_on_channel_expired(hw);
fallthrough;
case ATH12K_SCAN_STARTING:
- cancel_delayed_work(&ar->scan.timeout);
+ ar->scan.finish_queued = true;
complete_all(&ar->scan.completed);
wiphy_work_queue(ar->ah->hw->wiphy, &ar->scan.vdev_clean_wk);
break;
@@ -5311,4 +5311,6 @@ static void ath12k_scan_abort(struct ath12k *ar)
case ATH12K_SCAN_RUNNING:
+ if (ar->scan.finish_queued)
+ break;
ar->scan.state = ATH12K_SCAN_ABORTING;
spin_unlock_bh(&ar->data_lock);
@@ -5323,14 +5325,17 @@ static void ath12k_scan_abort(struct ath12k *ar)
spin_unlock_bh(&ar->data_lock);
}
-static void ath12k_scan_timeout_work(struct work_struct *work)
+static void ath12k_scan_timeout_work(struct wiphy *wiphy,
+ struct wiphy_work *work)
{
- struct ath12k *ar = container_of(work, struct ath12k,
- scan.timeout.work);
+ struct wiphy_delayed_work *dwork;
+ struct ath12k *ar;
+
+ dwork = container_of(work, struct wiphy_delayed_work, work);
+ ar = container_of(dwork, struct ath12k, scan.timeout);
+ lockdep_assert_wiphy(wiphy);
- wiphy_lock(ath12k_ar_to_hw(ar)->wiphy);
ath12k_scan_abort(ar);
- wiphy_unlock(ath12k_ar_to_hw(ar)->wiphy);
}
static void ath12k_mac_scan_send_complete(struct ath12k *ar,
@@ -5361,6 +5370,8 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
arvif = ar->scan.arvif;
+ wiphy_delayed_work_cancel(wiphy, &ar->scan.timeout);
+
/* The scan vdev has already been deleted. This can occur when a
* new scan request is made on the same vif with a different
* frequency, causing the scan arvif to move from one radio to
@@ -5392,6 +5403,7 @@ static void ath12k_scan_vdev_clean_work(struct wiphy *wiphy, struct wiphy_work *
}
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -5678,6 +5690,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
reinit_completion(&ar->scan.completed);
ar->scan.state = ATH12K_SCAN_STARTING;
ar->scan.is_roc = false;
+ ar->scan.finish_queued = false;
ar->scan.arvif = arvif;
ret = 0;
break;
@@ -5734,6 +5747,7 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
spin_unlock_bh(&ar->data_lock);
goto exit;
}
@@ -5741,9 +5755,10 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac scan started");
/* Add a margin to account for event/command processing */
- ieee80211_queue_delayed_work(ath12k_ar_to_hw(ar), &ar->scan.timeout,
- msecs_to_jiffies(arg->max_scan_time +
- ATH12K_MAC_SCAN_TIMEOUT_MSECS));
+ wiphy_delayed_work_queue(ath12k_ar_to_hw(ar)->wiphy,
+ &ar->scan.timeout,
+ msecs_to_jiffies(arg->max_scan_time +
+ ATH12K_MAC_SCAN_TIMEOUT_MSECS));
exit:
if (arg) {
@@ -5826,6 +5841,7 @@ int ath12k_mac_op_hw_scan(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.arvif = NULL;
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -5861,7 +5877,7 @@ void ath12k_mac_op_cancel_hw_scan(struct ieee80211_hw *hw,
ath12k_scan_abort(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
}
}
EXPORT_SYMBOL(ath12k_mac_op_cancel_hw_scan);
@@ -10007,7 +10023,7 @@ static void ath12k_mac_stop(struct ath12k *ar)
clear_bit(ATH12K_FLAG_CAC_RUNNING, &ar->dev_flags);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &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);
@@ -11024,6 +11040,7 @@ void ath12k_mac_op_remove_interface(struct ieee80211_hw *hw,
}
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
ar->scan_channel = NULL;
ar->scan.roc_freq = 0;
spin_unlock_bh(&ar->data_lock);
@@ -13943,7 +13960,7 @@ int ath12k_mac_op_cancel_remain_on_channel(struct ieee80211_hw *hw,
ath12k_scan_abort(ar);
- cancel_delayed_work_sync(&ar->scan.timeout);
+ wiphy_delayed_work_cancel(hw->wiphy, &ar->scan.timeout);
wiphy_work_flush(hw->wiphy, &ar->scan.vdev_clean_wk);
return 0;
@@ -14023,6 +14040,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
reinit_completion(&ar->scan.on_channel);
ar->scan.state = ATH12K_SCAN_STARTING;
ar->scan.is_roc = true;
+ ar->scan.finish_queued = false;
ar->scan.arvif = arvif;
ar->scan.roc_freq = chan->center_freq;
ar->scan.roc_notify = true;
@@ -14065,6 +14083,7 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
spin_lock_bh(&ar->data_lock);
ar->scan.state = ATH12K_SCAN_IDLE;
+ ar->scan.finish_queued = false;
spin_unlock_bh(&ar->data_lock);
return ret;
}
@@ -14078,8 +14097,8 @@ int ath12k_mac_op_remain_on_channel(struct ieee80211_hw *hw,
return -ETIMEDOUT;
}
- ieee80211_queue_delayed_work(hw, &ar->scan.timeout,
- msecs_to_jiffies(duration));
+ wiphy_delayed_work_queue(hw->wiphy, &ar->scan.timeout,
+ msecs_to_jiffies(duration));
return 0;
}
@@ -15113,6 +15132,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
ar->num_tx_chains = hweight32(pdev->cap.tx_chain_mask);
ar->num_rx_chains = hweight32(pdev->cap.rx_chain_mask);
ar->scan.arvif = NULL;
+ ar->scan.finish_queued = false;
ar->vdev_id_11d_scan = ATH12K_11D_INVALID_VDEV_ID;
spin_lock_init(&ar->data_lock);
@@ -15138,7 +15158,7 @@ static void ath12k_mac_setup(struct ath12k *ar)
ar->thermal.temperature = 0;
ar->thermal.hwmon_dev = NULL;
- INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
+ wiphy_delayed_work_init(&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);
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-15 6:58 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-17 2:56 [PATCH v3 ath-current] wifi: ath12k: convert scan timeout to wiphy delayed work Runyu Xiao
2026-07-17 14:40 ` Jeff Johnson
2026-09-11 20:36 ` Jeff Johnson
2026-09-12 8:04 ` Runyu Xiao
2026-09-15 6:57 ` [PATCH v4 " Runyu Xiao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®