[PATCH rtw-next v9 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop
From: Alastair D'Silva
Date: Fri Nov 06 2026 - 22:55:22 EST
When rtw_sdio_stop() disables interrupts, in-flight interrupt handlers or
delayed TX work may still run against a powered-down device.
Track the operational state in rtwsdio->running with an irq_lock mutex
(similar to PCI), check it at the entry of rtw_sdio_handle_interrupt()
and rtw_sdio_tx_handler(), serialize state transitions in rtw_sdio_start()
and rtw_sdio_stop() under sdio_claim_host() and irq_lock, and cancel the
TX worker synchronously in rtw_sdio_stop().
Drain rtwsdio->tx_queue[] with locked skb_dequeue() and
ieee80211_free_txskb() in rtw_sdio_stop() and again in rtw_sdio_start() to
catch any frames queued by an in-flight rtwdev->tx_work after
rtw_sdio_stop() completes, and reset rtwsdio->tx_queue_stopped[] while
waking stopped AC queues under tx_queue[].lock in rtw_sdio_start().
Fixes: 28eaf7971a20 ("wifi: rtw88: sdio: add TX back-pressure and retry on page starvation")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Alastair D'Silva <alastair@xxxxxxxxxxx>
---
drivers/net/wireless/realtek/rtw88/sdio.c | 60 ++++++++++++++++++++++-
drivers/net/wireless/realtek/rtw88/sdio.h | 3 ++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 172aa7ad4732a..3edbc627f9707 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -941,6 +941,8 @@ static void rtw_sdio_init(struct rtw_dev *rtwdev)
{
struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ mutex_init(&rtwsdio->irq_lock);
+
if (rtw_is_8723bs(rtwdev))
rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST;
else
@@ -1052,9 +1054,24 @@ static int rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)
return 0;
}
+static void rtw_sdio_purge_tx_queues(struct rtw_dev *rtwdev)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ struct sk_buff *skb;
+ int i;
+
+ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) {
+ while ((skb = skb_dequeue(&rtwsdio->tx_queue[i])))
+ ieee80211_free_txskb(rtwdev->hw, skb);
+ }
+}
+
static int rtw_sdio_start(struct rtw_dev *rtwdev)
{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ enum rtw_tx_queue_type queue;
u32 clear;
+ int i;
if (rtw_is_8723bs(rtwdev)) {
int ret = rtw_sdio_8723bs_check_rqpn(rtwdev);
@@ -1069,20 +1086,49 @@ static int rtw_sdio_start(struct rtw_dev *rtwdev)
rtw_sdio_enable_rx_aggregation(rtwdev);
+ rtw_sdio_purge_tx_queues(rtwdev);
+
+ for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+ queue = rtw_tx_ac_to_hwq(i);
+ scoped_guard(spinlock_irqsave, &rtwsdio->tx_queue[queue].lock) {
+ if (rtwsdio->tx_queue_stopped[queue]) {
+ rtwsdio->tx_queue_stopped[queue] = false;
+ ieee80211_wake_queue(rtwdev->hw, i);
+ }
+ }
+ }
+
+ sdio_claim_host(rtwsdio->sdio_func);
+
if (rtw_is_8723bs(rtwdev)) {
clear = rtw_read32(rtwdev, REG_SDIO_HISR) & RTW_SDIO_HISR_CLEAR_MASK;
if (clear)
rtw_write32(rtwdev, REG_SDIO_HISR, clear);
}
- rtw_sdio_enable_interrupt(rtwdev);
+ scoped_guard(mutex, &rtwsdio->irq_lock) {
+ rtwsdio->running = true;
+ rtw_sdio_enable_interrupt(rtwdev);
+ }
+
+ sdio_release_host(rtwsdio->sdio_func);
return 0;
}
static void rtw_sdio_stop(struct rtw_dev *rtwdev)
{
- rtw_sdio_disable_interrupt(rtwdev);
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ sdio_claim_host(rtwsdio->sdio_func);
+ scoped_guard(mutex, &rtwsdio->irq_lock) {
+ rtwsdio->running = false;
+ rtw_sdio_disable_interrupt(rtwdev);
+ }
+ sdio_release_host(rtwsdio->sdio_func);
+
+ cancel_delayed_work_sync(&rtwsdio->tx_handler_data->work);
+ rtw_sdio_purge_tx_queues(rtwdev);
}
static void rtw_sdio_deep_ps_enter(struct rtw_dev *rtwdev)
@@ -1477,6 +1523,11 @@ static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
rtwdev = hw->priv;
rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ scoped_guard(mutex, &rtwsdio->irq_lock) {
+ if (!rtwsdio->running)
+ return;
+ }
+
rtwsdio->irq_thread = current;
hisr = rtw_read32(rtwdev, REG_SDIO_HISR);
@@ -1710,6 +1761,11 @@ static void rtw_sdio_tx_handler(struct work_struct *work)
rtwdev = work_data->rtwdev;
rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+ scoped_guard(mutex, &rtwsdio->irq_lock) {
+ if (!rtwsdio->running)
+ return;
+ }
+
if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE))
rtw_sdio_deep_ps_leave(rtwdev);
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
index 6e7e6009744b0..64266b54f595d 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.h
+++ b/drivers/net/wireless/realtek/rtw88/sdio.h
@@ -168,6 +168,9 @@ struct rtw_sdio {
bool sdio3_bus_mode;
void *irq_thread;
+ bool running;
+ /* protect running state and enabling interrupts */
+ struct mutex irq_lock;
struct workqueue_struct *txwq;
struct rtw_sdio_work_data *tx_handler_data;
--
2.53.0