Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm
@ 2026-10-31  3:35 Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 1/4] wifi: rtw88: sdio: Handle allocation and read errors in rtw_sdio_rxfifo_recv Alastair D'Silva
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  3:35 UTC (permalink / raw)
  To: linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Luka Gejak, Martin Blumenstingl, Jernej Skrabec, Ulf Hansson,
	linux-kernel, Alastair D'Silva

On 3081-based SDIO chips (observed on RTL8821CS; RTL8822BS and RTL8822CS
are inferred to be affected as they share the 3081 architecture), clearing
REG_SDIO_HISR_RX_REQUEST requires both a software W1C acknowledge and an
empty RX FIFO. Masking this bit out before writing back to HISR prevents
the software acknowledge from happening, leaving the bit asserted even
after the FIFO is drained and trapping the CPU core in an infinite
interrupt storm that locks up the system upon receiving network traffic.

This series fixes the interrupt storm on 3081 chips and addresses
related SDIO lifecycle, locking, and error handling issues:

- Patch 1 returns error codes from rtw_sdio_rxfifo_recv() on allocation
  or read failures so the caller stops immediately instead of burning
  through the loop budget.
- Patch 2 tracks operational state via rtwsdio->running protected by an
  irq_lock mutex (using a mutex as SDIO bus operations sleep), serializes
  start/stop state transitions under sdio_claim_host() and irq_lock,
  cancels the TX worker synchronously on stop, and re-arms it on start to
  drain any parked frames and wake stopped queues.
- Patch 3 fixes the interrupt storm by splitting
  rtw_sdio_handle_interrupt() into 8051 and 3081 paths. On 3081,
  interrupts are masked via HIMR, acknowledged via W1C writeback,
  serviced, and re-enabled under irq_lock if still running.
- Patch 4 refactors rtw_sdio_rx_isr() into separate 8051 and 3081 variants
  to clarify the differences in register width and HISR handling without
  any logic changes.

Hardware Validation:
Validated on an Allwinner H618 board (Mellow Fly-C5) with RTL8821CS SDIO
on the sunxi-mmc host controller (which dispatches SDIO interrupts via the
ksdioirqd thread rather than MMC_CAP2_SDIO_IRQ_NOTHREAD / sdio_irq_work)
running Linux 7.2.10, with policy routing configured and verified via
interface byte counters so all test traffic traversed wlan0 exclusively:
- RX test (10s iperf3 to board): 36.5 MB sent (30.6 Mbits/sec),
  34.2 MB received (28.7 Mbits/sec), with zero stalls, zero lockups, and
  no interrupt storm.
- TX test (10s iperf3 -R from board): 40.5 MB sent (34.0 Mbits/sec),
  40.0 MB received (33.6 Mbits/sec).
- Bidirectional test (10s iperf3 --bidir simultaneous TX and RX):
  RX 27.0 MB sent (22.6 Mbits/sec) / 26.4 MB received (22.0 Mbits/sec)
  concurrently with TX 11.5 MB sent (9.68 Mbits/sec) / 11.1 MB received
  (9.28 Mbits/sec).
- Interface stop/start cycling (ip link set wlan0 down/up) and dmesg
  verified clean with zero warnings, traces, or hangs.

Differences in v7:
- Rebased onto latest rtw-next.
- Cover letter and Patch 3: Included RTL8822BS alongside RTL8822CS as
  inferred from sharing the 3081 architecture, and reworded the opening
  explanation to state that clearing REG_SDIO_HISR_RX_REQUEST requires
  both a software W1C acknowledge and an empty RX FIFO (per Luka Gejak).
- Patch 2: Re-arm the TX worker at the end of rtw_sdio_start() so any
  frames left parked in rtwsdio->tx_queue[] across a stop are drained and
  stopped mac80211 queues are woken (per Luka Gejak).

Differences in v6:
- Cover letter: Clarified that RTL8821CS was observed and tested while
  RTL8822CS is inferred from sharing the 3081 architecture, and noted that
  testing was performed on sunxi-mmc (ksdioirqd) per Luka Gejak. Added
  details below the cut line on how REG_SDIO_HISR_RX_REQUEST level
  sensitivity was verified on hardware.
- Patch 1: Added Reviewed-by from Luka Gejak.
- Patch 2: Dropped the rtw_sdio_deinit() helper and probe error path label
  rename per Luka Gejak, and refined the irq_lock comment in sdio.h.
- Patch 3: Replaced goto out with if (hisr) block to avoid mixing goto
  with cleanup.h helpers in rtw_sdio_handle_interrupt(), and documented
  why the full HISR word is written back on 3081 (matching all non-8723BS
  SDIO chips) and that REG_SDIO_HISR_RX_REQUEST is level-sensitive to RX
  FIFO occupancy (verified on hardware), addressing Luka Gejak's feedback.
- Patch 4: Added Reviewed-by from Luka Gejak.

Differences in v5:
- Patch 1: Added Acked-by from Ping-Ke Shih.
- Patch 2: Updated Fixes: tag to commit 28eaf7971a20 ("wifi: rtw88: sdio:
  add TX back-pressure and retry on page starvation") per discussion
  between Ping-Ke Shih and Luka Gejak.
- Patch 2: Replaced lockless running checks with an irq_lock mutex in
  struct rtw_sdio. Since SDIO I/O sleeps, a mutex is used instead of a
  spinlock. Enforced lock ordering (sdio_claim_host() -> irq_lock) in
  start and stop paths to avoid lock inversion with the MMC IRQ handler.
- Patch 2: Retained running checks in rtw_sdio_tx_handler() and
  rtw_sdio_handle_interrupt() to avoid races during stop.
- Patch 3: Protected the 3081 interrupt re-enable check under irq_lock
  (scoped_guard) per feedback from Ping-Ke Shih.
- Patch 4: Fixed comment formatting style in rtw_sdio_rx_isr_8051() per
  coding style rules.
- Patch 4: Added Acked-by from Ping-Ke Shih.

Differences in v4:
- Split pre-existing lifecycle and error handling issues into separate
  patches (Patches 1 and 2) prior to the storm fix (per Luka Gejak).
- Guard HIMR re-enabling with rtwsdio->running in the 3081 handler.
- Significantly reduced and streamlined commit messages across the series
  (per Ping-Ke Shih).
- Clarified in Patch 4 that it is a refactoring without logic changes,
  highlighting register width and HISR behavior (per Ping-Ke Shih).
- Tagged with rtw-next in subject prefix for NIPA testing.

---
Verifying REG_SDIO_HISR_RX_REQUEST level-sensitivity on RTL8821CS (3081):

To verify Luka's question on whether acknowledging REG_SDIO_HISR before
draining the RX FIFO can strand unread packets if rtw_sdio_rx_isr_3081()
exits early (upon hitting the 64 KB loop budget or an allocation/read
error), we instrumented rtw_sdio_handle_interrupt_3081() and
rtw_sdio_rx_isr_3081() on the RTL8821CS test board with a 4-point register
probe and a forced early-exit stress parameter (test_early_exit=1, which
forces rtw_sdio_rx_isr_3081() to break after reading a single RX DMA batch
so unread packets remain in the hardware FIFO):

1. Normal single-packet drain (FIFO emptied during ISR):
   - Entry: hisr=0x00800031 (BIT_RX_REQUEST=1), rx0_init=334
   - After W1C writeback (before FIFO read): post_ack_hisr=0x00800031,
     rx0_len=334. Even though software wrote 1 to clear BIT_RX_REQUEST,
     the hardware immediately kept BIT_RX_REQUEST asserted (1) because 334
     bytes of unread packet data were still sitting in the RX FIFO.
   - After rtw_sdio_rxfifo_recv() drained the 334 bytes: rx0_rem=0,
     post_drain_hisr=0x00800030 (BIT_RX_REQUEST=0). Only once the FIFO was
     empty AND the W1C acknowledge had occurred did BIT_RX_REQUEST drop to
     0.

2. Forced early exit with unread data remaining in the FIFO:
   - Under an iperf3 UDP flood with test_early_exit=1, the ISR exited
     after one read while unread packets remained in the FIFO (e.g.
     rx0_init=1538 -> post_ack_hisr=0x00800031 -> rx0_rem=3096).
   - Both post_drain_hisr and post_unmask_hisr read 0x00800031
     (BIT_RX_REQUEST=1), and the hardware immediately re-asserted the SDIO
     interrupt line upon unmasking HIMR. Over 1,920 forced early exits in
     a single burst, 100% re-asserted BIT_RX_REQUEST and re-triggered the
     interrupt without a single stalled packet.

Alastair D'Silva (4):
  wifi: rtw88: sdio: Handle allocation and read errors in
    rtw_sdio_rxfifo_recv
  wifi: rtw88: sdio: Track running state and cancel TX worker on stop
  wifi: rtw88: sdio: Fix unhandled RX request interrupt storm
  wifi: rtw88: sdio: Split rtw_sdio_rx_isr into 8051 and 3081 variants

 drivers/net/wireless/realtek/rtw88/sdio.c | 165 ++++++++++++++++------
 drivers/net/wireless/realtek/rtw88/sdio.h |   3 +
 2 files changed, 128 insertions(+), 40 deletions(-)

-- 
2.53.0

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v7 1/4] wifi: rtw88: sdio: Handle allocation and read errors in rtw_sdio_rxfifo_recv
  2026-10-31  3:35 [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
@ 2026-10-31  3:35 ` Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop Alastair D'Silva
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  3:35 UTC (permalink / raw)
  To: linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Luka Gejak, Martin Blumenstingl, Jernej Skrabec, Ulf Hansson,
	linux-kernel, Alastair D'Silva, stable

When dev_alloc_skb() or rtw_sdio_read_port() fails, rtw_sdio_rxfifo_recv()
returns without reading data. Because it returned void, the caller still
counted rx_len against the loop budget while leaving data stranded in the
FIFO.

Return an error code from rtw_sdio_rxfifo_recv() on failure and break out
of the receive loop in the caller immediately.

Fixes: 65371a3f14e7 ("wifi: rtw88: sdio: Add HCI implementation for SDIO based chipsets")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Reviewed-by: Luka Gejak <luka.gejak@linux.dev>
Signed-off-by: Alastair D'Silva <alastair@d-silva.org>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index dc2fd0f8f9ff..e39284b71837 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1349,7 +1349,7 @@ static void rtw_sdio_rx_skb(struct rtw_dev *rtwdev, struct sk_buff *skb,
 	ieee80211_rx_irqsafe(rtwdev->hw, skb);
 }
 
-static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
+static int rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
 {
 	struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv;
 	const struct rtw_chip_info *chip = rtwdev->chip;
@@ -1366,12 +1366,12 @@ static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
 
 	skb = dev_alloc_skb(bufsz);
 	if (!skb)
-		return;
+		return -ENOMEM;
 
 	ret = rtw_sdio_read_port(rtwdev, skb->data, bufsz);
 	if (ret) {
 		dev_kfree_skb_any(skb);
-		return;
+		return ret;
 	}
 
 	while (true) {
@@ -1380,7 +1380,7 @@ static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
 					   &pkt_stat, &rx_status);
 		if (ret) {
 			dev_kfree_skb_any(skb);
-			return;
+			return ret;
 		}
 
 		pkt_offset = pkt_desc_sz + pkt_stat.drv_info_sz +
@@ -1416,11 +1416,14 @@ static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
 		skb_reserve(skb, curr_pkt_len);
 		rx_len -= curr_pkt_len;
 	}
+
+	return 0;
 }
 
 static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev)
 {
 	u32 rx_len, hisr, total_rx_bytes = 0;
+	int ret;
 
 	do {
 		if (rtw_chip_wcpu_8051(rtwdev))
@@ -1431,7 +1434,9 @@ static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev)
 		if (!rx_len)
 			break;
 
-		rtw_sdio_rxfifo_recv(rtwdev, rx_len);
+		ret = rtw_sdio_rxfifo_recv(rtwdev, rx_len);
+		if (ret)
+			break;
 
 		total_rx_bytes += rx_len;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop
  2026-10-31  3:35 [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 1/4] wifi: rtw88: sdio: Handle allocation and read errors in rtw_sdio_rxfifo_recv Alastair D'Silva
@ 2026-10-31  3:35 ` Alastair D'Silva
  2026-10-31  7:10   ` Luka Gejak
  2026-10-31  3:35 ` [PATCH rtw-next v7 3/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 4/4] wifi: rtw88: sdio: Split rtw_sdio_rx_isr into 8051 and 3081 variants Alastair D'Silva
  3 siblings, 1 reply; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  3:35 UTC (permalink / raw)
  To: linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Luka Gejak, Martin Blumenstingl, Jernej Skrabec, Ulf Hansson,
	linux-kernel, Alastair D'Silva, stable

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, cancel the TX
worker synchronously in rtw_sdio_stop(), and re-arm it in rtw_sdio_start()
so any frames left parked across a stop are drained and stopped queues are
woken.

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 <alastair@d-silva.org>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 35 +++++++++++++++++++++--
 drivers/net/wireless/realtek/rtw88/sdio.h |  3 ++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index e39284b71837..253467250ee6 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,38 @@ 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);
+
+	queue_delayed_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work, 0);
 
 	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 +1491,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 +1729,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


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v7 3/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm
  2026-10-31  3:35 [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 1/4] wifi: rtw88: sdio: Handle allocation and read errors in rtw_sdio_rxfifo_recv Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop Alastair D'Silva
@ 2026-10-31  3:35 ` Alastair D'Silva
  2026-10-31  3:35 ` [PATCH rtw-next v7 4/4] wifi: rtw88: sdio: Split rtw_sdio_rx_isr into 8051 and 3081 variants Alastair D'Silva
  3 siblings, 0 replies; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  3:35 UTC (permalink / raw)
  To: linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Luka Gejak, Martin Blumenstingl, Jernej Skrabec, Ulf Hansson,
	linux-kernel, Alastair D'Silva, stable

On 3081-based SDIO chips (observed on RTL8821CS; RTL8822BS and RTL8822CS
are inferred to be affected as they share the 3081 architecture), clearing
REG_SDIO_HISR_RX_REQUEST requires both a software W1C acknowledge and an
empty RX FIFO. Masking this bit out before writing back to HISR prevents
the software acknowledge from happening, leaving the bit asserted even
after the FIFO is drained and causing an infinite interrupt storm loop
that locks up the system.

8051-based chips instead clear this bit in hardware once the RX buffer is
empty without a W1C writeback, and software must not clear it.

Split rtw_sdio_handle_interrupt() into separate 8051 and 3081 handlers.
For 3081, disable interrupts via HIMR, acknowledge all pending status
bits via W1C writeback (preserving the existing full status word
writeback used on non-RTL8723BS chips, now including
REG_SDIO_HISR_RX_REQUEST), service pending events, and re-enable
interrupts under irq_lock if still running.

If unread packets remain in the FIFO when interrupts are re-enabled (or
if new packets arrived), REG_SDIO_HISR_RX_REQUEST remains asserted and
re-triggers the IRQ line. For 8051 chips, preserve the existing behavior.

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 <alastair@d-silva.org>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 73 ++++++++++++++++++-----
 1 file changed, 57 insertions(+), 16 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 253467250ee6..75a5f5a96169 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1481,6 +1481,58 @@ 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_8051(struct rtw_dev *rtwdev, u32 hisr)
+{
+	if (hisr & REG_SDIO_HISR_TXERR)
+		rtw_sdio_tx_err_isr(rtwdev);
+	if (hisr & REG_SDIO_HISR_RX_REQUEST) {
+		hisr &= ~REG_SDIO_HISR_RX_REQUEST;
+		rtw_sdio_rx_isr(rtwdev);
+	}
+
+	/*
+	 * RTL8723BS keeps raising the interrupt after resume if undefined
+	 * status bits are written back, so acknowledge only the bits this
+	 * driver defines. Other chips keep the existing behaviour.
+	 */
+	if (rtw_is_8723bs(rtwdev))
+		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);
+
+	/*
+	 * Acknowledge all pending status bits via W1C writeback. Unlike 8051,
+	 * 3081 requires REG_SDIO_HISR_RX_REQUEST to be explicitly cleared by
+	 * software.
+	 */
+	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 or if
+	 * unread data remains in the RX FIFO (e.g. if the 64KB budget was
+	 * reached or an RX error occurred). On 3081 hardware, clearing
+	 * REG_SDIO_HISR_RX_REQUEST requires both the W1C writeback above and
+	 * an empty RX FIFO, so the bit remains asserted as long as unread
+	 * data is in the buffer and re-triggers the interrupt when unmasked.
+	 */
+	scoped_guard(mutex, &rtwsdio->irq_lock) {
+		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);
@@ -1499,24 +1551,13 @@ static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func)
 	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) {
-		hisr &= ~REG_SDIO_HISR_RX_REQUEST;
-		rtw_sdio_rx_isr(rtwdev);
+	if (hisr) {
+		if (rtw_chip_wcpu_8051(rtwdev))
+			rtw_sdio_handle_interrupt_8051(rtwdev, hisr);
+		else
+			rtw_sdio_handle_interrupt_3081(rtwdev, hisr);
 	}
 
-	/*
-	 * RTL8723BS keeps raising the interrupt after resume if undefined
-	 * status bits are written back, so acknowledge only the bits this
-	 * driver defines. Other chips keep the existing behaviour.
-	 */
-	if (rtw_is_8723bs(rtwdev))
-		hisr &= RTW_SDIO_HISR_CLEAR_MASK;
-
-	rtw_write32(rtwdev, REG_SDIO_HISR, hisr);
-
 	rtwsdio->irq_thread = NULL;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH rtw-next v7 4/4] wifi: rtw88: sdio: Split rtw_sdio_rx_isr into 8051 and 3081 variants
  2026-10-31  3:35 [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
                   ` (2 preceding siblings ...)
  2026-10-31  3:35 ` [PATCH rtw-next v7 3/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
@ 2026-10-31  3:35 ` Alastair D'Silva
  3 siblings, 0 replies; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  3:35 UTC (permalink / raw)
  To: linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Luka Gejak, Martin Blumenstingl, Jernej Skrabec, Ulf Hansson,
	linux-kernel, Alastair D'Silva

Split rtw_sdio_rx_isr() into separate 8051 and 3081 variants to be
clear. The two differences between the architectures are:
- Register size: REG_SDIO_RX0_REQ_LEN is 16-bit on 8051, 32-bit on 3081.
- HISR behavior: 8051 re-reads REG_SDIO_HISR on each iteration, while
  3081 drains based on rx_len without re-reading HISR.

This is a refactoring without any logic changes.

Assisted-by: LLM
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Reviewed-by: Luka Gejak <luka.gejak@linux.dev>
Signed-off-by: Alastair D'Silva <alastair@d-silva.org>
---
 drivers/net/wireless/realtek/rtw88/sdio.c | 58 +++++++++++++----------
 1 file changed, 33 insertions(+), 25 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
index 75a5f5a96169..352ebb380a3e 100644
--- a/drivers/net/wireless/realtek/rtw88/sdio.c
+++ b/drivers/net/wireless/realtek/rtw88/sdio.c
@@ -1441,17 +1441,13 @@ static int rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len)
 	return 0;
 }
 
-static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev)
+static void rtw_sdio_rx_isr_8051(struct rtw_dev *rtwdev)
 {
 	u32 rx_len, hisr, total_rx_bytes = 0;
 	int ret;
 
 	do {
-		if (rtw_chip_wcpu_8051(rtwdev))
-			rx_len = rtw_read16(rtwdev, REG_SDIO_RX0_REQ_LEN);
-		else
-			rx_len = rtw_read32(rtwdev, REG_SDIO_RX0_REQ_LEN);
-
+		rx_len = rtw_read16(rtwdev, REG_SDIO_RX0_REQ_LEN);
 		if (!rx_len)
 			break;
 
@@ -1461,33 +1457,45 @@ static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev)
 
 		total_rx_bytes += rx_len;
 
-		if (rtw_chip_wcpu_8051(rtwdev)) {
-			/* Stop if no more RX requests are pending, even if
-			 * rx_len could be greater than zero in the next
-			 * iteration. This is needed because the RX buffer may
-			 * already contain data while either HW or FW are not
-			 * done filling that buffer yet. Still reading the
-			 * buffer can result in packets where
-			 * rtw_rx_pkt_stat.pkt_len is zero or points beyond the
-			 * end of the buffer.
-			 */
-			hisr = rtw_read32(rtwdev, REG_SDIO_HISR);
-		} else {
-			/* RTW_WCPU_3081 chips have improved hardware or
-			 * firmware and can use rx_len unconditionally.
-			 */
-			hisr = REG_SDIO_HISR_RX_REQUEST;
-		}
+		/*
+		 * Stop if no more RX requests are pending, even if
+		 * rx_len could be greater than zero in the next
+		 * iteration. This is needed because the RX buffer may
+		 * already contain data while either HW or FW are not
+		 * done filling that buffer yet. Still reading the
+		 * buffer can result in packets where
+		 * rtw_rx_pkt_stat.pkt_len is zero or points beyond the
+		 * end of the buffer.
+		 */
+		hisr = rtw_read32(rtwdev, REG_SDIO_HISR);
 	} while (total_rx_bytes < SZ_64K && hisr & REG_SDIO_HISR_RX_REQUEST);
 }
 
+static void rtw_sdio_rx_isr_3081(struct rtw_dev *rtwdev)
+{
+	u32 rx_len, total_rx_bytes = 0;
+	int ret;
+
+	do {
+		rx_len = rtw_read32(rtwdev, REG_SDIO_RX0_REQ_LEN);
+		if (!rx_len)
+			break;
+
+		ret = rtw_sdio_rxfifo_recv(rtwdev, rx_len);
+		if (ret)
+			break;
+
+		total_rx_bytes += rx_len;
+	} while (total_rx_bytes < SZ_64K);
+}
+
 static void rtw_sdio_handle_interrupt_8051(struct rtw_dev *rtwdev, u32 hisr)
 {
 	if (hisr & REG_SDIO_HISR_TXERR)
 		rtw_sdio_tx_err_isr(rtwdev);
 	if (hisr & REG_SDIO_HISR_RX_REQUEST) {
 		hisr &= ~REG_SDIO_HISR_RX_REQUEST;
-		rtw_sdio_rx_isr(rtwdev);
+		rtw_sdio_rx_isr_8051(rtwdev);
 	}
 
 	/*
@@ -1517,7 +1525,7 @@ static void rtw_sdio_handle_interrupt_3081(struct rtw_dev *rtwdev, u32 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);
+		rtw_sdio_rx_isr_3081(rtwdev);
 
 	/*
 	 * Unmasking HIMR re-asserts the IRQ line if new packets arrived or if
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop
  2026-10-31  3:35 ` [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop Alastair D'Silva
@ 2026-10-31  7:10   ` Luka Gejak
  2026-10-31  8:41     ` Alastair D'Silva
  0 siblings, 1 reply; 7+ messages in thread
From: Luka Gejak @ 2026-10-31  7:10 UTC (permalink / raw)
  To: Alastair D'Silva, linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Martin Blumenstingl, Jernej Skrabec, Ulf Hansson, linux-kernel,
	Alastair D'Silva, stable

Hi Alastair,

Thanks for the new version. One comment below.

October 31, 2026 at 4:35 AM, "Alastair D'Silva" <alastair@d-silva.org> wrote:

> diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c
> index e39284b71837..253467250ee6 100644
> --- a/drivers/net/wireless/realtek/rtw88/sdio.c
> +++ b/drivers/net/wireless/realtek/rtw88/sdio.c

[ ... ]

> @@ -1069,20 +1072,38 @@ static int rtw_sdio_start(struct rtw_dev *rtwdev)

[ ... ]

> +     sdio_release_host(rtwsdio->sdio_func);
> +
> +     queue_delayed_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work, 0);
>
>       return 0;
>  }

I brought up this rearm on v6, but looking at the callers again I think it
may run a bit too early. rtw_sdio_start() is called from rtw_power_on(),
so the worker can start on whatever is parked before rtw_core_start() is
done with the device:

        rtw_core_start()
          rtw_power_on()
            rtw_hci_start()             <- worker queued here
            rtw_fw_send_general_info()
            rtw_coex_init_hw_config()
          rtw_sec_enable_sec_engine()
          set_bit(RTW_FLAG_RUNNING, rtwdev->flags)

The parked frames got their TX descriptor in rtw_sdio_tx_write() before
the stop, with the macid and sec type of that session. So if I read it
right, after an interface down/up or an idle IPS cycle they can go out for
a station that is gone, and maybe even before rtw_sec_enable_sec_engine()
has run. Resume from WoWLAN looks similar, rtw_wow_stop() starts the
HCI while the WoWLAN firmware is still loaded and rtw_wow_swap_fw()
follows right after.

PCI drops what is left in its rings in rtw_pci_stop(), through
rtw_pci_dma_release(). Maybe doing the same here would be safer, purging
rtwsdio->tx_queue[] with ieee80211_purge_tx_queue() and clearing
tx_queue_stopped[] with a wake of those queues, instead of the rearm.
What do you think?

The stop/start cycling in the cover letter was also run on 7.2.10, where
tx_handler_data->work is still a work_struct and the 8723BS HISR mask is
not there yet, so I guess the board ran a backport. Was this version run
on the board as posted?

Best regards,
Luka Gejak

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop
  2026-10-31  7:10   ` Luka Gejak
@ 2026-10-31  8:41     ` Alastair D'Silva
  0 siblings, 0 replies; 7+ messages in thread
From: Alastair D'Silva @ 2026-10-31  8:41 UTC (permalink / raw)
  To: Luka Gejak, linux-wireless, Ping-Ke Shih, Kalle Valo
  Cc: Martin Blumenstingl, Jernej Skrabec, Ulf Hansson, linux-kernel,
	stable


On Sat, 2026-10-31 at 07:10 +0000, Luka Gejak wrote:
> Hi Alastair,
> 
> Thanks for the new version. One comment below.
> 
> October 31, 2026 at 4:35 AM, "Alastair D'Silva" <alastair@d-silva.org> wrote:
> 
> > diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c
> > b/drivers/net/wireless/realtek/rtw88/sdio.c
> > index e39284b71837..253467250ee6 100644
> > --- a/drivers/net/wireless/realtek/rtw88/sdio.c
> > +++ b/drivers/net/wireless/realtek/rtw88/sdio.c
> 
> [ ... ]
> 
> > @@ -1069,20 +1072,38 @@ static int rtw_sdio_start(struct rtw_dev *rtwdev)
> 
> [ ... ]
> 
> > +     sdio_release_host(rtwsdio->sdio_func);
> > +
> > +     queue_delayed_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work, 0);
> > 
> >       return 0;
> >  }
> 
> I brought up this rearm on v6, but looking at the callers again I think it
> may run a bit too early. rtw_sdio_start() is called from rtw_power_on(),
> so the worker can start on whatever is parked before rtw_core_start() is
> done with the device:
> 
>         rtw_core_start()
>           rtw_power_on()
>             rtw_hci_start()             <- worker queued here
>             rtw_fw_send_general_info()
>             rtw_coex_init_hw_config()
>           rtw_sec_enable_sec_engine()
>           set_bit(RTW_FLAG_RUNNING, rtwdev->flags)
> 
> The parked frames got their TX descriptor in rtw_sdio_tx_write() before
> the stop, with the macid and sec type of that session. So if I read it
> right, after an interface down/up or an idle IPS cycle they can go out for
> a station that is gone, and maybe even before rtw_sec_enable_sec_engine()
> has run. Resume from WoWLAN looks similar, rtw_wow_stop() starts the
> HCI while the WoWLAN firmware is still loaded and rtw_wow_swap_fw()
> follows right after.
> 
> PCI drops what is left in its rings in rtw_pci_stop(), through
> rtw_pci_dma_release(). Maybe doing the same here would be safer, purging
> rtwsdio->tx_queue[] with ieee80211_purge_tx_queue() and clearing
> tx_queue_stopped[] with a wake of those queues, instead of the rearm.
> What do you think?

Agreed, your trace of the callers makes it clear that re-arming in
rtw_sdio_start() is too early and risks transmitting frames with stale
TX descriptors from the previous session (or while WoWLAN firmware is
still loaded).

Purging rtwsdio->tx_queue[] and waking any stopped AC queues in
rtw_sdio_stop() after cancel_delayed_work_sync() is much cleaner and
matches rtw_pci_stop(). Since rtwsdio->tx_queue_stopped[] is indexed by
enum rtw_tx_queue_type while ieee80211_wake_queue() takes the mac80211
AC index, I propose doing the following in rtw_sdio_stop() (and
dropping the re-arm from rtw_sdio_start()):

	cancel_delayed_work_sync(&rtwsdio->tx_handler_data->work);
	for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++)
		ieee80211_purge_tx_queue(rtwdev->hw, &rtwsdio->tx_queue[i]);
	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
		queue = rtw_tx_ac_to_hwq(i);
		if (rtwsdio->tx_queue_stopped[queue]) {
			rtwsdio->tx_queue_stopped[queue] = false;
			ieee80211_wake_queue(rtwdev->hw, i);
		}
	}

> 
> The stop/start cycling in the cover letter was also run on 7.2.10, where
> tx_handler_data->work is still a work_struct and the 8723BS HISR mask is
> not there yet, so I guess the board ran a backport. Was this version run
> on the board as posted?
> 

