From: Lorenzo Bianconi <lorenzo@kernel.org>
To: Florian Maurer <fmaurer@disroot.org>
Cc: Felix Fietkau <nbd@nbd.name>, Ryder Lee <ryder.lee@mediatek.com>,
Shayne Chen <shayne.chen@mediatek.com>,
Sean Wang <sean.wang@mediatek.com>,
Matthias Brugger <matthias.bgg@gmail.com>,
AngeloGioacchino Del Regno
<angelogioacchino.delregno@collabora.com>,
linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-mediatek@lists.infradead.org, stable@vger.kernel.org
Subject: Re: [PATCH wireless 1/2] wifi: mt76: npu: return rx slots to the NPU only after refilling them
Date: Fri, 6 Nov 2026 15:21:52 +0100 [thread overview]
Message-ID: <au3igPTxdz3i64SO@lore-desk> (raw)
In-Reply-To: <d6314f34e0965464207656d38a145490b6437aa5.1793927986.git.fmaurer@disroot.org>
[-- Attachment #1: Type: text/plain, Size: 7775 bytes --]
> 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@vger.kernel.org
> Signed-off-by: Florian Maurer <fmaurer@disroot.org>
> ---
> 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);
why are you writing q->head here?
> +
> 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;
can this really occur?
> +
> + /* 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;
> + }
I got your time, and I agree this is an issue we need to fix but I guess we
should avoid a double loop. What about the patch below? (just compiled and not
tested).
Regards,
Lorenzo
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 2ade848fe3bb..9537c6d28e8d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -249,6 +249,7 @@ struct mt76_queue {
int ndesc;
int queued;
int buf_size;
+ int pending;
bool stopped;
bool blocked;
diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c
index c4c7c0af6321..e2fc361a5248 100644
--- a/drivers/net/wireless/mediatek/mt76/npu.c
+++ b/drivers/net/wireless/mediatek/mt76/npu.c
@@ -56,61 +56,74 @@ void mt76_npu_queue_cleanup(struct mt76_dev *dev, struct mt76_queue *q)
q->tail = (q->tail + 1) % q->ndesc;
q->queued--;
}
+ if (q->rx_head) {
+ dev_kfree_skb(q->rx_head);
+ q->rx_head = NULL;
+ q->pending = 0;
+ }
spin_unlock_bh(&q->lock);
}
-static struct sk_buff *mt76_npu_dequeue(struct mt76_dev *dev,
- struct mt76_queue *q,
- u32 *info)
+static int mt76_npu_rx_process_entry(struct napi_struct *napi)
{
+ struct mt76_dev *dev = mt76_priv(napi->dev);
+ enum mt76_rxq_id qid = napi - dev->napi;
+ struct mt76_queue *q = &dev->q_rx[qid];
struct airoha_npu_rx_dma_desc *desc = (void *)q->desc;
- int i, nframes, index = q->tail;
- struct sk_buff *skb = NULL;
+ int nframes = q->pending;
+ u32 info = 0;
- nframes = FIELD_GET(NPU_RX_DMA_PKT_COUNT_MASK, desc[index].info);
- nframes = max_t(int, nframes, 1);
+ if (!nframes) {
+ nframes = FIELD_GET(NPU_RX_DMA_PKT_COUNT_MASK,
+ desc[q->tail].info);
+ nframes = max_t(int, nframes, 1);
+ }
- for (i = 0; i < nframes; i++) {
- struct mt76_queue_entry *e = &q->entry[index];
+ while (nframes > 0) {
+ struct mt76_queue_entry *e = &q->entry[q->tail];
int len = FIELD_GET(NPU_RX_DMA_DESC_CUR_LEN_MASK,
- desc[index].ctrl);
+ desc[q->tail].ctrl);
- if (!FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[index].ctrl)) {
- dev_kfree_skb(skb);
- return NULL;
- }
+ if (!FIELD_GET(NPU_RX_DMA_DESC_DONE_MASK, desc[q->tail].ctrl))
+ return -EBUSY;
dma_sync_single_for_cpu(dev->dma_dev, e->dma_addr[0],
e->dma_len[0],
page_pool_get_dma_dir(q->page_pool));
- if (!skb) {
- skb = napi_build_skb(e->buf, q->buf_size);
- if (!skb)
- return NULL;
+ if (!q->rx_head) {
+ q->rx_head = napi_build_skb(e->buf, q->buf_size);
+ if (!q->rx_head)
+ return -ENOMEM;
- __skb_put(skb, len);
- skb_reset_mac_header(skb);
- skb_mark_for_recycle(skb);
+ __skb_put(q->rx_head, len);
+ skb_reset_mac_header(q->rx_head);
+ skb_mark_for_recycle(q->rx_head);
} else {
- struct skb_shared_info *shinfo = skb_shinfo(skb);
+ struct skb_shared_info *shinfo = skb_shinfo(q->rx_head);
struct page *page = virt_to_head_page(e->buf);
int nr_frags = shinfo->nr_frags;
if (nr_frags < ARRAY_SIZE(shinfo->frags))
- skb_add_rx_frag(skb, nr_frags, page,
+ skb_add_rx_frag(q->rx_head, nr_frags, page,
e->buf - page_address(page),
len, q->buf_size);
}
- *info = desc[index].info;
- index = (index + 1) % q->ndesc;
+ info = desc[q->tail].info;
+ q->tail = (q->tail + 1) % q->ndesc;
+ q->queued--;
+ nframes--;
}
- q->tail = index;
- q->queued -= i;
+
+ dev->drv->rx_skb(dev, qid, q->rx_head, &info);
+ mt76_rx_poll_complete(dev, qid, napi);
+ q->rx_head = NULL;
+ q->pending = 0;
+
Q_WRITE(q, dma_idx, q->tail);
- return skb;
+ return 0;
}
void mt76_npu_check_ppe(struct mt76_dev *dev, struct sk_buff *skb,
@@ -155,18 +168,10 @@ static int mt76_npu_rx_poll(struct napi_struct *napi, int budget)
goto out;
while (done < budget) {
- struct sk_buff *skb;
- u32 info = 0;
-
- skb = mt76_npu_dequeue(dev, &dev->q_rx[qid], &info);
- if (!skb)
+ if (mt76_npu_rx_process_entry(napi))
break;
-
- dev->drv->rx_skb(dev, qid, skb, &info);
- mt76_rx_poll_complete(dev, qid, napi);
done++;
}
-
mt76_npu_fill_rx_queue(dev, &dev->q_rx[qid]);
out:
if (done < budget && napi_complete(napi))
>
> 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
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
next prev parent reply other threads:[~2026-11-06 14:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 11:51 [PATCH wireless 0/2] wifi: mt76: npu: fix rx ring handling Florian Maurer
2026-11-06 11:51 ` [PATCH wireless 1/2] wifi: mt76: npu: return rx slots to the NPU only after refilling them Florian Maurer
2026-11-06 14:21 ` Lorenzo Bianconi [this message]
2026-11-08 20:53 ` Florian Maurer
2026-11-09 11:00 ` Lorenzo Bianconi
2026-11-06 11:51 ` [PATCH wireless 2/2] wifi: mt76: npu: read the rx slot count after the first slot is complete Florian Maurer
2026-11-06 14:26 ` Lorenzo Bianconi
2026-11-08 21:21 ` Florian Maurer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=au3igPTxdz3i64SO@lore-desk \
--to=lorenzo@kernel.org \
--cc=angelogioacchino.delregno@collabora.com \
--cc=fmaurer@disroot.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=linux-wireless@vger.kernel.org \
--cc=matthias.bgg@gmail.com \
--cc=nbd@nbd.name \
--cc=ryder.lee@mediatek.com \
--cc=sean.wang@mediatek.com \
--cc=shayne.chen@mediatek.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.