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@vger.kernel.org Assisted-by: LLM Signed-off-by: Alastair D'Silva --- drivers/net/wireless/realtek/rtw88/sdio.c | 33 +++++++++++++++++++++-- drivers/net/wireless/realtek/rtw88/sdio.h | 3 +++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c index e39284b71837..2e26e7a2a0fd 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,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 +1489,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 +1727,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 6e7e6009744b..64266b54f595 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