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: 5+ 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-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox