[PATCH wireless 1/2] wifi: mt76: npu: return rx slots to the NPU only after refilling them
From: Florian Maurer
Date: Fri Nov 06 2026 - 06:59:32 EST
mt76_npu_dequeue() writes the new tail to the dma_idx register of the
NPU rx ring as soon as it has consumed a frame, while the slots are
only refilled with new buffers by mt76_npu_rx_poll() after the whole
NAPI budget. Until then, the descriptors of the released slots still
point to the buffers that were passed up the stack, and on the first
round of the ring the slot that was never filled points to address 0.
If the NPU completes one of these slots, the host processes an entry
without buffer, which crashes in dma_sync_single_for_cpu() under heavy
rx load:
Unable to handle kernel paging request at virtual address ffffffff80000700
pc : dcache_inval_poc+0x28/0x58
lr : arch_sync_dma_for_cpu+0x28/0x38
Call trace:
dcache_inval_poc+0x28/0x58 (P)
__dma_sync_single_for_cpu+0xe4/0xf8
mt76_npu_rx_poll+0x184/0x390 [mt76]
__napi_poll+0x34/0x180
Write dma_idx after refilling the ring instead, up to the slot kept
free in front of the tail, which is the same value as before once the
ring is full.
Also check that all slots of a frame are queued and complete before
building the skb. So far a frame whose later slots were not yet
done was freed together with the buffers of its first slots, which
were then used again on the next poll.
Fixes: 7fb554b1b623 ("wifi: mt76: Introduce the NPU generic layer")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Florian Maurer <fmaurer@xxxxxxxxxxx>
---
drivers/net/wireless/mediatek/mt76/npu.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c
index c4c7c0af6321..6e503a4a890c 100644
--- a/drivers/net/wireless/mediatek/mt76/npu.c
+++ b/drivers/net/wireless/mediatek/mt76/npu.c
@@ -40,6 +40,11 @@ int mt76_npu_fill_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
nframes++;
}
+ /* Hand the slots back to the NPU only once they have a new buffer,
+ * up to the one kept free in front of the tail.
+ */
+ Q_WRITE(q, dma_idx, (q->head + 1) % q->ndesc);
+
return nframes;
}
@@ -69,17 +74,22 @@ static struct sk_buff *mt76_npu_dequeue(struct mt76_dev *dev,
nframes = FIELD_GET(NPU_RX_DMA_PKT_COUNT_MASK, desc[index].info);
nframes = max_t(int, nframes, 1);
+ if (nframes > q->queued)
+ return NULL;
+
+ /* Only take the frame once all of its slots are complete */
+ for (i = 0; i < nframes; i++) {
+ int idx = (index + i) % q->ndesc;
+
+ if (!FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[idx].ctrl))
+ return NULL;
+ }
for (i = 0; i < nframes; i++) {
struct mt76_queue_entry *e = &q->entry[index];
int len = FIELD_GET(NPU_RX_DMA_DESC_CUR_LEN_MASK,
desc[index].ctrl);
- if (!FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[index].ctrl)) {
- dev_kfree_skb(skb);
- return NULL;
- }
-
dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0],
e->dma_len[0],
page_pool_get_dma_dir(q->page_pool));
@@ -108,7 +118,6 @@ static struct sk_buff *mt76_npu_dequeue(struct mt76_dev *dev,
}
q->tail = index;
q->queued -= i;
- Q_WRITE(q, dma_idx, q->tail);
return skb;
}
--
2.47.3