[PATCH wireless 2/2] wifi: mt76: npu: read the rx slot count after the first slot is complete

From: Florian Maurer

Date: Fri Nov 06 2026 - 07:07:05 EST


mt76_npu_dequeue() reads the slot count of a frame from the info word
of its first descriptor before checking the done bit of that slot. If
the NPU completes the slot in between, the count is still the 0 left
by the refill, so a frame spanning two slots is taken as a single slot
frame and its second slot is parsed as the start of the next frame.
With mt7996 this shows up as bogus tx-free events, which hit the
WARN_ON_ONCE() checks in mt7996_mac_tx_free(). From the code, such an
event can also release tokens that are still in use.

Check the done bit of the first slot before reading the slot count,
and order the descriptor reads with dma_rmb().

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 | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c
index 6e503a4a890c..f00ae683a7eb 100644
--- a/drivers/net/wireless/mediatek/mt76/npu.c
+++ b/drivers/net/wireless/mediatek/mt76/npu.c
@@ -72,18 +72,25 @@ static struct sk_buff *mt76_npu_dequeue(struct mt76_dev *dev,
int i, nframes, index = q->tail;
struct sk_buff *skb = NULL;

+ if (!q->queued ||
+ !FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[index].ctrl))
+ return NULL;
+
+ /* The slot count is only valid once the first slot is complete */
+ dma_rmb();
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++) {
+ for (i = 1; i < nframes; i++) {
int idx = (index + i) % q->ndesc;

if (!FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[idx].ctrl))
return NULL;
}
+ dma_rmb();

for (i = 0; i < nframes; i++) {
struct mt76_queue_entry *e = &q->entry[index];
--
2.47.3