[PATCH v3 1/2] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm
From: Alastair D'Silva
Date: Wed Sep 30 2026 - 20:06:17 EST
8051 and 3081 SDIO chipsets handle the REG_SDIO_HISR_RX_REQUEST status bit
differently:
- 8051-based chips (e.g. RTL8723BS, RTL8723CS, RTL8723DS):
The hardware automatically clears REG_SDIO_HISR_RX_REQUEST once the RX
buffer is empty. Software must not clear this bit, because the drain
loop in rtw_sdio_rx_isr() re-reads REG_SDIO_HISR across iterations to
decide whether more requests are pending. Clearing it in software
terminates the loop after a single request, stranding the remainder of
the FIFO. Additionally, RTL8723BS requires RTW_SDIO_HISR_CLEAR_MASK to
avoid undefined bits causing resume storms.
- 3081-based chips (e.g. RTL8821CS, RTL8822CS):
The hardware does not automatically clear REG_SDIO_HISR_RX_REQUEST when
the RX buffer is empty. Masking this bit out in software before writing
back to HISR prevented it from ever being acknowledged in hardware,
trapping the CPU core in an infinite interrupt storm loop that starved
RCU and locked up the system. Furthermore, on 3081 chips the physical
RX FIFO capacity is at most 24 KB (16 KB on RTL8821CS, 24 KB on
RTL8822CS), which is well within the 64 KB loop budget.
As the number of architecture-specific special cases has grown (16-bit vs
32-bit register widths, differing HISR writeback timing, synthetic loop
flags, and RTL8723BS resume masking), attempting to accommodate both
architectures within a single monolithic handler has become fragile and
prone to cross-architecture regressions.
Resolve this by making rtw_sdio_handle_interrupt() a dispatcher with
separate paths for 8051 and 3081:
1. 8051 chips preserve the existing unmasked writeback behavior, leaving
REG_SDIO_HISR_RX_REQUEST for hardware to drop and respecting
RTW_SDIO_HISR_CLEAR_MASK on RTL8723BS.
2. 3081 chips adopt the interrupt masking pattern: disable HIMR,
acknowledge pending status bits in HISR via W1C, service the pending
events, and re-enable HIMR. Any packet arriving during servicing
latches REG_SDIO_HISR_RX_REQUEST in hardware and re-asserts the IRQ line
once unmasked.
Splitting into separate handlers isolates these quirks cleanly and
simplifies future maintenance.
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>
---
v3:
- Rebased on rtw-next.
- Preserved 8051 interrupt handling behavior and RTW_SDIO_HISR_CLEAR_MASK
by splitting rtw_sdio_handle_interrupt() into separate 8051 and 3081
dispatch routines (thanks Luka Gejak for the review and analysis).
- Moved hisr reading and early exit on spurious IRQ into the dispatcher.
- Verified on Allwinner H618 (Mellow Fly-C5) with RTL8821CS under Linux 7.2:
* Baseline (0 us delay): 36.7 Mbps UDP (0.0% loss), 35.1 Mbps TCP, 0 RCU stalls.
HIMR toggle overhead is 34.2 us (10.3% of ISR duration).
* Injected delays (500 us, 2 ms, 10 ms): zero packet loss (0.0%), masked
arrivals verified latched and re-asserted upon unmasking (7.5% -> 16.5%
of IRQs catching masked packet arrivals).
- Added Assisted-by tag per Documentation/process/coding-assistants.rst.
v2:
- Reworked interrupt acknowledgment to use HIMR masking
(rtw_sdio_disable_interrupt / rtw_sdio_enable_interrupt) to avoid races
between reading RX0_REQ_LEN and clearing HISR.
v1:
- https://lore.kernel.org/linux-wireless/20260921085502.123456-1-alastair@xxxxxxxxxxx/
drivers/net/wireless/realtek/rtw88/sdio.c | 51 +++++++++++++++++------
1 file changed, 38 insertions(+), 13 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index dc2fd0f8f9ff..63d62242ac1e 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1455,20 +1455,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;
-
- 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) {
@@ -1485,7 +1473,44 @@ 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)
+{
+ 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 */
+ 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;
+
+ 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