The test board's base kernel is an Armbian sunxi64 build (versioned
7.2.10-edge-sunxi64 by Armbian). For the test run, rtw88_sdio.ko was
built directly from the rtw-next sdio.c/sdio.h (including delayed_work
and RTW_SDIO_HISR_CLEAR_MASK) with our patches applied, and only
rtw_is_8723bs() stubbed to false so rtw88_sdio.ko matched the struct
rtw_dev layout of the board's existing rtw88_core.ko. For v8 I will
build and load the full rtw-next drivers/net/wireless/realtek/rtw88/
module set on the board so it runs completely unmodified as posted.

-- 
Alastair D'Silva
0493 18 5566

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-31  8:41 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-31  3:35 [PATCH rtw-next v7 0/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
2026-10-31  3:35 ` [PATCH rtw-next v7 1/4] wifi: rtw88: sdio: Handle allocation and read errors in rtw_sdio_rxfifo_recv Alastair D'Silva
2026-10-31  3:35 ` [PATCH rtw-next v7 2/4] wifi: rtw88: sdio: Track running state and cancel TX worker on stop Alastair D'Silva
2026-10-31  7:10   ` Luka Gejak
2026-10-31  8:41     ` Alastair D'Silva
2026-10-31  3:35 ` [PATCH rtw-next v7 3/4] wifi: rtw88: sdio: Fix unhandled RX request interrupt storm Alastair D'Silva
2026-10-31  3:35 ` [PATCH rtw-next v7 4/4] wifi: rtw88: sdio: Split rtw_sdio_rx_isr into 8051 and 3081 variants Alastair D'Silva

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox