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

From: Alastair D'Silva

Date: Sun Oct 11 2026 - 18:13:15 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().

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, 45 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index e39284b71837..7d4f17e96648 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
@@ -948,6 +950,13 @@ static void rtw_sdio_init(struct rtw_dev *rtwdev)
REG_SDIO_HIMR_CPWM1;
}

+static void rtw_sdio_deinit(struct rtw_dev *rtwdev)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ mutex_destroy(&rtwsdio->irq_lock);
+}
+
static void rtw_sdio_enable_rx_aggregation(struct rtw_dev *rtwdev)
{
u8 size, timeout;
@@ -1054,6 +1063,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 +1079,36 @@ 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;
+
+ 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);
}

static void rtw_sdio_deep_ps_enter(struct rtw_dev *rtwdev)
@@ -1470,6 +1496,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);
@@ -1703,6 +1734,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);

@@ -1818,7 +1854,7 @@ int rtw_sdio_probe(struct sdio_func *sdio_func,
ret = rtw_sdio_init_tx(rtwdev);
if (ret) {
rtw_err(rtwdev, "failed to init SDIO TX queue\n");
- goto err_sdio_declaim;
+ goto err_deinit;
}

ret = rtw_chip_info_setup(rtwdev);
@@ -1843,7 +1879,8 @@ int rtw_sdio_probe(struct sdio_func *sdio_func,
rtw_sdio_free_irq(rtwdev, sdio_func);
err_destroy_txwq:
rtw_sdio_deinit_tx(rtwdev);
-err_sdio_declaim:
+err_deinit:
+ rtw_sdio_deinit(rtwdev);
rtw_sdio_declaim(rtwdev, sdio_func);
err_deinit_core:
rtw_core_deinit(rtwdev);
@@ -1869,6 +1906,7 @@ void rtw_sdio_remove(struct sdio_func *sdio_func)
rtw_sdio_free_irq(rtwdev, sdio_func);
rtw_sdio_declaim(rtwdev, sdio_func);
rtw_sdio_deinit_tx(rtwdev);
+ rtw_sdio_deinit(rtwdev);
rtw_core_deinit(rtwdev);
ieee80211_free_hw(hw);
}
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h
index 6e7e6009744b..00a61415ba9c 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 interrupt mask */
+ struct mutex irq_lock;

struct workqueue_struct *txwq;
struct rtw_sdio_work_data *tx_handler_data;
--
2.53.0