[PATCH ath-next v3 10/10] wifi: ath10k: drop async writes and sleep timer across power cycles
Kang Yang
kang.yang at oss.qualcomm.com
Thu Nov 12 03:26:09 PST 2026
ath10k_sdio_hif_tx_sg() queues writes on ar_sdio->wr_asyncq and
schedules wr_async_work. The SDIO sleep timer also schedules this
work, and the work may re-arm the timer.
The current shutdown sequence does not prevent this activity from
surviving an SDIO power cycle:
- ath10k_sdio_hif_stop() drains wr_asyncq once, but new writes may
still be queued before the card is marked disabled.
- ath10k_sdio_hif_power_down() deletes the sleep timer but does not
cancel wr_async_work.
- ath10k_sdio_hif_power_up() does not clear work or requests left
from the previous power cycle.
As a result, stale work may access the SDIO bus while the device is
being reinitialized in BMI mode, causing errors such as:
failed to read from address 0x868: -110
failed to read fifo/chip control register: -110
bmi communication timeout
could not write board data (-110)
Mark the card disabled before power-down begins so new asynchronous
transfers are rejected. Make wr_async_work drop requests that raced
with the state change instead of accessing the bus.
Synchronize the sleep timer and wr_async_work, then drain wr_asyncq
during both power-down and power-up so no request from an earlier
power cycle can run during BMI initialization. Factor the queue
cleanup into a helper shared with ath10k_sdio_hif_stop().
A dropped request is completed the same way as a written one, only
without the bus access. HTC messages go through
ath10k_htc_notify_tx_completion(), which lets the endpoint release the
skb. The IRQ disable request signals its completion and leaves the skb
to the waiter, which frees it. Every bus request is returned to the
free pool, so nothing is leaked.
Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1
Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189
Signed-off-by: Kang Yang <kang.yang at oss.qualcomm.com>
---
drivers/net/wireless/ath/ath10k/sdio.c | 78 ++++++++++++++++++++++++----------
1 file changed, 56 insertions(+), 22 deletions(-)
diff --git a/drivers/net/wireless/ath/ath10k/sdio.c b/drivers/net/wireless/ath/ath10k/sdio.c
index fdf19ce4b55b..f0e1feacb629 100644
--- a/drivers/net/wireless/ath/ath10k/sdio.c
+++ b/drivers/net/wireless/ath/ath10k/sdio.c
@@ -1461,6 +1461,33 @@ static void ath10k_sdio_sleep_timer_handler(struct timer_list *t)
queue_work(ar_sdio->workqueue, &ar_sdio->wr_async_work);
}
+/* Drop any requests still queued on wr_asyncq without touching the bus.
+ * Caller must ensure wr_async_work is not running concurrently.
+ */
+static void ath10k_sdio_drop_wr_async_reqs(struct ath10k *ar)
+{
+ struct ath10k_sdio *ar_sdio = ath10k_sdio_priv(ar);
+ struct ath10k_sdio_bus_request *req, *tmp_req;
+
+ spin_lock_bh(&ar_sdio->wr_async_lock);
+
+ list_for_each_entry_safe(req, tmp_req, &ar_sdio->wr_asyncq, list) {
+ struct ath10k_htc_ep *ep;
+
+ list_del(&req->list);
+
+ if (req->htc_msg) {
+ ep = &ar->htc.endpoint[req->eid];
+ ath10k_htc_notify_tx_completion(ep, req->skb);
+ } else if (req->comp) {
+ complete(req->comp);
+ }
+ ath10k_sdio_free_bus_req(ar, req);
+ }
+
+ spin_unlock_bh(&ar_sdio->wr_async_lock);
+}
+
static void ath10k_sdio_write_async_work(struct work_struct *work)
{
struct ath10k_sdio *ar_sdio = container_of(work, struct ath10k_sdio,
@@ -1469,6 +1496,15 @@ static void ath10k_sdio_write_async_work(struct work_struct *work)
struct ath10k_sdio_bus_request *req, *tmp_req;
struct ath10k_mbox_info *mbox_info = &ar_sdio->mbox_info;
+ /* The card is powered down; drop pending requests instead of
+ * touching the bus. Prevents stale writes from racing with a
+ * concurrent BMI power-up on the next recovery cycle.
+ */
+ if (ar_sdio->is_disabled) {
+ ath10k_sdio_drop_wr_async_reqs(ar);
+ return;
+ }
+
spin_lock_bh(&ar_sdio->wr_async_lock);
list_for_each_entry_safe(req, tmp_req, &ar_sdio->wr_asyncq, list) {
@@ -1586,6 +1622,13 @@ static int ath10k_sdio_hif_power_up(struct ath10k *ar,
if (!ar_sdio->is_disabled)
return 0;
+ /* Flush any sleep timer or async work left over from the previous
+ * cycle so they cannot fire during BMI and poke the bus.
+ */
+ timer_delete_sync(&ar_sdio->sleep_timer);
+ cancel_work_sync(&ar_sdio->wr_async_work);
+ ath10k_sdio_drop_wr_async_reqs(ar);
+
ath10k_dbg(ar, ATH10K_DBG_BOOT, "sdio power on\n");
ret = ath10k_sdio_config(ar);
@@ -1629,7 +1672,16 @@ static void ath10k_sdio_hif_power_down(struct ath10k *ar)
ath10k_dbg(ar, ATH10K_DBG_BOOT, "sdio power off\n");
+ /* Reject new async writes from ath10k_sdio_hif_tx_sg() and make
+ * ath10k_sdio_write_async_work() drop any already-queued
+ * requests before they touch the bus.
+ */
+ ar_sdio->is_disabled = true;
+
timer_delete_sync(&ar_sdio->sleep_timer);
+ cancel_work_sync(&ar_sdio->wr_async_work);
+ ath10k_sdio_drop_wr_async_reqs(ar);
+
ath10k_sdio_set_mbox_sleep(ar, true);
/* Disable the card */
@@ -1647,8 +1699,6 @@ static void ath10k_sdio_hif_power_down(struct ath10k *ar)
ath10k_warn(ar, "unable to reset sdio: %d\n", ret);
sdio_release_host(ar_sdio->func);
-
- ar_sdio->is_disabled = true;
}
static int ath10k_sdio_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
@@ -1659,6 +1709,9 @@ static int ath10k_sdio_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
struct sk_buff *skb;
int ret, i;
+ if (ar_sdio->is_disabled)
+ return -ESHUTDOWN;
+
eid = pipe_id_to_eid(pipe_id);
for (i = 0; i < n_items; i++) {
@@ -1972,7 +2025,6 @@ static void ath10k_sdio_irq_disable(struct ath10k *ar)
static void ath10k_sdio_hif_stop(struct ath10k *ar)
{
- struct ath10k_sdio_bus_request *req, *tmp_req;
struct ath10k_sdio *ar_sdio = ath10k_sdio_priv(ar);
struct sk_buff *skb;
@@ -1984,25 +2036,7 @@ static void ath10k_sdio_hif_stop(struct ath10k *ar)
dev_kfree_skb_any(skb);
cancel_work_sync(&ar_sdio->wr_async_work);
-
- spin_lock_bh(&ar_sdio->wr_async_lock);
-
- /* Free all bus requests that have not been handled */
- list_for_each_entry_safe(req, tmp_req, &ar_sdio->wr_asyncq, list) {
- struct ath10k_htc_ep *ep;
-
- list_del(&req->list);
-
- if (req->htc_msg) {
- ep = &ar->htc.endpoint[req->eid];
- ath10k_htc_notify_tx_completion(ep, req->skb);
- } else if (req->skb) {
- kfree_skb(req->skb);
- }
- ath10k_sdio_free_bus_req(ar, req);
- }
-
- spin_unlock_bh(&ar_sdio->wr_async_lock);
+ ath10k_sdio_drop_wr_async_reqs(ar);
ath10k_core_napi_sync_disable(ar);
}
--
2.34.1
More information about the ath10k
mailing list