[PATCH rtw-next v4 3/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm

From: Alastair D'Silva

Date: Mon Oct 05 2026 - 04:50:38 EST


On 3081-based SDIO chips (e.g. RTL8821CS), hardware does not automatically
clear REG_SDIO_HISR_RX_REQUEST when the RX buffer is empty. Masking this
bit out before writing back to HISR prevents it from being acknowledged,
causing an infinite interrupt storm loop that locks up the system.

8051-based chips instead rely on hardware to clear this bit automatically
once the buffer is empty, and software must not clear it.

Split rtw_sdio_handle_interrupt() into separate 8051 and 3081 handlers.
For 3081, disable interrupts via HIMR, acknowledge pending status bits
via W1C writeback, service pending events, and re-enable interrupts.
For 8051 chips, preserve the existing behavior.

Fixes: 65371a3f14e7 ("wifi: rtw88: sdio: Add HCI implementation for SDIO based chipsets")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Alastair D'Silva <alastair@xxxxxxxxxxx>
---
drivers/net/wireless/realtek/rtw88/sdio.c | 60 +++++++++++++++++------
1 file changed, 44 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index d2f4d7e8bc83..c74644bf14ba 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1466,23 +1466,8 @@ static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev)
} while (total_rx_bytes < SZ_64K && hisr & REG_SDIO_HISR_RX_REQUEST);
}

-static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
+static void rtw_sdio_handle_interrupt_8051(struct rtw_dev *rtwdev, u32 hisr)
{
- struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func);
- struct rtw_sdio *rtwsdio;
- struct rtw_dev *rtwdev;
- u32 hisr;
-
- rtwdev = hw->priv;
- rtwsdio = (struct rtw_sdio *)rtwdev->priv;
-
- if (!rtwsdio->running)
- return;
-
- rtwsdio->irq_thread = current;
-
- hisr = rtw_read32(rtwdev, REG_SDIO_HISR);
-
if (hisr & REG_SDIO_HISR_TXERR)
rtw_sdio_tx_err_isr(rtwdev);
if (hisr & REG_SDIO_HISR_RX_REQUEST) {
@@ -1499,7 +1484,50 @@ static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
hisr &= RTW_SDIO_HISR_CLEAR_MASK;

rtw_write32(rtwdev, REG_SDIO_HISR, hisr);
+}
+
+static void rtw_sdio_handle_interrupt_3081(struct rtw_dev *rtwdev, u32 hisr)
+{
+ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ rtw_sdio_disable_interrupt(rtwdev);
+ rtw_write32(rtwdev, REG_SDIO_HISR, hisr);
+
+ if (hisr & REG_SDIO_HISR_TXERR)
+ rtw_sdio_tx_err_isr(rtwdev);
+ if (hisr & REG_SDIO_HISR_RX_REQUEST)
+ rtw_sdio_rx_isr(rtwdev);
+
+ /* Unmasking HIMR re-asserts the IRQ line if new packets arrived */
+ if (rtwsdio->running)
+ rtw_sdio_enable_interrupt(rtwdev);
+}
+
+static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
+{
+ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func);
+ struct rtw_sdio *rtwsdio;
+ struct rtw_dev *rtwdev;
+ u32 hisr;
+
+ rtwdev = hw->priv;
+ rtwsdio = (struct rtw_sdio *)rtwdev->priv;
+
+ if (!rtwsdio->running)
+ return;
+
+ rtwsdio->irq_thread = current;
+
+ hisr = rtw_read32(rtwdev, REG_SDIO_HISR);
+ if (!hisr)
+ goto out;
+
+ if (rtw_chip_wcpu_8051(rtwdev))
+ rtw_sdio_handle_interrupt_8051(rtwdev, hisr);
+ else
+ rtw_sdio_handle_interrupt_3081(rtwdev, hisr);

+out:
rtwsdio->irq_thread = NULL;
}

--
2.53.0