* [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support
@ 2026-10-06 10:02 Lorenzo Bianconi
2026-10-06 10:02 ` [PATCH net-next v4 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-06 10:02 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() to build the xdp_buff from all the descriptors that make
up a frame, so the XDP program runs on the whole (possibly fragmented)
packet instead of the first buffer only. Fix possible rx_q state leaks
when closing/opening the interface.
Note that an XDP program still cannot be attached with an MTU larger than
ETH_DATA_LEN and SPH is disabled while XDP is enabled, so an ordinary
frame fits in a single buffer. The multi-buffer path is exercised by
frames that do span several descriptors (for example jumbo frames the MAC
admits through its jumbo-enable bit) and is groundwork for extending XDP
to jumbo/SPH; it does not enable XDP jumbo support by itself.
---
Changes in v4:
- Reset in_progress flag when the packet is fully consumed.
- Advetise NETDEV_XDP_ACT_RX_SG capability.
- Link to v3: https://lore.kernel.org/r/20261004-stmmac-rx-mb-v3-0-50fa171af9ec@oss.qualcomm.com
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 | 390 ++++++++++++++--------
2 files changed, 250 insertions(+), 143 deletions(-)
---
base-commit: a5e7d8e446af9803e37a3b6a4d416fb41178348f
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 v4 1/2] net: stmmac: take ownership of saved RX state at poll entry
2026-10-06 10:02 [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
@ 2026-10-06 10:02 ` Lorenzo Bianconi
2026-10-08 22:03 ` netdev-bot+sashiko
2026-10-06 10:02 ` [PATCH net-next v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-06 10:02 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
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.
The saved state is also dropped when the queue parameters are reset, not
only when the ring is destroyed: stmmac_reset_rx_queue() is reached from
__stmmac_open(), stmmac_xdp_open(), stmmac_resume() and
stmmac_enable_rx_queue(). Otherwise a partial frame saved before suspend
(or before a queue is disabled and re-enabled) survives while cur_rx and
dirty_rx are re-armed, and the first poll resumes that stale skb with
descriptors from the freshly initialized ring. Teardown still releases it
in __free_dma_rx_desc_resources().
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>
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 85 ++++++++++++-----------
1 file changed, 44 insertions(+), 41 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index 0cfd14d24002..28e9f8438f93 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;
@@ -5580,12 +5583,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);
@@ -5596,23 +5599,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;
@@ -5651,6 +5656,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);
@@ -5665,6 +5672,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
if (unlikely(error && (status & rx_not_ls)))
goto read_again;
+
if (unlikely(error)) {
count++;
continue;
@@ -5723,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;
@@ -5765,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;
@@ -5787,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;
@@ -5834,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)) {
@@ -5846,11 +5860,10 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
if (unlikely(error && (status & rx_not_ls)))
goto read_again;
+
if (unlikely(error)) {
dev_kfree_skb(skb);
- skb = NULL;
- count++;
- continue;
+ goto next;
}
/* Buffer is good. Go on. */
@@ -5909,24 +5922,12 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
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;
+ goto next;
} else if (xdp_res & (STMMAC_XDP_TX |
STMMAC_XDP_REDIRECT)) {
xdp_status |= xdp_res;
buf->page = NULL;
- skb = NULL;
- count++;
- continue;
+ goto next;
}
}
}
@@ -5983,10 +5984,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
/* Remove FCS if needed */
if (!fcs_stripped && pskb_trim(skb, len)) {
dev_kfree_skb_any(skb);
- skb = NULL;
rx_dropped++;
- count++;
- continue;
+ goto next;
}
stmmac_get_rx_hwtstamp(priv, p, np, skb);
@@ -6011,14 +6010,15 @@ 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;
+next:
+ in_progress = false;
+ skb = NULL;
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;
@@ -8338,6 +8338,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 v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
2026-10-06 10:02 [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-06 10:02 ` [PATCH net-next v4 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-06 10:02 ` Lorenzo Bianconi
2026-10-08 22:03 ` netdev-bot+sashiko
2026-10-06 10:05 ` [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
2026-10-09 10:30 ` Lorenzo Bianconi
3 siblings, 1 reply; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-06 10:02 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().
Note that XDP still rejects an MTU larger than ETH_DATA_LEN
(stmmac_xdp_set_prog(), stmmac_change_mtu()) and disables SPH, so an
ordinary frame fits in a single buffer. This path is therefore exercised
by frames that do span several descriptors (for example jumbo frames the
MAC admits through its jumbo-enable bit) and is groundwork for extending
XDP to jumbo/SPH; it does not enable XDP jumbo support by itself.
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:
- advertise NETDEV_XDP_ACT_RX_SG, since the RX path can now deliver
non-linear buffers to XDP;
- 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 the buffers collected in the xdp_buff when a frame is dropped
on RX errors; the previous code relied on dev_kfree_skb() recycling the
skb frags, which no longer applies now that the head and fragments live
in an xdp_buff rather than a partially built skb;
- drop the whole frame when it would exceed MAX_SKB_FRAGS, where the
previous skb_add_rx_frag() calls were unbounded;
- 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.
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 | 331 ++++++++++++++--------
2 files changed, 219 insertions(+), 115 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 28e9f8438f93..742866466526 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,19 +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);
- goto next;
+ 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. */
@@ -5873,121 +5996,89 @@ 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++;
- goto next;
- } else if (xdp_res & (STMMAC_XDP_TX |
- STMMAC_XDP_REDIRECT)) {
- xdp_status |= xdp_res;
- buf->page = NULL;
- goto next;
- }
- }
- }
-
- 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;
+ }
+
+ 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;
+ }
- /* Remove FCS if needed */
- if (!fcs_stripped && pskb_trim(skb, len)) {
- dev_kfree_skb_any(skb);
- rx_dropped++;
goto next;
}
+ 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)
@@ -6014,15 +6105,23 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
rx_bytes += len;
next:
in_progress = false;
- skb = NULL;
+ frames = 0;
+ count++;
+ continue;
+error_free_frag:
+ if (frames)
+ stmmac_xdp_put_buff(rx_q, &ctx.xdp, -1, true);
+ in_progress = false;
+ 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);
@@ -8022,6 +8121,7 @@ static int __stmmac_dvr_probe(struct device *device,
ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
NETIF_F_RXCSUM;
ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_RX_SG |
NETDEV_XDP_ACT_XSK_ZEROCOPY;
ret = stmmac_tc_init(priv, priv);
@@ -8338,9 +8438,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 v4 0/2] net: stmmac: introduce XDP rx multi-buff support
2026-10-06 10:02 [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-06 10:02 ` [PATCH net-next v4 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
2026-10-06 10:02 ` [PATCH net-next v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-06 10:05 ` netdev-bot+sinfo
2026-10-06 10:09 ` Lorenzo Bianconi
2026-10-09 10:30 ` Lorenzo Bianconi
3 siblings, 1 reply; 8+ messages in thread
From: netdev-bot+sinfo @ 2026-10-06 10:05 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 v4 0/2] net: stmmac: introduce XDP rx multi-buff support
2026-10-06 10:05 ` [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
@ 2026-10-06 10:09 ` Lorenzo Bianconi
0 siblings, 0 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-06 10:09 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 v4 1/2] net: stmmac: take ownership of saved RX state at poll entry
2026-10-06 10:02 ` [PATCH net-next v4 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-08 22:03 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-08 22:03 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 · Medium: 0 (2) · 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 this commit, stmmac_rx() handles an XDP non-PASS verdict
(STMMAC_XDP_CONSUMED, plus TX/REDIRECT) with `goto next` even when the…
- [Low] The commit message suggests that releasing the saved partial skb
at teardown already existed.
Pre-existing issues:
- [Medium] In stmmac_rx_zc(), the 'XSK pool expects RX frame 1:1 mapped
to XSK buffer' branch handles a non-last descriptor (rx_not_ls) by…
- [Medium] In stmmac_rx() at this commit, if napi_build_skb() fails on a
first descriptor that has rx_not_ls set, the code recycles the page,…
--
net: stmmac: take ownership of saved RX state at poll entry
This patch changes stmmac_rx() and stmmac_rx_zc() so that each poll takes
the saved partial-frame state at entry and clears it right away. The state
is saved again only if the frame is still incomplete. The saved skb is also
freed, and state_saved cleared, in stmmac_reset_rx_queue() and
__free_dma_rx_desc_resources().
> The saved state is also dropped when the queue parameters are reset, not
> only when the ring is destroyed: stmmac_reset_rx_queue() is reached from
> __stmmac_open(), stmmac_xdp_open(), stmmac_resume() and
> stmmac_enable_rx_queue(). Otherwise a partial frame saved before suspend
> (or before a queue is disabled and re-enabled) survives while cur_rx and
> dirty_rx are re-armed, and the first poll resumes that stale skb with
> descriptors from the freshly initialized ring. Teardown still releases it
> in __free_dma_rx_desc_resources().
[Severity: Low]
Is the last sentence accurate? Before this patch,
__free_dma_rx_desc_resources() did not free rx_q->state.skb or clear
rx_q->state_saved. Nothing outside stmmac_rx() referenced state.skb.
The first hunk of this patch is what adds the teardown release:
+ dev_kfree_skb_any(rx_q->state.skb);
+ rx_q->state.skb = NULL;
+ rx_q->state_saved = false;
That hunk seems to fix a separate problem. A partial skb leaked on close or
MTU change. A stale state_saved/state.skb also survived
stmmac_disable_rx_queue() and stmmac_enable_rx_queue() after the page_pool
had been destroyed.
Could the commit message describe this as part of the fix, rather than as
existing behaviour?
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 0cfd14d240022..28e9f8438f93f 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -5665,6 +5672,7 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
>
> if (unlikely(error && (status & rx_not_ls)))
> goto read_again;
> +
> if (unlikely(error)) {
> count++;
> continue;
[Severity: Medium]
This is a pre-existing issue, not one introduced by this patch. It is in
the continuation handling this patch reworks. Just below, a non-last
descriptor is handled like this:
/* 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;
}
This branch sets neither error nor len. Won't the last descriptor of the
frame then pass both error checks with len still 0?
In that case, stmmac_rx_buf1_len(priv, p, status, 0) returns
min(dma_buf_sz, plen). For a multi-descriptor frame that is dma_buf_sz.
The tail is then run through XDP and delivered as a complete frame,
either by XDP_PASS through stmmac_dispatch_skb_zc() or by redirect to the
XSK. That frame starts in the middle of the real frame and is padded with
stale UMEM bytes.
The same thing happens across polls. The save at the end of stmmac_rx_zc()
records state.error = 0 and state.len = 0 for this continuation.
Should the rest of this frame be marked for discard here?
[ ... ]
> @@ -5909,24 +5922,12 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
> 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;
> + goto next;
[Severity: High]
Before this change, an STMMAC_XDP_CONSUMED verdict on a descriptor with
rx_not_ls set went back to read_again and kept the running len. Now it
jumps to next:, which does this:
next:
in_progress = false;
skb = NULL;
count++;
The top of the loop then resets the length:
if (!in_progress) {
skb = NULL;
error = 0;
len = 0;
}
Doesn't this make the remaining descriptors of the same hardware frame get
parsed as a new frame? The STMMAC_XDP_TX/STMMAC_XDP_REDIRECT branch below
also reaches next: in the same state.
Here is an example on a core with buf2 (dwmac4/xgmac, dma_buf_sz = 1536,
order-0 sec_page). Jumbo frames are accepted there because GMAC_CORE_INIT
includes GMAC_CONFIG_JE. Take a 9000 byte frame whose first descriptor is
dropped by XDP:
- The second descriptor is treated as a frame start, which leaves len at
3072.
- On the last descriptor, stmmac_rx_buf1_len() returns 1536.
- stmmac_rx_buf2_len() then returns 9000 - 4608 = 4392 from this line:
return plen > len ? plen - len : 0;
Would skb_add_rx_frag() on buf->sec_page with 4392 bytes then cover more
than the 4096 byte page? Only 1320 of those bytes are valid, so stale
page_pool data would reach the stack.
Mid-frame data would also go to XDP and to the stack as a separate frame.
The follow-up commit "net: stmmac: rework stmmac_rx to support XDP rx
multi-buff" seems to address this, since it runs XDP only after the last
descriptor. This patch has Fixes: tags and could be backported on its own.
Could the rx_not_ls continuation for the STMMAC_XDP_CONSUMED case be kept
in this patch?
[ ... ]
> @@ -5983,10 +5984,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
> /* Remove FCS if needed */
> if (!fcs_stripped && pskb_trim(skb, len)) {
> dev_kfree_skb_any(skb);
> - skb = NULL;
> rx_dropped++;
> - count++;
> - continue;
> + goto next;
> }
>
> stmmac_get_rx_hwtstamp(priv, p, np, skb);
[Severity: Medium]
This isn't a bug introduced by this patch, but there is a related frame
boundary problem earlier in stmmac_rx(). Suppose napi_build_skb() fails on
the first descriptor of a frame that has rx_not_ls set:
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;
}
...
drain_data:
if (likely(status & rx_not_ls))
goto read_again;
When the code goes back to read_again, error is still 0 and skb is NULL.
Won't the next descriptor of the same frame take the !skb path again? That
would run XDP on mid-frame data and build a new skb from the remaining
fragments.
If that second allocation succeeds, a truncated frame is delivered. The
pskb_trim(skb, len) check above doesn't catch it, because pskb_trim()
returns 0 when len is larger than skb->len:
return (len < skb->len) ? __pskb_trim(skb, len) : 0;
The follow-up commit "net: stmmac: rework stmmac_rx to support XDP rx
multi-buff" removes this path. It calls stmmac_build_skb() only after the
last descriptor and sends failures to error_free_frag.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261006-stmmac-rx-mb-v4-0-efa4ca974e3d%40oss.qualcomm.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
2026-10-06 10:02 ` [PATCH net-next v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-08 22:03 ` netdev-bot+sashiko
0 siblings, 0 replies; 8+ messages in thread
From: netdev-bot+sashiko @ 2026-10-08 22:03 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 4 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · Low: 2
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 patch advertises NETDEV_XDP_ACT_RX_SG, and stmmac_rx() now
gives the XDP program a multi-buffer xdp_buff.
- [Medium] XDP programs loaded without BPF_F_XDP_HAS_FRAGS
(prog->aux->xdp_has_frags == false) now receive multi-buffer…
- [Low] The rework quietly fixes a live RX bug in the previous
stmmac_rx() for multi-descriptor frames when XDP is attached.
- [Low] The new stmmac_build_skb() computes metasize = xdp->data -
xdp->data_meta and calls skb_metadata_set(skb, metasize).
--
net: stmmac: rework stmmac_rx to support XDP rx multi-buff
This commit reworks stmmac_rx() so that all descriptors of a frame are
collected into one xdp_buff. The XDP program then runs on the full,
possibly fragmented, packet, and on XDP_PASS the skb is built from that
xdp_buff. The in-progress xdp_buff is kept in rx_q->state across NAPI
polls, the FCS is stripped with a new stmmac_xdp_shrink_tail() helper,
and NETDEV_XDP_ACT_RX_SG is advertised.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 28e9f8438f93f..742866466526e 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -5758,6 +5763,118 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
[ ... ]
> +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);
[Severity: Low]
This isn't a bug, but the skb_metadata_set() call changes behavior, and
the commit message doesn't list it.
The old XDP_PASS path (napi_build_skb() + skb_reserve() + skb_put()) never
set the skb metadata length. Metadata written with bpf_xdp_adjust_meta()
was therefore dropped. With this patch it is carried into the skb, and
TC/BPF programs can read it through data_meta.
Could this be added to the list of changes in the commit message?
[ ... ]
> @@ -5873,121 +5996,89 @@ 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;
> - }
> -
[ ... ]
> - /* 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++;
> - goto next;
> - } else if (xdp_res & (STMMAC_XDP_TX |
> - STMMAC_XDP_REDIRECT)) {
> - xdp_status |= xdp_res;
> - buf->page = NULL;
> - goto next;
> - }
> - }
> - }
[Severity: Low]
Does this rework also fix an existing RX bug for multi-descriptor frames
when XDP is attached?
In the old stmmac_rx(), XDP ran on the first buffer only. After a
CONSUMED, TX or REDIRECT verdict, the goto next above reset in_progress
and skb even when rx_not_ls was still set on that descriptor. The next
continuation descriptor was then handled as the start of a new frame,
so XDP ran on mid-frame payload.
The removed !buf2_len branch also subtracted ETH_FCS_LEN from a first
buffer that was not the last one. That dropped 4 bytes from the middle of
the frame, and setting fcs_stripped skipped the final pskb_trim().
The commit message says frames like this do reach this path with XDP
enabled, but it calls the change groundwork. Could the commit message
describe the old behavior? Could a minimal fix with a Fixes: tag also be
sent, so that stable kernels get it?
[ ... ]
> + 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;
> + }
[Severity: High]
What happens on XDP_TX now that ctx.xdp can carry frags?
stmmac_xdp_xmit_back() converts the buffer with
xdp_convert_buff_to_frame(). That keeps the frags flag but sets
xdpf->len to the linear head only. The frame is then passed to
stmmac_xdp_xmit_xdpf() with no xdp_frame_has_frags() check, and that
function programs a single descriptor:
stmmac_xdp_xmit_xdpf() {
...
dma_sync_single_for_device(priv->device, dma_addr,
xdpf->len, DMA_BIDIRECTIONAL);
...
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);
...
}
No descriptor points at the fragment pages, and they are never synced for
the device. A frame that spans several RX buffers would therefore be sent
truncated to its first buffer. Nothing leaks, because the fragments are
freed on TX completion, but the data on the wire is wrong.
The EST check in the same function only compares the linear length:
xdpf->len > priv->est->max_sdu[queue]) {
So an oversized multi-buffer frame would also get past the max_sdu limit.
GMAC_CORE_INIT in dwmac4.h always sets GMAC_CONFIG_JE. With the default
1536 byte buffers, a remote sender can produce such frames even at MTU
1500.
Should XDP_TX drop frames with frags, by returning STMMAC_XDP_CONSUMED,
until stmmac_xdp_xmit_xdpf() can chain one descriptor per fragment?
The ndo_xdp_xmit path in stmmac_xdp_xmit() has the same single-descriptor
limitation, but that code was already there before this patch.
[ ... ]
> @@ -8022,6 +8121,7 @@ static int __stmmac_dvr_probe(struct device *device,
> ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM |
> NETIF_F_RXCSUM;
> ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
> + NETDEV_XDP_ACT_RX_SG |
> NETDEV_XDP_ACT_XSK_ZEROCOPY;
[Severity: Medium]
With RX_SG advertised, what stops programs loaded without
BPF_F_XDP_HAS_FRAGS from receiving multi-buffer xdp_buffs?
stmmac_xdp_set_prog() in stmmac_xdp.c only checks the MTU:
stmmac_xdp_set_prog() {
...
if (prog && dev->mtu > ETH_DATA_LEN) {
...
}
It never checks prog->aux->xdp_has_frags. stmmac_rx() also calls
stmmac_xdp_run_prog() on ctx.xdp whether or not it has frags.
GMAC4 always enables jumbo frames, so the MTU limit does not keep out
frames longer than one RX buffer. A single-buffer program would decide
the verdict for the whole frame after seeing only data..data_end. On
XDP_PASS, payload the program never inspected goes up the stack. On
XDP_TX, the frame is truncated as described above.
Other RX_SG drivers handle this case. mvneta, bnxt, ice, mlx5 and ionic
reject such programs at attach time. aq_ring.c and fbnic_txrx.c drop such
frames at runtime. For example, aq_ring.c does:
if (xdp_buff_has_frags(xdp) && !prog->aux->xdp_has_frags)
goto out_aborted;
Should stmmac do the same for programs that are not frags-aware?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261006-stmmac-rx-mb-v4-0-efa4ca974e3d%40oss.qualcomm.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support
2026-10-06 10:02 [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
` (2 preceding siblings ...)
2026-10-06 10:05 ` [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
@ 2026-10-09 10:30 ` Lorenzo Bianconi
3 siblings, 0 replies; 8+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 10:30 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
[-- Attachment #1: Type: text/plain, Size: 2265 bytes --]
> Rework stmmac_rx() to build the xdp_buff from all the descriptors that make
> up a frame, so the XDP program runs on the whole (possibly fragmented)
> packet instead of the first buffer only. Fix possible rx_q state leaks
> when closing/opening the interface.
>
> Note that an XDP program still cannot be attached with an MTU larger than
> ETH_DATA_LEN and SPH is disabled while XDP is enabled, so an ordinary
> frame fits in a single buffer. The multi-buffer path is exercised by
> frames that do span several descriptors (for example jumbo frames the MAC
> admits through its jumbo-enable bit) and is groundwork for extending XDP
> to jumbo/SPH; it does not enable XDP jumbo support by itself.
I will fix issues reported by sashiko in v5.
Regards,
Lorenzo
>
> ---
> Changes in v4:
> - Reset in_progress flag when the packet is fully consumed.
> - Advetise NETDEV_XDP_ACT_RX_SG capability.
> - Link to v3: https://lore.kernel.org/r/20261004-stmmac-rx-mb-v3-0-50fa171af9ec@oss.qualcomm.com
>
> 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 | 390 ++++++++++++++--------
> 2 files changed, 250 insertions(+), 143 deletions(-)
> ---
> base-commit: a5e7d8e446af9803e37a3b6a4d416fb41178348f
> change-id: 20260918-stmmac-rx-mb-16469a2714ea
>
> Best regards,
> --
> Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
>
[-- 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-09 10:30 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-06 10:02 [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-06 10:02 ` [PATCH net-next v4 1/2] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
2026-10-08 22:03 ` netdev-bot+sashiko
2026-10-06 10:02 ` [PATCH net-next v4 2/2] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
2026-10-08 22:03 ` netdev-bot+sashiko
2026-10-06 10:05 ` [PATCH net-next v4 0/2] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
2026-10-06 10:09 ` Lorenzo Bianconi
2026-10-09 10:30 ` Lorenzo Bianconi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox