[PATCH rtw-next v8 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop

From: Alastair D'Silva

Date: Wed Nov 04 2026 - 22:34:41 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, cancel the TX
worker synchronously in rtw_sdio_stop(), and purge any remaining frames in
rtwsdio->tx_queue[] while waking any stopped AC queues.

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 | 46 ++++++++++++++++++++++-
drivers/net/wireless/realtek/rtw88/sdio.h | 3 ++
2 files changed, 47 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 172aa7ad4732a..e318fefe5b5e7 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
@@ -1054,6 +1056,7 @@ static int rtw_sdio_8723bs_check_rqpn(struct rtw_dev *rtwdev)

static int rtw_sdio_start(struct rtw_dev *rtwdev)
{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
u32 clear;

if (rtw_is_8723bs(rtwdev)) {
@@ -1069,20 +1072,49 @@ static int rtw_sdio_start(struct rtw_dev *rtwdev)

rtw_sdio_enable_rx_aggregation(rtwdev);

+ 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;
+ enum rtw_tx_queue_type queue;
+ int i;
+
+ 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);
+
+ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++)
+ ieee80211_purge_tx_queue(rtwdev->hw, &rtwsdio->tx_queue[i]);
+
+ for (i = 0; i < IEEE80211_NUM_ACS; i++) {
+ queue = rtw_tx_ac_to_hwq(i);
+ if (rtwsdio->tx_queue_stopped[queue]) {
+ rtwsdio->tx_queue_stopped[queue] = false;
+ ieee80211_wake_queue(rtwdev->hw, i);
+ }
+ }
}

static void rtw_sdio_deep_ps_enter(struct rtw_dev *rtwdev)
@@ -1477,6 +1509,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 +1747,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