From: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
To: netdev-bot+sashiko@kernel.org
Cc: maxime.chevallier@bootlin.com, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, mcoquelin.stm32@gmail.com,
alexandre.torgue@foss.st.com, Jose.Abreu@synopsys.com,
ast@kernel.org, daniel@iogearbox.net, hawk@kernel.org,
john.fastabend@gmail.com, sdf@fomichev.me,
netdev@vger.kernel.org, linux-stm32@st-md-mailman.stormreply.com,
linux-arm-kernel@lists.infradead.org, bpf@vger.kernel.org
Subject: Re: [PATCH net v3] net: stmmac: fix rx Scatter-Gather support
Date: Sun, 27 Sep 2026 17:29:13 +0200 [thread overview]
Message-ID: <ark2SZ3qy76V1Djg@lore-qca> (raw)
In-Reply-To: <179050137856.2160803.6310706653062051206@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 14775 bytes --]
> Thank you for your contribution! Sashiko AI review found 5 potential
> issue(s) to consider.
>
> Critical: 0 · High: 3 (2) · Medium: 1 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] The new XDP FCS strip in stmmac_rx() (`if
> (stmmac_xdp_is_enabled(priv) && !buf2_len) { buf1_len -= ETH_FCS_LEN;…
> - [High] The page_pool sync length no longer matches its offset base on
> the XDP_CONSUMED path.
> - [High] Out-of-bounds buffer2 fragment after an XDP_TX/REDIRECT verdict
> in the middle of a frame.
> - [Medium] stmmac_rx() removes the FCS with the wrong length when XDP has
> changed the frame.
> - [Low] Some new or kept comments do not match the code.
>
> Pre-existing issues:
> - [High] This problem wasn't introduced by this patch, but the same
> unbacked buffer2 DMA destination is still reachable on AF_XDP zero-…
> - [High] This problem wasn't introduced by this patch, but in
> stmmac_rx(), when napi_build_skb() fails, buf->page is returned to the…
>
> --
>
[...]
> [Severity: High]
> Does the XDP_CONSUMED recycle path still sync the right range after this
> change?
>
> With XDP loaded, stmmac_xdp_set_prog() forces SPH off:
>
> priv->sph_active = priv->sph_capable && !stmmac_xdp_is_enabled(priv);
>
> So before this patch, pp_params.offset in XDP mode was always equal to
> buf->page_offset.
>
> stmmac_rx() still computes sync_len relative to buf->page_offset:
>
> sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start -
> buf->page_offset;
I think the reported issue is real it is already present before this patch.
In particulat it is not correct to subtract buf->page_offset from the sync_len
since the program can push/pop headers in the headroom. I guess we should fix
this issue with a dedicated patch defining sync_len as below:
sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start;
> ...
> page_pool_put_page(rx_q->page_pool,
> virt_to_head_page(ctx.xdp.data),
> sync_len, true);
>
> __page_pool_dma_sync_for_device() then syncs starting at the pool offset:
>
> dma_sync_size = min(dma_sync_size, pool->p.max_len);
> __dma_sync_single_for_device(pool->p.dev, dma_addr + pool->p.offset,
> dma_sync_size, pool->p.dma_dir);
>
> With offset now 0, wouldn't this sync [0, sync_len) instead of
> [page_offset, page_offset + sync_len)? The last page_offset bytes the CPU
> touched would not be cleaned for the device. That is up to 258 bytes, and it
> includes the Ethernet header that an XDP_TX program may have rewritten
> before the TX ring filled up.
>
> On non-coherent platforms, could a dirty line left there be written back
> over the next frame DMA'd into the recycled page? One case is
> dma_sync_single_for_cpu() on the unaligned buf->addr, which cleans and
> invalidates the first line.
>
> This can happen for XDP_DROP or XDP_ABORTED after the program wrote the
> packet, for a failed XDP_REDIRECT, or for XDP_TX with a full TX ring.
>
> [ ... ]
>
> > @@ -5099,7 +5101,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
> > break;
> > }
> >
> > - if (priv->sph_active && !buf->sec_page) {
> > + if (stmmac_rx_check_buf2_cap(priv) && !buf->sec_page) {
> > buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp);
> > if (!buf->sec_page)
> > break;
>
> [Severity: High]
> This isn't a bug introduced by this patch, but it concerns how refill
> reuses buf->page. In stmmac_rx(), when napi_build_skb() fails, the page is
> recycled but buf->page still points at it:
>
> skb = napi_build_skb(page_address(buf->page),
> rx_q->napi_skb_frag_size);
> if (!skb) {
> page_pool_recycle_direct(rx_q->page_pool,
> buf->page);
> rx_dropped++;
> count++;
> goto drain_data;
> }
>
> stmmac_rx_refill() only allocates when !buf->page, so it re-arms the
> descriptor with the recycled page. The same page also sits in the pool's
> direct cache, and the next page_pool_alloc_pages() can hand it out again.
>
> Refill now also allocates sec_page for every consumed secondary buffer on
> GMAC4/XGMAC, which makes that reuse more likely. Can two descriptors end up
> DMAing into the same page, with both owners later recycling it?
>
> Should buf->page be set to NULL after page_pool_recycle_direct()?
This patch is already fixed in the following commit:
commit 0a7822e34a0bfde31b194ac3da3253e5032b44cc
Author: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
Date: Mon Sep 21 16:46:18 2026 +0200
net: stmmac: clear stale buf->page after recycling on skb build failure
In stmmac_rx(), when napi_build_skb() fails the descriptor page is
recycled back to the page pool with page_pool_recycle_direct(), but
buf->page is left pointing at the recycled page, unlike every other
consumption site in the function which clears the pointer after handing
the page away.
With the stale pointer stmmac_rx_refill() skips the replacement
allocation and programs the already-recycled page back into the RX
descriptor.
Clear buf->page on the napi_build_skb() failure path to keep the buffer
lifecycle consistent with the other consumption sites.
Fixes: df542f669307 ("net: stmmac: Switch to zero-copy in non-XDP RX path")
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Link: https://patch.msgid.link/20260921-stmmac-fix-napi-build-skb-error-v1-1-3d54bf6d9bb6@oss.qualcomm.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
>
> > @@ -5110,10 +5112,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue)
> > buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset;
> >
> > stmmac_set_desc_addr(priv, p, buf->addr);
> > - if (priv->sph_active)
> > - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true);
> > - else
> > - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false);
> > + stmmac_set_desc_sec_addr(priv, p, buf->sec_addr,
> > + stmmac_rx_check_buf2_cap(priv));
>
> [Severity: High]
> This is a pre-existing issue, and the commit message already says AF_XDP
> zero-copy is not covered. Still, the same unbacked buffer2 can be reached
> on XGMAC ZC queues. stmmac_rx_refill_zc() does:
>
> stmmac_set_desc_sec_addr(priv, rx_desc, 0, false);
>
> and dwxgmac2_set_sec_addr() ignores is_valid:
>
> static void dwxgmac2_set_sec_addr(struct dma_desc *p, dma_addr_t addr, bool is_valid)
> {
> p->des2 = cpu_to_le32(lower_32_bits(addr));
> p->des3 = cpu_to_le32(upper_32_bits(addr));
> }
>
> stmmac_set_queue_rx_buf_size() gives a ZC queue a buffer size of
> xsk_pool_get_rx_frame_size(), which is about 3840 bytes with 4 KiB chunks.
> XGMAC_CORE_INIT_RX accepts frames up to 16368 bytes.
>
> On a ZC queue, would a frame that overflows buffer1 be written to DMA
> address 0? That would give the same SMMU fault this patch fixes, or a write
> to physical address 0 on systems without an IOMMU.
As pointed out by sashiko, this is a pre-existing issue and it needs to be
fixed with a dedicated patch.
>
> [ ... ]
>
> > @@ -5190,14 +5193,15 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv,
> > * Thus 'plen - len' always gives the correct length of buf2.
> > */
> >
> > - /* Not GMAC4 and not last descriptor */
> > - if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls))
> > + /* Not GMAC4, or non-SPH and not last descriptor */
> > + if ((priv->plat->core_type != DWMAC_CORE_GMAC4 || !priv->sph_active) &&
> > + (status & rx_not_ls))
> > return priv->dma_conf.dma_buf_sz;
> >
> > /* GMAC4 or last descriptor */
> > plen = stmmac_get_rx_frame_len(priv, p, coe);
> >
> > - return plen - len;
> > + return plen > len ? plen - len : 0;
> > }
>
> [Severity: High]
> buffer2 is now enabled in non-SPH mode, which is always the case with XDP
> loaded. Can this return more than dma_buf_sz?
>
> When the XDP program returns TX or REDIRECT on a non-last descriptor,
> stmmac_rx() does:
>
> } else if (xdp_res & (STMMAC_XDP_TX |
> STMMAC_XDP_REDIRECT)) {
> xdp_status |= xdp_res;
> buf->page = NULL;
> skb = NULL;
> count++;
> continue;
> }
>
> The top of the loop then resets len to 0 even though the frame isn't
> finished. The next descriptor is handled as a new frame, and on the last
> descriptor plen - len is no longer limited to one descriptor.
>
> For example, take GMAC4 with dma_buf_sz 1536 and a 6000 byte frame. desc0
> is transmitted, and desc1 (the last one) gets buf1_len 1536 and buf2_len
> 4464.
>
> If XDP then returns PASS for that chunk, stmmac_rx() does:
>
> dma_sync_single_for_cpu(priv->device, buf->sec_addr,
> buf2_len, dma_dir);
> skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
> buf->sec_page, 0, buf2_len,
> priv->dma_conf.dma_buf_sz);
>
> sec_page is an order-0 page here. Wouldn't the fragment run past the end of
> the page, so that adjacent memory and stale pool contents reach the stack?
> GMAC4 accepts frames up to about 9018 bytes with JE set, and XGMAC up to
> 16368.
>
> Before this patch, buf2_len was always 0 without SPH.
This issue (as even the ones reported below) is due to the missing XDP
multi-buff support. The reported problem will be cleanly fixed adding
XDP multi-buff support (I have already posted the related patches on
the mailing list).
>
> [ ... ]
>
> > @@ -5809,24 +5814,25 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
> > len += buf2_len;
> >
> > /* ACS is disabled; strip manually. */
> > - if (likely(!(status & rx_not_ls))) {
> > - if (buf2_len) {
> > - buf2_len -= ETH_FCS_LEN;
> > - len -= ETH_FCS_LEN;
> > - } else if (buf1_len) {
> > - buf1_len -= ETH_FCS_LEN;
> > - len -= ETH_FCS_LEN;
> > - }
> > - }
> > + if (likely(!(status & rx_not_ls)))
> > + len -= ETH_FCS_LEN;
> >
> > if (!skb) {
> > unsigned int pre_len, sync_len;
> >
> > + /* Each frame starts here: reset the FCS handling */
> > + fcs_stripped = false;
> > +
> > dma_sync_single_for_cpu(priv->device, buf->addr,
> > buf1_len, dma_dir);
> > net_prefetch(page_address(buf->page) +
> > buf->page_offset);
> >
> > + if (stmmac_xdp_is_enabled(priv) && !buf2_len) {
> > + buf1_len -= ETH_FCS_LEN;
> > + fcs_stripped = true;
> > + }
>
> [Severity: High]
> Is buf1_len guaranteed to be at least ETH_FCS_LEN here, and is this always
> the last descriptor?
>
> On GMAC4/XGMAC, an XDP_CONSUMED verdict on a non-last descriptor does goto
> read_again with skb = NULL, and len keeps the bytes counted so far. The next
> descriptor then enters this block as if it were a new frame.
>
> Take a frame of n * 2 * dma_buf_sz + r bytes with r = 1..3, for example 3073
> to 3075 bytes with dma_buf_sz 1536. The last descriptor gets
> buf1_len = min(dma_buf_sz, plen - len) = r from stmmac_rx_buf1_len() and
> buf2_len = 0, so this subtraction wraps:
>
> buf1_len -= ETH_FCS_LEN;
>
> xdp_prepare_buff() then gets data_end < data, and on XDP_PASS this passes a
> value around 0xfffffffd to skb_put():
>
> buf1_len = ctx.xdp.data_end - ctx.xdp.data;
> ...
> skb_put(skb, buf1_len);
>
> Could this send an skb with len around 4 GiB to eth_type_trans() and the IP
> stack, where later trims can put the tail past the napi frag?
>
> On legacy cores without set_sec_addr, stmmac_rx_buf2_len() returns 0 for
> every descriptor, so !buf2_len doesn't mean last descriptor there either.
> Wouldn't the first descriptor of a multi-descriptor frame lose 4 bytes of
> payload, and the final pskb_trim() then be skipped so the real FCS stays at
> the tail?
>
> Before this patch the strip only happened on the last descriptor. buf1_len
> there was min(dma_buf_sz, plen), so it could not be 1..3.
This issue is due to the missing XDP multi-buff support. The reported problem
will be cleanly fixed adding XDP multi-buff support.
>
> > +
> > xdp_init_buff(&ctx.xdp, bufsz, &rx_q->xdp_rxq);
> > xdp_prepare_buff(&ctx.xdp, page_address(buf->page),
> > buf->page_offset, buf1_len, true);
> > @@ -5926,6 +5932,15 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
> >
> > /* Got entire packet into SKB. Finish it. */
> >
> > + /* Remove FCS if needed */
> > + if (!fcs_stripped && pskb_trim(skb, len)) {
>
> [Severity: Medium]
> Does len still match the skb here when an XDP program has changed the size
> of the frame?
>
> For a frame that uses buffer2 on the first descriptor, fcs_stripped stays
> false. On XDP_PASS the skb head is built from the adjusted XDP boundaries:
>
> buf1_len = ctx.xdp.data_end - ctx.xdp.data;
> ...
> skb_reserve(skb, head_pad_len);
> skb_put(skb, buf1_len);
>
> len, however, comes only from the descriptor lengths minus ETH_FCS_LEN. It
> never includes the bpf_xdp_adjust_head() or bpf_xdp_adjust_tail() delta.
>
> pskb_trim() takes an absolute length and does nothing when len >= skb->len:
>
> return (len < skb->len) ? __pskb_trim(skb, len) : 0;
>
> So wouldn't a program that pushes N bytes lose N bytes of payload from the
> tail? And wouldn't one that pulls or shrinks by 4 or more bytes leave the
> FCS in place?
>
> Would pskb_trim(skb, skb->len - ETH_FCS_LEN) be more robust here?
This issue is due to the missing XDP multi-buff support. The reported problem
will be cleanly fixed adding XDP multi-buff support.
Regards,
Lorenzo
>
> > + dev_kfree_skb_any(skb);
> > + skb = NULL;
> > + rx_dropped++;
> > + count++;
> > + continue;
> > + }
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260923-stmmac-rx-sg-fix-v3-1-ed26fea7180d%40oss.qualcomm.com
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
next prev parent reply other threads:[~2026-09-27 15:29 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 9:14 [PATCH net v3] net: stmmac: fix rx Scatter-Gather support Lorenzo Bianconi
2026-09-24 9:15 ` sashiko-bot
2026-09-24 13:09 ` Lorenzo Bianconi
2026-09-27 9:29 ` netdev-bot+sashiko
2026-09-27 15:29 ` Lorenzo Bianconi [this message]
2026-09-27 17:48 ` Maxime Chevallier
2026-09-29 1:40 ` patchwork-bot+netdevbpf
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=ark2SZ3qy76V1Djg@lore-qca \
--to=lorenzo.bianconi@oss.qualcomm.com \
--cc=Jose.Abreu@synopsys.com \
--cc=alexandre.torgue@foss.st.com \
--cc=andrew+netdev@lunn.ch \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=hawk@kernel.org \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-stm32@st-md-mailman.stormreply.com \
--cc=maxime.chevallier@bootlin.com \
--cc=mcoquelin.stm32@gmail.com \
--cc=netdev-bot+sashiko@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@fomichev.me \
/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.