Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support
@ 2026-10-04 20:42 Lorenzo Bianconi
  2026-10-04 20:42 ` [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-04 20:42 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong
  Cc: netdev, linux-stm32, linux-arm-kernel, bpf, James Hilliard,
	Lorenzo Bianconi

Rework stmmac_rx routine to properly support XDP rx multi-buff.
Fix possible rx_q state leaks closing/opening the interface.

---
Changes in v3:
- Return secondary page in case of error.
- Introduce stmmac_xdp_shrink_tail() to remove the ethernet FCS.
- Add patch 'net: stmmac: take ownership of saved RX state at poll
  entry' from James.
- Link to v2: https://lore.kernel.org/r/20260921-stmmac-rx-mb-v2-1-6e826e1ff306@oss.qualcomm.com

Changes in v2:
- Rely on xdp_buff_add_frag() to create xdp fragments
- Fix bugs in rx_q state recording
- Fix the corner case where we receive more than MAX_SKB_FRAGS fragments
- Cosmetics
- Link to v1: https://lore.kernel.org/r/20260918-stmmac-rx-mb-v1-1-0b4517d404af@oss.qualcomm.com

---
James Hilliard (1):
      net: stmmac: take ownership of saved RX state at poll entry

Lorenzo Bianconi (1):
      net: stmmac: rework stmmac_rx to support XDP rx multi-buff

 drivers/net/ethernet/stmicro/stmmac/stmmac.h      |   3 +-
 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 388 ++++++++++++++--------
 2 files changed, 249 insertions(+), 142 deletions(-)
---
base-commit: 14b9ac2f189db8809a3c9f750d7700cfc9d21f77
change-id: 20260918-stmmac-rx-mb-16469a2714ea

Best regards,
-- 
Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry
  2026-10-04 20:42 [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
@ 2026-10-04 20:42 ` Lorenzo Bianconi
  2026-10-05 21:16   ` netdev-bot+sashiko
  2026-10-04 20:42 ` [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
  2026-10-04 20:49 ` [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
  2 siblings, 1 reply; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-04 20:42 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong
  Cc: netdev, linux-stm32, linux-arm-kernel, bpf, James Hilliard

From: James Hilliard <james.hilliard1@gmail.com>

When a saved partial packet completes with a poll budget of one, the
old loop can leave state_saved and state.skb pointing at an skb that
has already been delivered or freed. The next poll then reuses that
pointer, causing a use-after-free or double free.

Take the saved state at poll entry and clear the stored ownership
immediately. Save it again only if the packet remains incomplete,
including when the next descriptor is still DMA-owned. Release a
saved partial skb when the RX ring is destroyed.

Apply the same state handling to stmmac_rx_zc(), which also leaves
state_saved set when a saved frame finishes at the budget boundary.
That path saves only error and length bookkeeping, not an skb pointer.
Track whether a frame remains incomplete independently of the status
read from the next descriptor, so a DMA-owned descriptor does not erase
the continuation state.

Fixes: ec222003bd94 ("net: stmmac: Prepare to add Split Header support")
Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 54 +++++++++++++++--------
 1 file changed, 35 insertions(+), 19 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 0c381ae0d0ff..b2d20628ed21 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -2180,6 +2180,9 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
 	else
 		dma_free_rx_skbufs(priv, dma_conf, queue);
 
+	dev_kfree_skb_any(rx_q->state.skb);
+	rx_q->state.skb = NULL;
+	rx_q->state_saved = false;
 	rx_q->buf_alloc_num = 0;
 	rx_q->xsk_pool = NULL;
 
@@ -5581,12 +5584,12 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
 	unsigned int count = 0, error = 0, len = 0;
 	int dirty = stmmac_rx_dirty(priv, queue);
 	unsigned int next_entry = rx_q->cur_rx;
+	bool in_progress = rx_q->state_saved;
 	u32 rx_errors = 0, rx_dropped = 0;
 	unsigned int desc_size;
 	struct bpf_prog *prog;
 	bool failure = false;
 	int xdp_status = 0;
-	int status = 0;
 
 	if (netif_msg_rx_status(priv)) {
 		void *rx_head = stmmac_get_rx_desc(priv, rx_q, 0);
@@ -5597,23 +5600,25 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
 		stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
 				    rx_q->dma_rx_phy, desc_size);
 	}
+
+	if (rx_q->state_saved) {
+		error = rx_q->state.error;
+		len = rx_q->state.len;
+		rx_q->state_saved = false;
+	}
+
 	while (count < limit) {
 		struct stmmac_rx_buffer *buf;
 		struct stmmac_xdp_buff *ctx;
 		unsigned int buf1_len = 0;
 		struct dma_desc *np, *p;
-		int entry;
+		int entry, status;
 		int res;
 
-		if (!count && rx_q->state_saved) {
-			error = rx_q->state.error;
-			len = rx_q->state.len;
-		} else {
-			rx_q->state_saved = false;
+		if (!in_progress) {
 			error = 0;
 			len = 0;
 		}
-
 read_again:
 		if (count >= limit)
 			break;
@@ -5652,6 +5657,8 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
 		if (!buf->xdp)
 			break;
 
+		in_progress = status & rx_not_ls;
+
 		if (priv->extend_desc)
 			stmmac_rx_extended_status(priv, &priv->xstats,
 						  rx_q->dma_erx + entry);
@@ -5724,7 +5731,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
 		count++;
 	}
 
-	if (status & rx_not_ls) {
+	if (in_progress) {
 		rx_q->state_saved = true;
 		rx_q->state.error = error;
 		rx_q->state.len = len;
@@ -5766,9 +5773,10 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
 	struct stmmac_channel *ch = &priv->channel[queue];
 	unsigned int count = 0, error = 0, len = 0;
-	int status = 0, coe = priv->hw->rx_csum;
 	unsigned int next_entry = rx_q->cur_rx;
+	bool in_progress = rx_q->state_saved;
 	enum dma_data_direction dma_dir;
+	int coe = priv->hw->rx_csum;
 	unsigned int desc_size;
 	struct sk_buff *skb = NULL;
 	struct stmmac_xdp_buff ctx;
@@ -5788,25 +5796,28 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 		stmmac_display_ring(priv, rx_head, priv->dma_conf.dma_rx_size, true,
 				    rx_q->dma_rx_phy, desc_size);
 	}
+
+	if (rx_q->state_saved) {
+		skb = rx_q->state.skb;
+		error = rx_q->state.error;
+		len = rx_q->state.len;
+		rx_q->state.skb = NULL;
+		rx_q->state_saved = false;
+	}
+
 	while (count < limit) {
 		unsigned int buf1_len = 0, buf2_len = 0;
 		enum pkt_hash_types hash_type;
 		struct stmmac_rx_buffer *buf;
 		struct dma_desc *np, *p;
-		int entry;
+		int entry, status;
 		u32 hash;
 
-		if (!count && rx_q->state_saved) {
-			skb = rx_q->state.skb;
-			error = rx_q->state.error;
-			len = rx_q->state.len;
-		} else {
-			rx_q->state_saved = false;
+		if (!in_progress) {
 			skb = NULL;
 			error = 0;
 			len = 0;
 		}
-
 read_again:
 		if (count >= limit)
 			break;
@@ -5835,6 +5846,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 
 		prefetch(np);
 
+		in_progress = status & rx_not_ls;
+
 		if (priv->extend_desc)
 			stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry);
 		if (unlikely(status == discard_frame)) {
@@ -6019,7 +6032,7 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 		count++;
 	}
 
-	if (status & rx_not_ls || skb) {
+	if (in_progress || skb) {
 		rx_q->state_saved = true;
 		rx_q->state.skb = skb;
 		rx_q->state.error = error;
@@ -8339,6 +8352,9 @@ static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
 {
 	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
 
+	dev_kfree_skb_any(rx_q->state.skb);
+	rx_q->state.skb = NULL;
+	rx_q->state_saved = false;
 	rx_q->cur_rx = 0;
 	rx_q->dirty_rx = 0;
 }

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
  2026-10-04 20:42 [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
  2026-10-04 20:42 ` [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-04 20:42 ` Lorenzo Bianconi
  2026-10-05 21:16   ` netdev-bot+sashiko
  2026-10-04 20:49 ` [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
  2 siblings, 1 reply; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-04 20:42 UTC (permalink / raw)
  To: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong
  Cc: netdev, linux-stm32, linux-arm-kernel, bpf, Lorenzo Bianconi

Build the xdp_buff by accumulating all the descriptors that make up a
frame, so the XDP program runs on the full (possibly fragmented) packet
instead of just the first buffer. When the frame is not consumed by the
program, assemble the skb from the head buffer and the collected
fragments via napi_build_skb()/xdp_update_skb_frags_info().

To do so, store the in-progress xdp_buff in rx_q->state instead of the
partially built skb, so the accumulated head and fragments survive a
NAPI poll boundary (mid-frame dma_own or dirty_rx break). The state is
saved only while a frame is in progress and cleared once it completes,
leaving it untouched when the poll does not process anything (e.g.
netpoll invoked with a zero budget).

In addition:
- build the skb head with napi_build_skb() passing xdp->frame_sz, so
  skb_shinfo() lands on the same shared_info the fragments were
  accumulated into;
- attach fragments with xdp_buff_add_frag(), which initializes all the
  shared_info fields and takes care of the pfmemalloc bit;
- release all buffers belonging to a frame when it is dropped on RX
  errors, instead of leaking the ones already attached to the xdp_buff;
- drop the whole frame when the number of fragments exceeds
  MAX_SKB_FRAGS, instead of delivering a truncated one;
- strip the Ethernet FCS from the accumulated xdp_buff with a
  driver-local tail shrink (stmmac_xdp_shrink_tail()) for both linear
  and fragmented frames, instead of trimming the skb or the first
  descriptor length;
- skip zero-length fragments, which can happen for non-first
  descriptors when split-header (SPH) is enabled.

Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
---
 drivers/net/ethernet/stmicro/stmmac/stmmac.h      |   3 +-
 drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 348 ++++++++++++++--------
 2 files changed, 221 insertions(+), 130 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 4fc96b317d79..0148891dbcb4 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
@@ -132,7 +132,8 @@ struct stmmac_rx_queue {
 	dma_addr_t dma_rx_phy;
 	unsigned int state_saved;
 	struct {
-		struct sk_buff *skb;
+		struct xdp_buff xdp;
+		unsigned int frames;
 		unsigned int len;
 		unsigned int error;
 	} state;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index b2d20628ed21..3098fd6df158 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -158,6 +158,9 @@ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue);
 static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode,
 					  u32 rxmode, u32 chan);
 static void stmmac_vlan_restore(struct stmmac_priv *priv);
+static void stmmac_xdp_put_buff(struct stmmac_rx_queue *rx_q,
+				struct xdp_buff *xdp, int sync_len,
+				bool allow_direct);
 
 #ifdef CONFIG_DEBUG_FS
 static const struct net_device_ops stmmac_netdev_ops;
@@ -2180,8 +2183,10 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv,
 	else
 		dma_free_rx_skbufs(priv, dma_conf, queue);
 
-	dev_kfree_skb_any(rx_q->state.skb);
-	rx_q->state.skb = NULL;
+	if (rx_q->state.frames && !rx_q->xsk_pool) {
+		stmmac_xdp_put_buff(rx_q, &rx_q->state.xdp, -1, false);
+		rx_q->state.frames = 0;
+	}
 	rx_q->state_saved = false;
 	rx_q->buf_alloc_num = 0;
 	rx_q->xsk_pool = NULL;
@@ -5758,6 +5763,118 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
 	return failure ? limit : (int)count;
 }
 
+static void
+stmmac_xdp_put_buff(struct stmmac_rx_queue *rx_q, struct xdp_buff *xdp,
+		    int sync_len, bool allow_direct)
+{
+	struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
+	int i;
+
+	if (likely(!xdp_buff_has_frags(xdp)))
+		goto out;
+
+	for (i = 0; i < sinfo->nr_frags; i++)
+		page_pool_put_full_page(rx_q->page_pool,
+					skb_frag_page(&sinfo->frags[i]),
+					allow_direct);
+out:
+	page_pool_put_page(rx_q->page_pool, virt_to_head_page(xdp->data),
+			   sync_len, allow_direct);
+}
+
+static struct sk_buff *stmmac_build_skb(struct xdp_buff *xdp)
+{
+	struct skb_shared_info *sinfo = xdp_get_shared_info_from_buff(xdp);
+	u32 metasize = xdp->data - xdp->data_meta;
+	struct sk_buff *skb;
+	u8 num_frags = 0;
+
+	if (unlikely(xdp_buff_has_frags(xdp)))
+		num_frags = sinfo->nr_frags;
+
+	skb = napi_build_skb(xdp->data_hard_start, xdp->frame_sz);
+	if (!skb)
+		return NULL;
+
+	skb_mark_for_recycle(skb);
+	skb_reserve(skb, xdp->data - xdp->data_hard_start);
+	skb_put(skb, xdp->data_end - xdp->data);
+	if (metasize)
+		skb_metadata_set(skb, metasize);
+
+	if (unlikely(xdp_buff_has_frags(xdp)))
+		xdp_update_skb_frags_info(skb, num_frags, sinfo->xdp_frags_size,
+					  num_frags * xdp->frame_sz,
+					  xdp_buff_get_skb_flags(xdp));
+	return skb;
+}
+
+static bool stmmac_build_xdp_frags(struct stmmac_priv *priv,
+				   struct stmmac_rx_queue *rx_q,
+				   unsigned int len, struct page *page,
+				   unsigned int offset,
+				   enum dma_data_direction dma_dir,
+				   struct xdp_buff *xdp)
+{
+	dma_addr_t dma_addr = page_pool_get_dma_addr(page) + offset;
+
+	dma_sync_single_for_cpu(priv->device, dma_addr, len, dma_dir);
+	if (!xdp_buff_add_frag(xdp, page_to_netmem(page), offset, len,
+			       xdp->frame_sz)) {
+		page_pool_put_full_page(rx_q->page_pool, page, true);
+		return false;
+	}
+
+	return true;
+}
+
+static int stmmac_xdp_shrink_tail(struct stmmac_rx_queue *rx_q,
+				  struct xdp_buff *xdp, int offset)
+{
+	struct skb_shared_info *sinfo;
+	int i;
+
+	if (unlikely(offset < 0 ||
+		     offset > (int)xdp_get_buff_len(xdp) - ETH_HLEN))
+		return -EINVAL;
+
+	if (likely(!xdp_buff_has_frags(xdp))) {
+		xdp->data_end -= offset;
+		return 0;
+	}
+
+	sinfo = xdp_get_shared_info_from_buff(xdp);
+	for (i = sinfo->nr_frags - 1; i >= 0 && offset > 0; i--) {
+		skb_frag_t *frag = &sinfo->frags[i];
+		int delta = min_t(int, offset, skb_frag_size(frag));
+
+		if (delta == skb_frag_size(frag)) {
+			/* The whole frag is consumed by the strip: return it
+			 * to the page pool right away. Its ring slot still
+			 * carries the page DMA address until stmmac_rx_refill()
+			 * re-arms it at the end of the NAPI poll. This is
+			 * safe because HW cannot touch the slot until then.
+			 */
+			page_pool_put_full_page(rx_q->page_pool,
+						skb_frag_page(frag), true);
+			sinfo->nr_frags--;
+		} else {
+			skb_frag_size_sub(frag, delta);
+		}
+
+		sinfo->xdp_frags_size -= delta;
+		offset -= delta;
+	}
+
+	if (unlikely(!sinfo->nr_frags)) {
+		xdp_buff_clear_frags_flag(xdp);
+		xdp_buff_clear_frag_pfmemalloc(xdp);
+		xdp->data_end -= offset;
+	}
+
+	return 0;
+}
+
 /**
  * stmmac_rx - manage the receive process
  * @priv: driver private structure
@@ -5771,21 +5888,20 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 	u32 rx_errors = 0, rx_dropped = 0, rx_bytes = 0, rx_packets = 0;
 	struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue];
 	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
+	unsigned int frames = 0, next_entry = rx_q->cur_rx;
 	struct stmmac_channel *ch = &priv->channel[queue];
 	unsigned int count = 0, error = 0, len = 0;
-	unsigned int next_entry = rx_q->cur_rx;
 	bool in_progress = rx_q->state_saved;
 	enum dma_data_direction dma_dir;
 	int coe = priv->hw->rx_csum;
-	unsigned int desc_size;
-	struct sk_buff *skb = NULL;
 	struct stmmac_xdp_buff ctx;
-	bool fcs_stripped = false;
+	unsigned int desc_size;
 	int xdp_status = 0;
 	int bufsz;
 
 	dma_dir = page_pool_get_dma_dir(rx_q->page_pool);
-	bufsz = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE) * PAGE_SIZE;
+	bufsz = rx_q->napi_skb_frag_size;
+	ctx.priv = priv;
 
 	if (netif_msg_rx_status(priv)) {
 		void *rx_head = stmmac_get_rx_desc(priv, rx_q, 0);
@@ -5798,23 +5914,25 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 	}
 
 	if (rx_q->state_saved) {
-		skb = rx_q->state.skb;
+		ctx.xdp = rx_q->state.xdp;
 		error = rx_q->state.error;
+		frames = rx_q->state.frames;
 		len = rx_q->state.len;
-		rx_q->state.skb = NULL;
 		rx_q->state_saved = false;
+		rx_q->state.frames = 0;
 	}
 
 	while (count < limit) {
 		unsigned int buf1_len = 0, buf2_len = 0;
+		unsigned int pre_len, sync_len;
 		enum pkt_hash_types hash_type;
 		struct stmmac_rx_buffer *buf;
 		struct dma_desc *np, *p;
+		struct sk_buff *skb;
 		int entry, status;
 		u32 hash;
 
 		if (!in_progress) {
-			skb = NULL;
 			error = 0;
 			len = 0;
 		}
@@ -5851,20 +5969,24 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 		if (priv->extend_desc)
 			stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry);
 		if (unlikely(status == discard_frame)) {
-			page_pool_put_page(rx_q->page_pool, buf->page, 0, true);
-			buf->page = NULL;
 			error = 1;
 			if (!priv->hwts_rx_en)
 				rx_errors++;
 		}
 
-		if (unlikely(error && (status & rx_not_ls)))
-			goto read_again;
 		if (unlikely(error)) {
-			dev_kfree_skb(skb);
-			skb = NULL;
-			count++;
-			continue;
+			page_pool_put_page(rx_q->page_pool, buf->page, 0, true);
+			buf->page = NULL;
+			if (buf->sec_page) {
+				page_pool_put_page(rx_q->page_pool,
+						   buf->sec_page, 0, true);
+				buf->sec_page = NULL;
+			}
+
+			if (status & rx_not_ls)
+				goto read_again;
+
+			goto error_free_frag;
 		}
 
 		/* Buffer is good. Go on. */
@@ -5874,135 +5996,94 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 		buf2_len = stmmac_rx_buf2_len(priv, p, status, len);
 		len += buf2_len;
 
-		/* ACS is disabled; strip manually. */
-		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;
-
+		if (!frames) {
 			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;
-			}
-
 			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);
-
-			pre_len = ctx.xdp.data_end - ctx.xdp.data_hard_start -
-				  buf->page_offset;
-
-			ctx.priv = priv;
-			ctx.desc = p;
-			ctx.ndesc = np;
-
-			skb = stmmac_xdp_run_prog(priv, &ctx.xdp);
-			/* Due xdp_adjust_tail: DMA sync for_device
-			 * cover max len CPU touch
-			 */
-			sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start -
-				   buf->page_offset;
-			sync_len = max(sync_len, pre_len);
-
-			/* For Not XDP_PASS verdict */
-			if (IS_ERR(skb)) {
-				unsigned int xdp_res = -PTR_ERR(skb);
-
-				if (xdp_res & STMMAC_XDP_CONSUMED) {
-					page_pool_put_page(rx_q->page_pool,
-							   virt_to_head_page(ctx.xdp.data),
-							   sync_len, true);
-					buf->page = NULL;
-					rx_dropped++;
-
-					/* Clear skb as it was set as
-					 * status by XDP program.
-					 */
-					skb = NULL;
-
-					if (unlikely((status & rx_not_ls)))
-						goto read_again;
-
-					count++;
-					continue;
-				} else if (xdp_res & (STMMAC_XDP_TX |
-						      STMMAC_XDP_REDIRECT)) {
-					xdp_status |= xdp_res;
-					buf->page = NULL;
-					skb = NULL;
-					count++;
-					continue;
-				}
-			}
-		}
-
-		if (!skb) {
-			unsigned int head_pad_len;
-
-			/* XDP program may expand or reduce tail */
-			buf1_len = ctx.xdp.data_end - ctx.xdp.data;
-
-			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);
-				buf->page = NULL;
-				rx_dropped++;
-				count++;
-				goto drain_data;
-			}
-
-			/* XDP program may adjust header */
-			head_pad_len = ctx.xdp.data - ctx.xdp.data_hard_start;
-			skb_reserve(skb, head_pad_len);
-			skb_put(skb, buf1_len);
-			skb_mark_for_recycle(skb);
 			buf->page = NULL;
 		} else if (buf1_len) {
-			dma_sync_single_for_cpu(priv->device, buf->addr,
-						buf1_len, dma_dir);
-			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
-					buf->page, buf->page_offset, buf1_len,
-					priv->dma_conf.dma_buf_sz);
+			if (!stmmac_build_xdp_frags(priv, rx_q, buf1_len,
+						    buf->page,
+						    buf->page_offset,
+						    dma_dir, &ctx.xdp)) {
+				if (!error)
+					rx_dropped++;
+				error = 1;
+			}
 			buf->page = NULL;
 		}
 
 		if (buf2_len) {
-			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);
+			if (!stmmac_build_xdp_frags(priv, rx_q, buf2_len,
+						    buf->sec_page, 0,
+						    dma_dir, &ctx.xdp)) {
+				if (!error)
+					rx_dropped++;
+				error = 1;
+			}
 			buf->sec_page = NULL;
 		}
+		frames++;
 
-drain_data:
 		if (likely(status & rx_not_ls))
 			goto read_again;
-		if (!skb)
-			continue;
 
-		/* Got entire packet into SKB. Finish it. */
+		/* ACS is disabled; strip manually. */
+		len -= ETH_FCS_LEN;
+		if (stmmac_xdp_shrink_tail(rx_q, &ctx.xdp, ETH_FCS_LEN)) {
+			if (!error)
+				rx_dropped++;
+			error = 1;
+		}
 
-		/* Remove FCS if needed */
-		if (!fcs_stripped && pskb_trim(skb, len)) {
-			dev_kfree_skb_any(skb);
-			skb = NULL;
-			rx_dropped++;
+		if (unlikely(error))
+			goto error_free_frag;
+
+		pre_len = ctx.xdp.data_end - ctx.xdp.data_hard_start;
+
+		ctx.desc = p;
+		ctx.ndesc = np;
+
+		skb = stmmac_xdp_run_prog(priv, &ctx.xdp);
+		/* Due xdp_adjust_tail: DMA sync for_device
+		 * cover max len CPU touch
+		 */
+		sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start;
+		sync_len = max(sync_len, pre_len);
+
+		/* For Not XDP_PASS verdict */
+		if (IS_ERR(skb)) {
+			unsigned int xdp_res = -PTR_ERR(skb);
+
+			if (xdp_res & STMMAC_XDP_CONSUMED) {
+				stmmac_xdp_put_buff(rx_q, &ctx.xdp, sync_len,
+						    true);
+				rx_dropped++;
+			} else if (xdp_res & (STMMAC_XDP_TX |
+					      STMMAC_XDP_REDIRECT)) {
+				xdp_status |= xdp_res;
+			}
+
+			/* Clear skb as it was set as status by
+			 * the XDP program
+			 */
+			frames = 0;
 			count++;
 			continue;
 		}
 
+		skb = stmmac_build_skb(&ctx.xdp);
+		if (!skb) {
+			rx_dropped++;
+			goto error_free_frag;
+		}
+
+		/* Got entire packet into SKB. Finish it. */
 		stmmac_get_rx_hwtstamp(priv, p, np, skb);
 
 		if (priv->hw->hw_vlan_en)
@@ -6025,18 +6106,24 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
 
 		skb_record_rx_queue(skb, queue);
 		napi_gro_receive(&ch->rx_napi, skb);
-		skb = NULL;
-
 		rx_packets++;
 		rx_bytes += len;
+		frames = 0;
+		count++;
+		continue;
+error_free_frag:
+		if (frames)
+			stmmac_xdp_put_buff(rx_q, &ctx.xdp, -1, true);
+		frames = 0;
 		count++;
 	}
 
-	if (in_progress || skb) {
-		rx_q->state_saved = true;
-		rx_q->state.skb = skb;
+	if (in_progress || frames) {
+		rx_q->state.xdp = ctx.xdp;
+		rx_q->state.frames = frames;
 		rx_q->state.error = error;
 		rx_q->state.len = len;
+		rx_q->state_saved = true;
 	}
 
 	stmmac_finalize_xdp_rx(priv, xdp_status);
@@ -8352,9 +8439,12 @@ static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
 {
 	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
 
-	dev_kfree_skb_any(rx_q->state.skb);
-	rx_q->state.skb = NULL;
+	if (rx_q->state.frames && !rx_q->xsk_pool) {
+		stmmac_xdp_put_buff(rx_q, &rx_q->state.xdp, -1, false);
+		rx_q->state.frames = 0;
+	}
 	rx_q->state_saved = false;
+
 	rx_q->cur_rx = 0;
 	rx_q->dirty_rx = 0;
 }

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support
  2026-10-04 20:42 [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
  2026-10-04 20:42 ` [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
  2026-10-04 20:42 ` [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-04 20:49 ` netdev-bot+sinfo
  2026-10-05  7:43   ` Lorenzo Bianconi
  2026-10-06  9:42   ` Lorenzo Bianconi
  2 siblings, 2 replies; 8+ messages in thread
From: netdev-bot+sinfo @ 2026-10-04 20:49 UTC (permalink / raw)
  To: Lorenzo Bianconi
  Cc: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong,
	netdev, linux-stm32, linux-arm-kernel, bpf, James Hilliard

Hi!

This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:

 - How the issue was discovered, e.g. hit in production, hit during
   development, syzbot report, manual code inspection, LLM or static
   analysis tool scan.

 - Whether the issue was actually triggered, or is only theoretical
   (e.g. found by code inspection). If it was triggered please include
   the symptoms, like the stack trace or error messages.

 - What hardware the change was tested on. For driver fixes please
   mention the device (and if relevant firmware version) used for
   testing, or say that the change was not tested on real hardware.

Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.

The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support
  2026-10-04 20:49 ` [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
@ 2026-10-05  7:43   ` Lorenzo Bianconi
  2026-10-06  9:42   ` Lorenzo Bianconi
  1 sibling, 0 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-05  7:43 UTC (permalink / raw)
  To: netdev-bot+sinfo
  Cc: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong,
	netdev, linux-stm32, linux-arm-kernel, bpf, James Hilliard

[-- Attachment #1: Type: text/plain, Size: 1341 bytes --]

> Hi!
> 
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
> 
>  - How the issue was discovered, e.g. hit in production, hit during
>    development, syzbot report, manual code inspection, LLM or static
>    analysis tool scan.

The issue was spotted during code development.

> 
>  - Whether the issue was actually triggered, or is only theoretical
>    (e.g. found by code inspection). If it was triggered please include
>    the symptoms, like the stack trace or error messages.

This is a theoretical issue found during code inspection.

> 
>  - What hardware the change was tested on. For driver fixes please
>    mention the device (and if relevant firmware version) used for
>    testing, or say that the change was not tested on real hardware.

I tested this patch on a Qualcomm Rb3-gen2 board.

Regards,
Lorenzo

> 
> Please do not repost the series just to address the above. Instead,
> reply to this email with the missing information, so that reviewers
> can take it into account. If the series needs another revision for
> other reasons, please include the information in the commit messages
> then.
> 
> The evaluation is done by an LLM so it may be wrong, if you think
> that is the case please reply and explain.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry
  2026-10-04 20:42 ` [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-05 21:16   ` netdev-bot+sashiko
  0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-05 21:16 UTC (permalink / raw)
  To: lorenzo.bianconi
  Cc: maxime.chevallier, andrew+netdev, davem, edumazet, kuba, pabeni,
	mcoquelin.stm32, alexandre.torgue, ast, daniel, hawk,
	john.fastabend, sdf, Jose.Abreu, boon.leong.ong, netdev,
	linux-stm32, linux-arm-kernel, bpf

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 0 · 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] At the reviewed commit, stmmac_rx() resets skb/error/len at the
  top of the loop only when `!in_progress`, instead of whenever count >…
- [Low] The commit message leaves out one of the behaviour changes.

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but it remains at
  the end of the series and the patch reworks the same continuation-…

--

net: stmmac: take ownership of saved RX state at poll entry

This patch changes stmmac_rx() and stmmac_rx_zc() so they take the saved
RX state at poll entry and clear it right away. The state is saved again
only if the frame is still incomplete, which a new in_progress flag tracks.
The patch also frees any saved partial skb in
__free_dma_rx_desc_resources() and stmmac_reset_rx_queue().

> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 0c381ae0d0ffa..b2d20628ed214 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c

[ ... ]

> @@ -5652,6 +5657,8 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
>  		if (!buf->xdp)
>  			break;
>  
> +		in_progress = status & rx_not_ls;
> +
>  		if (priv->extend_desc)
>  			stmmac_rx_extended_status(priv, &priv->xstats,
>  						  rx_q->dma_erx + entry);

[Severity: High]
This isn't a bug introduced by this patch, but this patch reworks the
continuation state in stmmac_rx_zc(), so it seems worth raising here. Can
a frame that spans more than one descriptor be delivered with the wrong
length?

The non-LS branch drops the buffer without updating len or setting error:

	/* XSK pool expects RX frame 1:1 mapped to XSK buffer */
	if (likely(status & rx_not_ls)) {
		xsk_buff_free(buf->xdp);
		buf->xdp = NULL;
		dirty++;
		count++;
		goto read_again;
	}

The LS descriptor is then handled as a complete frame with len == 0:

	buf1_len = stmmac_rx_buf1_len(priv, p, status, len);
	len += buf1_len;
	...
	buf->xdp->data_end = buf->xdp->data + buf1_len;

stmmac_rx_buf1_len() returns min(dma_buf_sz, plen - len), so buf1_len
comes from the total frame length. The LS buffer only holds the tail of
the frame, though. Also, the hardware buffer size for an XSK queue is
xsk_pool_get_rx_frame_size() (see stmmac_set_queue_rx_buf_size()), not
dma_buf_sz.

stmmac_xdp_enable_pool() only requires frame_size >= ETH_FRAME_LEN +
VLAN_HLEN * 2, and its comment says "XDP ZC does not span multiple
frame". However, dwmac4 sets GMAC_CONFIG_JE in GMAC_CORE_INIT. So jumbo
frames from the wire are accepted and split across descriptors.

For example, with 4K chunks frame_size is 3840. A 5000-byte frame leaves
1160 real bytes in the LS buffer, but data_end = data + 1532. The XDP
program, AF_XDP or stmmac_construct_skb_zc() would then see a frame whose
L2 header comes from the sender's payload, followed by 372 stale umem
bytes. If frame_size is in [1522, 1536), data_end can also run a few
bytes past the end of the chunk.

The new in_progress tracking only carries error and len, and both are 0
here, so it doesn't change this.

Would it work to set error = 1 for a non-LS descriptor in ZC mode? The new
in_progress tracking would then carry the drop state across poll and
DMA-owned boundaries. The follow-up patch in the series doesn't touch
stmmac_rx_zc().

[ ... ]

> @@ -5788,25 +5796,28 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)

[ ... ]

> -		if (!count && rx_q->state_saved) {
> -			skb = rx_q->state.skb;
> -			error = rx_q->state.error;
> -			len = rx_q->state.len;
> -		} else {
> -			rx_q->state_saved = false;
> +		if (!in_progress) {
>  			skb = NULL;
>  			error = 0;
>  			len = 0;
>  		}
> -
>  read_again:
>  		if (count >= limit)
>  			break;
> @@ -5835,6 +5846,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
>  
>  		prefetch(np);
>  
> +		in_progress = status & rx_not_ls;
> +
>  		if (priv->extend_desc)
>  			stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry);
>  		if (unlikely(status == discard_frame)) {

[Severity: High]
Can len carry over into a new frame when an XDP program returns XDP_TX
or XDP_REDIRECT on the first, non-LS descriptor of a multi-descriptor
frame?

in_progress is set from status & rx_not_ls before the XDP verdict, and
the TX/REDIRECT path doesn't clear it:

	} else if (xdp_res & (STMMAC_XDP_TX |
			      STMMAC_XDP_REDIRECT)) {
		xdp_status |= xdp_res;
		buf->page = NULL;
		skb = NULL;
		count++;
		continue;
	}

The next iteration skips the !in_progress reset. len is kept while skb
is NULL, so the continuation descriptor is treated as the start of a
frame.

If plen - len is 1 to 3 bytes, stmmac_rx_buf1_len() returns that value
and stmmac_rx_buf2_len() returns 0. Then this code:

	if (stmmac_xdp_is_enabled(priv) && !buf2_len) {
		buf1_len -= ETH_FCS_LEN;
		fcs_stripped = true;
	}

wraps buf1_len to about 0xfffffffd. xdp_prepare_buff() then sets
data_end about 4GiB past data. On XDP_PASS, this later code would hit
skb_over_panic():

	buf1_len = ctx.xdp.data_end - ctx.xdp.data;
	...
	skb_put(skb, buf1_len);

dwmac4 sets GMAC_CONFIG_JE, so a remote sender can choose the frame
length.

Before this patch, the XDP_CONSUMED plus rx_not_ls goto read_again path
already kept len. The mid-frame TX/REDIRECT route looks new, though.

The next patch in the series, "net: stmmac: rework stmmac_rx to support
XDP rx multi-buff", removes this path. After it, the XDP program only runs
after the LS descriptor, and the FCS is stripped by
stmmac_xdp_shrink_tail(), which bounds-checks the offset.

This patch carries Fixes: tags. Could it be backported to stable without
that rework?

[ ... ]

> @@ -8339,6 +8352,9 @@ static void stmmac_reset_rx_queue(struct stmmac_priv *priv, u32 queue)
>  {
>  	struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue];
>  
> +	dev_kfree_skb_any(rx_q->state.skb);
> +	rx_q->state.skb = NULL;
> +	rx_q->state_saved = false;
>  	rx_q->cur_rx = 0;
>  	rx_q->dirty_rx = 0;
>  }

[Severity: Low]
The commit message says:

    Release a saved partial skb when the RX ring is destroyed.

That covers the __free_dma_rx_desc_resources() hunk. The
stmmac_reset_rx_queue() hunk doesn't destroy the ring, though. It runs
through stmmac_reset_queues_param() from __stmmac_open(),
stmmac_xdp_open() and stmmac_resume(), and is also called directly from
stmmac_enable_rx_queue().

Before this patch, stmmac_reset_rx_queue() only cleared cur_rx and
dirty_rx. So after stmmac_resume(), the first poll could add descriptors
from the re-armed ring to the partial skb saved before suspend.

Could the commit message also mention the open, resume and queue-enable
change? The patch carries Fixes: tags and may go to stable.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261004-stmmac-rx-mb-v3-0-50fa171af9ec%40oss.qualcomm.com


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
  2026-10-04 20:42 ` [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-05 21:16   ` netdev-bot+sashiko
  0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-05 21:16 UTC (permalink / raw)
  To: lorenzo.bianconi
  Cc: maxime.chevallier, andrew+netdev, davem, edumazet, kuba, pabeni,
	mcoquelin.stm32, alexandre.torgue, ast, daniel, hawk,
	john.fastabend, sdf, Jose.Abreu, boon.leong.ong, netdev,
	linux-stm32, linux-arm-kernel, bpf

Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 2 · Low: 3

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).

- [Medium] stmmac_rx() now gathers every buffer of a frame into one
  xdp_buff.
- [Medium] stmmac_rx() now hands a multi-buffer xdp_buff (head plus frags
  in the head page's skb_shared_info) to the XDP program.
- [Low] Several 'In addition' bullets in the commit message describe the
  parent tree's behaviour wrongly.
- [Low] Rejected in the baseline decision; kept here because
  requires_validation is set.
- [Low] In stmmac_rx(), the non-PASS verdict branch (if (IS_ERR(skb)))
  still has the comment '/* Clear skb as it was set as status by the XDP…

--

net: stmmac: rework stmmac_rx to support XDP rx multi-buff

This reworks stmmac_rx() to accumulate all descriptors of a frame into a
single xdp_buff, so the XDP program runs on the full, possibly fragmented,
packet. A partial skb is no longer saved in rx_q->state across NAPI polls;
the in-progress xdp_buff is saved there instead. XDP_PASS frames are built
into an skb with napi_build_skb() and xdp_update_skb_frags_info(), and FCS
stripping moves to a new stmmac_xdp_shrink_tail() helper.

> Build the xdp_buff by accumulating all the descriptors that make up a
> frame, so the XDP program runs on the full (possibly fragmented) packet
> instead of just the first buffer.

[Severity: Low]
This isn't a bug, but could the scope be stated more precisely? When XDP is
enabled, stmmac_xdp_set_prog() still rejects dev->mtu > ETH_DATA_LEN and
still turns off SPH:

	priv->sph_active = priv->sph_capable && !stmmac_xdp_is_enabled(priv);

So the program only sees fragmented xdp_buffs for oversized frames that the
MAC accepts at MTU 1500 through GMAC_CONFIG_JE or the XGMAC GPSL setting.

Would it help to say in the commit message that the jumbo MTU restriction
for XDP stays in place?

[ ... ]

> - release all buffers belonging to a frame when it is dropped on RX
>   errors, instead of leaking the ones already attached to the xdp_buff;
> - drop the whole frame when the number of fragments exceeds
>   MAX_SKB_FRAGS, instead of delivering a truncated one;

[ ... ]

> - skip zero-length fragments, which can happen for non-first
>   descriptors when split-header (SPH) is enabled.

[Severity: Low]
Are these bullets accurate when compared with the parent tree?

On the leak claim: the parent stmmac_rx() built no xdp_buff frags. On
error it called dev_kfree_skb(skb) on an skb marked with
skb_mark_for_recycle(), which returns the attached page_pool frags.

On the truncation claim: the parent called
skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, ...) with no bound, so it
would not have delivered a truncated frame.

On zero-length fragments: the parent already had the
"} else if (buf1_len) {" and "if (buf2_len) {" guards.

These read like changelog notes from an earlier revision of the patch.
Could they be reworded to describe the change against the current tree?

> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index b2d20628ed214..3098fd6df1582 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c

[ ... ]

> @@ -5874,135 +5996,94 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)

[ ... ]

>  		} else if (buf1_len) {
> -			dma_sync_single_for_cpu(priv->device, buf->addr,
> -						buf1_len, dma_dir);
> -			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
> -					buf->page, buf->page_offset, buf1_len,
> -					priv->dma_conf.dma_buf_sz);
> +			if (!stmmac_build_xdp_frags(priv, rx_q, buf1_len,
> +						    buf->page,
> +						    buf->page_offset,
> +						    dma_dir, &ctx.xdp)) {
> +				if (!error)
> +					rx_dropped++;
> +				error = 1;
> +			}
>  			buf->page = NULL;
>  		}
>  
>  		if (buf2_len) {
> -			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);
> +			if (!stmmac_build_xdp_frags(priv, rx_q, buf2_len,
> +						    buf->sec_page, 0,
> +						    dma_dir, &ctx.xdp)) {
> +				if (!error)
> +					rx_dropped++;
> +				error = 1;
> +			}
>  			buf->sec_page = NULL;
>  		}
> +		frames++;

[ ... ]

> +		skb = stmmac_xdp_run_prog(priv, &ctx.xdp);

[Severity: Medium]
Can this pass a multi-buffer xdp_buff to an XDP program that was loaded
without BPF_F_XDP_HAS_FRAGS?

stmmac_build_xdp_frags() calls xdp_buff_add_frag(), which sets
XDP_FLAGS_HAS_FRAGS. stmmac_xdp_run_prog() then runs whatever program is
attached, and the driver never checks prog->aux->xdp_has_frags.
stmmac_xdp_set_prog() only gates the attach on MTU:

	if (prog && dev->mtu > ETH_DATA_LEN) {

These frames can still arrive at MTU 1500. GMAC_CORE_INIT always sets
GMAC_CONFIG_JE, XGMAC sets GPSL to XGMAC_JUMBO_LEN, and dma_buf_sz is 1536.
On cores with buffer2, a 1537 to 3072 byte frame fills buf1 and buf2 of one
descriptor and becomes a head plus one frag. Larger frames span several
descriptors.

A program that never opted in to frags sees only the head through
data/data_end, but its verdict applies to the whole frame.

Also, stmmac_dvr_probe() still advertises:

	ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
			     NETDEV_XDP_ACT_XSK_ZEROCOPY;

Should NETDEV_XDP_ACT_RX_SG be advertised here? And should programs
without xdp_has_frags either be rejected or kept away from frags-bearing
buffers?

> +		/* Due xdp_adjust_tail: DMA sync for_device
> +		 * cover max len CPU touch
> +		 */
> +		sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start;
> +		sync_len = max(sync_len, pre_len);
> +
> +		/* For Not XDP_PASS verdict */
> +		if (IS_ERR(skb)) {
> +			unsigned int xdp_res = -PTR_ERR(skb);
> +
> +			if (xdp_res & STMMAC_XDP_CONSUMED) {
> +				stmmac_xdp_put_buff(rx_q, &ctx.xdp, sync_len,
> +						    true);
> +				rx_dropped++;
> +			} else if (xdp_res & (STMMAC_XDP_TX |
> +					      STMMAC_XDP_REDIRECT)) {
> +				xdp_status |= xdp_res;
> +			}

[Severity: Medium]
What happens on XDP_TX when ctx.xdp carries frags? Does the frame go out
truncated to the head buffer?

The path is:

__stmmac_xdp_run_prog()
  stmmac_xdp_xmit_back()
    xdp_convert_buff_to_frame()
    stmmac_xdp_xmit_xdpf()

The converted frame keeps the frags flag, but xdpf->len covers only the
linear head. stmmac_xdp_xmit_xdpf() sets up one descriptor and marks it as
both first and last:

	stmmac_set_tx_dma_entry(tx_q, entry, buf_type, dma_addr, xdpf->len,
				false);
	stmmac_set_tx_dma_last_segment(tx_q, entry);
	...
	stmmac_prepare_tx_desc(priv, tx_desc, 1, xdpf->len,
			       csum, priv->descriptor_mode, true, true,
			       xdpf->len);

The frags are never mapped or queued, yet the action is counted as
successful. The EST SDU check also measures only the head:

	    xdpf->len > priv->est->max_sdu[queue]) {

The frag pages are not leaked, because stmmac_tx_clean() returns them via
xdp_return_frame_rx_napi().

Oversized XDP_TX frames were also truncated before this patch. The
difference is that the program now sees the whole frame and returns
XDP_TX for it.

Should frags-bearing frames be dropped on XDP_TX until
stmmac_xdp_xmit_xdpf() can transmit multiple descriptors?

> +
> +			/* Clear skb as it was set as status by
> +			 * the XDP program
> +			 */
> +			frames = 0;

[Severity: Low]
This isn't a bug, but the comment still talks about clearing skb. The code
now resets frames instead, and skb is a loop-local variable that is never
cleared. Could the comment say that the in-progress frame state is being
dropped?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261004-stmmac-rx-mb-v3-0-50fa171af9ec%40oss.qualcomm.com


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support
  2026-10-04 20:49 ` [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
  2026-10-05  7:43   ` Lorenzo Bianconi
@ 2026-10-06  9:42   ` Lorenzo Bianconi
  1 sibling, 0 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-06  9:42 UTC (permalink / raw)
  To: netdev-bot+sinfo
  Cc: Maxime Chevallier, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin, Alexandre Torgue,
	Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
	John Fastabend, Stanislav Fomichev, Jose Abreu, Ong Boon Leong,
	netdev, linux-stm32, linux-arm-kernel, bpf, James Hilliard

[-- Attachment #1: Type: text/plain, Size: 1223 bytes --]

> Hi!
> 
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
> 
>  - How the issue was discovered, e.g. hit in production, hit during
>    development, syzbot report, manual code inspection, LLM or static
>    analysis tool scan.
> 
>  - Whether the issue was actually triggered, or is only theoretical
>    (e.g. found by code inspection). If it was triggered please include
>    the symptoms, like the stack trace or error messages.
> 
>  - What hardware the change was tested on. For driver fixes please
>    mention the device (and if relevant firmware version) used for
>    testing, or say that the change was not tested on real hardware.
> 
> Please do not repost the series just to address the above. Instead,
> reply to this email with the missing information, so that reviewers
> can take it into account. If the series needs another revision for
> other reasons, please include the information in the commit messages
> then.
> 
> The evaluation is done by an LLM so it may be wrong, if you think
> that is the case please reply and explain.

I will fix the sashiko's reported problem in v4.

Regards,
Lorenzo

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-06  9:42 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-04 20:42 [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-04 20:42 ` [PATCH net-next v3 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
2026-10-05 21:16   ` netdev-bot+sashiko
2026-10-04 20:42 ` [PATCH net-next v3 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
2026-10-05 21:16   ` netdev-bot+sashiko
2026-10-04 20:49 ` [PATCH net-next v3 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
2026-10-05  7:43   ` Lorenzo Bianconi
2026-10-06  9:42   ` Lorenzo Bianconi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox