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@vger.kernel.org Assisted-by: LLM Signed-off-by: Alastair D'Silva --- 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@d-silva.org/ 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