* [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support
@ 2026-10-09 10:26 Lorenzo Bianconi
2026-10-09 10:26 ` [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 10:26 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.
Enable XDP support with MTU bigger than ETH_DATA_LEN.
---
Changes in v5:
- Fix possible read after bound issue in patch 1/3.
- Do not allow parsing non-linear XDP buffers if the program loaded does
not support frags.
- Add patch 3/3 to remove MTU constraint for XDP prog.
- Link to v4: https://lore.kernel.org/r/20261006-stmmac-rx-mb-v4-0-efa4ca974e3d@oss.qualcomm.com
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 (2):
net: stmmac: rework stmmac_rx to support XDP rx multi-buff
net: stmmac: allow non-linear xdp_buff in XDP mode
drivers/net/ethernet/stmicro/stmmac/stmmac.h | 3 +-
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 401 ++++++++++++++--------
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 8 -
3 files changed, 253 insertions(+), 159 deletions(-)
---
base-commit: d8674294aefef02266c4d47ad10131f1bffbe534
change-id: 20260918-stmmac-rx-mb-16469a2714ea
Best regards,
--
Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry
2026-10-09 10:26 [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
@ 2026-10-09 10:26 ` Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:26 ` [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 10:26 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.
Drop the saved state both when the queue parameters are reset and when
the ring is destroyed. stmmac_reset_rx_queue() is reached from
__stmmac_open(), stmmac_xdp_open(), stmmac_resume() and
stmmac_enable_rx_queue(); without draining it there, 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. __free_dma_rx_desc_resources() releases it on teardown.
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.
Keep the running frame length when an XDP verdict ends a descriptor
early. The old loop returned to read_again for STMMAC_XDP_CONSUMED while
rx_not_ls was set, but the STMMAC_XDP_TX/STMMAC_XDP_REDIRECT path fell
through to the next iteration. Resetting the frame state there makes the
remaining descriptors of the same hardware frame get parsed as a new
frame; on cores with a second RX buffer this makes stmmac_rx_buf2_len()
report more bytes than the buffer holds and read past the page. Route
both verdicts back to read_again while rx_not_ls is set, clearing skb
first since it holds the XDP status rather than a packet.
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 | 91 +++++++++++++----------
1 file changed, 52 insertions(+), 39 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index b8b4de4c5b1a..6128e6d9504c 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;
@@ -5620,12 +5623,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);
@@ -5636,23 +5639,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;
@@ -5691,6 +5696,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);
@@ -5705,6 +5712,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;
@@ -5763,7 +5771,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;
@@ -5805,9 +5813,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;
@@ -5827,25 +5836,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;
@@ -5874,6 +5886,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)) {
@@ -5886,11 +5900,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. */
@@ -5950,23 +5963,21 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
buf->page = NULL;
rx_dropped++;
- /* Clear skb as it was set as
- * status by XDP program.
- */
- skb = NULL;
-
- if (unlikely((status & rx_not_ls)))
+ if (unlikely((status & rx_not_ls))) {
+ skb = NULL;
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;
+
+ if (unlikely((status & rx_not_ls))) {
+ skb = NULL;
+ goto read_again;
+ }
+ goto next;
}
}
}
@@ -6023,10 +6034,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);
@@ -6051,14 +6060,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;
@@ -8378,6 +8388,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.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
2026-10-09 10:26 [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-09 10:26 ` [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-09 10:26 ` Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:26 ` [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode Lorenzo Bianconi
2026-10-09 10:30 ` [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
3 siblings, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 10:26 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.
For those multi-descriptor frames the old code was buggy as well: it ran
the XDP program once per descriptor buffer, so for a multi-descriptor
frame the program was also invoked on continuation buffers holding
mid-frame payload. It also subtracted ETH_FCS_LEN from the first buffer
even when it was not the last descriptor (its second buffer empty),
dropping four bytes from the middle of the frame while marking the FCS as
already stripped and skipping the final pskb_trim(). Accumulating the
whole frame before running XDP fixes both.
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;
- drop multi-buffer frames when the attached program was not loaded with
BPF_F_XDP_HAS_FRAGS, since a single-buffer program must not be handed a
non-linear xdp_buff;
- 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;
- carry the XDP metadata written with bpf_xdp_adjust_meta() into the skb
via skb_metadata_set(), so it is no longer dropped and TC/BPF programs
can read it through data_meta;
- 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 | 346 ++++++++++++++--------
2 files changed, 222 insertions(+), 127 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h
index 9278378407e8..63020e8edd8a 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 6128e6d9504c..be2cba8fcbc6 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;
@@ -5460,8 +5465,8 @@ static int __stmmac_xdp_run_prog(struct stmmac_priv *priv,
static struct sk_buff *stmmac_xdp_run_prog(struct stmmac_priv *priv,
struct xdp_buff *xdp)
{
+ int res = STMMAC_XDP_CONSUMED;
struct bpf_prog *prog;
- int res;
prog = READ_ONCE(priv->xdp_prog);
if (!prog) {
@@ -5469,7 +5474,8 @@ static struct sk_buff *stmmac_xdp_run_prog(struct stmmac_priv *priv,
goto out;
}
- res = __stmmac_xdp_run_prog(priv, prog, xdp);
+ if (likely(!xdp_buff_has_frags(xdp) || prog->aux->xdp_has_frags))
+ res = __stmmac_xdp_run_prog(priv, prog, xdp);
out:
return ERR_PTR(-res);
}
@@ -5798,6 +5804,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
@@ -5811,21 +5929,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);
@@ -5838,23 +5955,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;
}
@@ -5891,19 +6010,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. */
@@ -5913,131 +6037,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++;
-
- if (unlikely((status & rx_not_ls))) {
- skb = NULL;
- goto read_again;
- }
- goto next;
- } else if (xdp_res & (STMMAC_XDP_TX |
- STMMAC_XDP_REDIRECT)) {
- xdp_status |= xdp_res;
- buf->page = NULL;
-
- if (unlikely((status & rx_not_ls))) {
- skb = NULL;
- goto read_again;
- }
- 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)
@@ -6064,15 +6146,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);
@@ -8068,6 +8158,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);
@@ -8388,9 +8479,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.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode
2026-10-09 10:26 [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-09 10:26 ` [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
2026-10-09 10:26 ` [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-09 10:26 ` Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:30 ` [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
3 siblings, 1 reply; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 10:26 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
Since the RX path now builds multi-buffer xdp_buffs, XDP can operate on
frames bigger than ETH_DATA_LEN (jumbo frames and scatter-gather
frames). Drop the checks that rejected attaching an XDP program when
the MTU is larger than ETH_DATA_LEN, and that refused to increase the
MTU while XDP is enabled.
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 ------
drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 8 --------
2 files changed, 14 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index be2cba8fcbc6..cbeb43011c4b 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -6351,12 +6351,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
txfifosz = priv->dma_cap.tx_fifo_size;
txfifosz /= priv->plat->tx_queues_to_use;
-
- if (stmmac_xdp_is_enabled(priv) && new_mtu > ETH_DATA_LEN) {
- netdev_dbg(priv->dev, "Jumbo frames not supported for XDP\n");
- return -EINVAL;
- }
-
new_mtu = STMMAC_ALIGN(new_mtu);
/* If condition true, FIFO is too small or MTU too large */
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
index 9af9e0cb366c..f6b3e8628a7c 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
@@ -109,14 +109,6 @@ int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
if_running = netif_running(dev);
- if (prog && dev->mtu > ETH_DATA_LEN) {
- /* For now, the driver doesn't support XDP functionality with
- * jumbo frames so we return error.
- */
- NL_SET_ERR_MSG_MOD(extack, "Jumbo frames not supported");
- return -EOPNOTSUPP;
- }
-
if (!prog)
xdp_features_clear_redirect_target(dev);
--
2.56.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support
2026-10-09 10:26 [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
` (2 preceding siblings ...)
2026-10-09 10:26 ` [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode Lorenzo Bianconi
@ 2026-10-09 10:30 ` netdev-bot+sinfo
2026-10-09 11:11 ` Lorenzo Bianconi
3 siblings, 1 reply; 9+ messages in thread
From: netdev-bot+sinfo @ 2026-10-09 10:30 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] 9+ messages in thread
* Re: [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support
2026-10-09 10:30 ` [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
@ 2026-10-09 11:11 ` Lorenzo Bianconi
0 siblings, 0 replies; 9+ messages in thread
From: Lorenzo Bianconi @ 2026-10-09 11:11 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: 1338 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 issues were 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.
these are theoretical issues 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 them 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] 9+ messages in thread
* Re: [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry
2026-10-09 10:26 ` [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
@ 2026-10-10 10:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 10:57 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: 2 (2) · Medium: 0 (1) · Low: 0
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() can still treat the middle or last
descriptor of one hardware frame as the start of a new frame.
- [High] bpf: packet injection via dropped jumbo frames in stmmac_rx_zc
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but it sits in the
stmmac_rx_zc() continuation logic this patch changes, and it is still…
- [High] This problem wasn't introduced by this patch, but this patch's
resume handling relies on it and the commit message describes the…
- [Medium] In stmmac_rx() at this commit, when napi_build_skb() fails on
a descriptor that is not the last one, the code increments rx_dropped…
--
net: stmmac: take ownership of saved RX state at poll entry
This patch makes stmmac_rx() and stmmac_rx_zc() take the saved
partial-frame state at poll entry and save it again only if the frame is
still incomplete. It also drops the saved state in stmmac_reset_rx_queue()
and __free_dma_rx_desc_resources(). For non-PASS XDP verdicts on non-last
descriptors, both verdicts now go back to read_again and keep the running
frame length.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index b8b4de4c5b1a7..6128e6d9504c8 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -5691,6 +5696,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);
> @@ -5705,6 +5712,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: High]
This is a pre-existing issue, but this patch reworks how
stmmac_rx_zc() carries error and len across descriptors and polls.
Should the dropped-head case be covered here too?
When a frame spans more than one XSK descriptor, the 1:1 mapping
check throws away the non-last descriptors:
/* 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;
}
That path never sets error. When the loop reaches the last descriptor
of the same frame, error is still 0, so both error checks are
skipped. The tail buffer then goes to __stmmac_xdp_run_prog() as if
it were a complete frame.
On XDP_PASS, stmmac_dispatch_skb_zc() builds an skb from it and
passes it up the stack. On XDP_REDIRECT, the AF_XDP socket receives
it.
The tail starts in the middle of the original payload, at an offset
the sender can predict from the RX buffer size. Can an on-link peer
send a jumbo frame (GMAC4 sets GMAC_CONFIG_JE) whose payload holds a
complete Ethernet/IP header at that offset?
The MAC address, VLAN and other hardware filters only check the outer
frame. The kernel would instead receive the crafted inner frame.
With this patch the same state can also cross a poll boundary. If the
budget runs out right after the head is dropped, in_progress is true
and error 0 is saved. The next poll then treats the tail the same
way.
Would setting error = 1 before the goto be enough? The error branches
only free buf->xdp and bump dirty when the current descriptor itself
reports discard_frame. Wouldn't the remaining descriptors of a
dropped frame then keep their XSK buffers and not be counted in
dirty? It looks like the drop path also has to release each
continuation buffer.
Later in the series stmmac_rx() is reworked to collect all
descriptors of a frame. stmmac_rx_zc() keeps this drop path, so the
problem is still present at the end of the series.
[Severity: High]
This is a pre-existing issue, but this patch reworks the continuation
state in stmmac_rx_zc(). Is the multi-descriptor case handled correctly
right after this point?
Non-last descriptors are dropped without setting error and without adding
their bytes to len:
/* 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;
}
So for an error-free frame that spans several XSK descriptors, the
continuation state is always error 0 and len 0. The last descriptor then
reaches:
buf1_len = stmmac_rx_buf1_len(priv, p, status, len);
...
buf->xdp->data_end = buf->xdp->data + buf1_len;
With len 0 and SPH disabled, stmmac_rx_buf1_len() returns
min(dma_buf_sz, plen). Here plen is the total frame length, not the number
of bytes in the last buffer.
Doesn't that make data_end cover stale UMEM bytes past the real tail? If
the XSK rx frame size is smaller than dma_buf_sz, can it even point past
the chunk?
On XDP_PASS, stmmac_dispatch_skb_zc() copies this into an skb. When coe is
set and the ethertype is IP, it sets CHECKSUM_UNNECESSARY without checking
csum_none. On REDIRECT, the AF_XDP socket gets the same frame.
The head of the frame is dropped, and its tail plus stale data is
delivered as a standalone frame. An on-link peer can trigger this by
sending a frame longer than the XSK rx frame size, because GMAC4 always
sets GMAC_CONFIG_JE. The code is unchanged later in the series.
[ ... ]
> @@ -5950,23 +5963,21 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
> buf->page = NULL;
> rx_dropped++;
>
> - /* Clear skb as it was set as
> - * status by XDP program.
> - */
> - skb = NULL;
> -
> - if (unlikely((status & rx_not_ls)))
> + if (unlikely((status & rx_not_ls))) {
> + skb = NULL;
> 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;
> +
> + if (unlikely((status & rx_not_ls))) {
> + skb = NULL;
> + goto read_again;
> + }
[Severity: High]
After jumping to read_again with skb == NULL, could the next descriptor of
the same hardware frame still be parsed as a new frame?
In stmmac_rx(), skb == NULL is the only marker for the start of a frame.
The next descriptor therefore enters the frame-start block again:
if (!skb) {
...
/* Each frame starts here: reset the FCS handling */
fcs_stripped = false;
...
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);
Because len is now kept, stmmac_rx_buf1_len() returns
min(dma_buf_sz, plen - len) for the last descriptor, and that can be 0 to
3 bytes. For example, on GMAC4 take a 3074-byte frame whose first
descriptor holds 3072 bytes in buf1 + buf2. The last descriptor then
holds 2 bytes.
Can buf1_len -= ETH_FCS_LEN then wrap to 0xFFFFFFFE, so that
xdp_prepare_buff() sets data_end = data - 2?
Suppose the XDP program returns XDP_PASS for that continuation. buf1_len
is recomputed as data_end - data and passed to skb_put(skb, buf1_len).
The tail wraps to below end, so skb_over_panic() does not fire. An skb
with len close to 4GB then goes to eth_type_trans() and the stack, with
no FCS trim because fcs_stripped is set.
If the continuation holds 4 or more bytes, the tail of a dropped or
redirected frame is instead shown to XDP as a standalone Ethernet frame,
and delivered on PASS.
The XDP_CONSUMED branch above already worked this way, and this change
puts XDP_TX and XDP_REDIRECT on the same path. The commit message says:
Resetting the frame state there makes the
remaining descriptors of the same hardware frame get parsed as a new
frame
Isn't that still the case after this patch?
Later in the series, "net: stmmac: rework stmmac_rx to support XDP rx
multi-buff" collects the whole frame into an xdp_buff, runs XDP only at
the last descriptor, and strips the FCS with stmmac_xdp_shrink_tail().
That resolves this. Since this patch carries Fixes: tags, could it be
backported without that rework?
> + goto next;
> }
> }
> }
[ ... ]
> @@ -6023,10 +6034,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;
> }
>
[Severity: Medium]
This isn't a bug introduced by this patch, but what happens here if
napi_build_skb() failed on an earlier non-last descriptor of the same
frame?
That path leaves skb NULL and does not set error:
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;
}
Because rx_not_ls is set, drain_data goes back to read_again. The next
descriptor then builds a new skb from the remaining mid-frame bytes.
This also works across a budget boundary. The saved state is in_progress
with skb NULL, error 0 and a nonzero len.
At the last descriptor, pskb_trim(skb, len) gets the full frame length.
That is larger than skb->len, so it returns 0 without trimming.
Doesn't that hand napi_gro_receive() a truncated frame that starts with
mid-frame payload and still carries the FCS? Doesn't it also over-count
rx_bytes?
This path goes away later in the series with "net: stmmac: rework
stmmac_rx to support XDP rx multi-buff". There, stmmac_build_skb() is
only called at the last descriptor, and a failure goes to error_free_frag.
[ ... ]
> @@ -8378,6 +8388,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: High]
This is a pre-existing issue, but the commit message calls the resumed
ring freshly initialized:
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.
Is the ring actually rebuilt on the stmmac_resume() path?
Suppose stmmac_rx_refill() hits a GFP_ATOMIC page_pool allocation failure
in the last poll before suspend. It breaks out and leaves the entries
between dirty_rx and cur_rx without pages:
buf->page = page_pool_alloc_pages(rx_q->page_pool, gfp);
if (!buf->page)
break;
On resume the sequence is then:
stmmac_resume()
stmmac_reset_queues_param()
stmmac_reset_rx_queue()
frees state.skb, returning its pages to the page pool
zeroes cur_rx and dirty_rx
stmmac_clear_descriptors()
init_rx_desc on every descriptor
stmmac_hw_setup()
stmmac_init_dma_engine()
RX tail pointer set to buf_alloc_num
On GMAC4, dwmac4_rd_init_rx_desc() only sets OWN | BUF1V | IOC in des3.
It does not restore des0/des1, which write-back overwrote with status
data. On enhanced/normal descriptors, des2 still holds the old page's DMA
address.
Could the DMA then write to an address built from write-back status bits,
or into a page that was just released to the page pool?
When stmmac_rx() later reaches such an entry, buf->page is NULL. The call
napi_build_skb(page_address(buf->page), ...) then uses a bogus address.
Before this patch, the stale skb was resumed and then freed on its first
completion while the descriptor still referenced its page. So the
page-release part already existed.
The same structure is still there at the end of the series, where
stmmac_reset_rx_queue() calls stmmac_xdp_put_buff() on the saved
xdp_buff.
Does the resume path need to rebuild the missing RX buffers and
descriptor addresses, and handle allocation failure, before it restarts
RX DMA?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-stmmac-rx-mb-v5-0-c38fa4eaa138%40oss.qualcomm.com
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff
2026-10-09 10:26 ` [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
@ 2026-10-10 10:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 10:57 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: 2 (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] net: stmmac: OOB read in stmmac_rx_zc() for multi-buffer frames
- [Medium] Real RX bug fixes are bundled into a net-next feature rework
that has no Fixes: tag.
- [Medium] The patch now attaches XDP metadata to the skb in
stmmac_build_skb() (skb_metadata_set(skb, metasize)).
- [Low] The new error branch in stmmac_rx() frees both buf->page and
buf->sec_page for every descriptor seen while `error` is set.
Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but the AF_XDP
zero-copy XDP_PASS path still loses metadata contents.
- [Medium] This problem wasn't introduced by this patch, but RX
descriptor reads have no DMA read ordering.
--
net: stmmac: rework stmmac_rx to support XDP rx multi-buff
This reworks stmmac_rx() so all descriptors of a frame are collected into
an xdp_buff before the XDP program runs. The skb is then built from that
xdp_buff with napi_build_skb() and xdp_update_skb_frags_info(). The
in-progress xdp_buff is kept in rx_q->state across NAPI polls,
NETDEV_XDP_ACT_RX_SG is advertised, XDP metadata is carried into the skb,
and the FCS is stripped by a new stmmac_xdp_shrink_tail() helper.
> For those multi-descriptor frames the old code was buggy as well: it ran
> the XDP program once per descriptor buffer, so for a multi-descriptor
> frame the program was also invoked on continuation buffers holding
> mid-frame payload. It also subtracted ETH_FCS_LEN from the first buffer
> even when it was not the last descriptor (its second buffer empty),
> dropping four bytes from the middle of the frame while marking the FCS as
> already stripped and skipping the final pskb_trim(). Accumulating the
> whole frame before running XDP fixes both.
[ ... ]
> Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
[Severity: Medium]
The paragraph above describes two existing RX bugs in stmmac_rx():
- the XDP program runs once per descriptor buffer.
- ETH_FCS_LEN is subtracted from buffer1 of a non-last descriptor
whenever stmmac_xdp_is_enabled(priv) && !buf2_len.
The old path also lost XDP metadata on XDP_PASS.
xdp_prepare_buff() was called with meta_valid=true, but
skb_metadata_set() was never called.
The FCS strip logic came from 88095728511e ("net: stmmac: fix rx
Scatter-Gather support"). That commit carries Fixes: 88ebe2cf7f3f, so it
is headed for stable. The per-descriptor XDP invocation goes back to
5fabb01207a2 and be8b38a722e6 from 2021.
GMAC4 sets GMAC_CONFIG_JE unconditionally. So even at MTU 1500 with XDP
loaded, a remote peer can send a frame that spans several descriptors.
The mid-frame FCS strip additionally needs stmmac_rx_check_buf2_cap() to
be false, which is the case on legacy cores.
Here these fixes are only side effects of a patch that also adds
NETDEV_XDP_ACT_RX_SG and multi-buffer support. The only trailer is
Signed-off-by. The first patch in this series ("net: stmmac: take
ownership of saved RX state at poll entry") does carry Fixes: tags.
Could the fixes go in a separate patch with Fixes: tags, so they can be
picked up for net and stable? As it stands, stable trees that take
88095728511e will keep the per-descriptor XDP run, the mid-frame FCS
strip and the dropped metadata.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index 6128e6d9504c8..be2cba8fcbc6d 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
[ ... ]
> @@ -5798,6 +5804,118 @@ static int stmmac_rx_zc(struct stmmac_priv *priv, int limit, u32 queue)
> return failure ? limit : (int)count;
> }
>
[Severity: High]
This isn't a bug introduced by this patch. The commit message calls
this work groundwork for XDP on frames that span several descriptors,
though, and the AF_XDP zero-copy receive path still mishandles such
frames. stmmac_rx_zc() releases every non-last descriptor of a frame
without adding its length to len:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_rx_zc() {
...
/* 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;
}
...
buf1_len = stmmac_rx_buf1_len(priv, p, status, len);
...
}
When the last descriptor arrives, len is still 0, or whatever was
saved at the previous poll boundary. stmmac_rx_buf1_len() therefore
computes min(dma_buf_sz, plen - len) from the full frame length, not
from the bytes left for this descriptor. For a frame longer than
dma_buf_sz that gives dma_buf_sz. The DMA only wrote the last
plen - (n - 1) * dma_buf_sz bytes into this XSK buffer.
buf->xdp->data_end is then set from that value. The XDP program, an
AF_XDP socket on XDP_REDIRECT, and stmmac_construct_skb_zc() on
XDP_PASS all receive the tail of the frame plus whatever was left in
the UMEM chunk, presented as a complete packet. The head of the frame
is silently lost, and none of this is counted in rx_dropped.
As noted above, GMAC4 sets GMAC_CONFIG_JE unconditionally. A remote
peer can therefore already send such frames while an XSK pool is
bound, whatever the MTU check in stmmac_xdp_set_prog() allows.
Fixing only the len accounting would still hand userspace the tail of
the frame as if it were a whole packet. Should stmmac_rx_zc() instead
mark the frame as errored on the first rx_not_ls descriptor? The last
descriptor would then be released too, and the whole frame accounted
as dropped.
[ ... ]
> +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: Medium]
This isn't a bug introduced by this patch, but the AF_XDP zero-copy
XDP_PASS path still loses the metadata contents.
stmmac_construct_skb_zc() copies only the packet bytes into the new skb:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_construct_skb_zc() {
...
skb_reserve(skb, xdp->data - xdp->data_hard_start);
memcpy(__skb_put(skb, datasize), xdp->data, datasize);
if (metasize)
skb_metadata_set(skb, metasize);
...
}
skb_metadata_set() only records meta_len. The bytes in
[xdp->data_meta, xdp->data) are never copied.
Take a program that calls bpf_xdp_adjust_meta() and returns XDP_PASS,
on the path stmmac_rx_zc()->stmmac_dispatch_skb_zc()->
stmmac_construct_skb_zc(). Would a TC/BPF program then read
uninitialized skb headroom through data_meta?
Other drivers such as i40e copy from xdp->data_meta instead. The commit
message says metadata is now carried into the skb. Should the ZC path
get the same treatment?
> +
> + 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;
> +}
[ ... ]
> @@ -5891,19 +6010,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++;
> }
[Severity: Medium]
This is a pre-existing issue, but is a DMA read barrier missing between
the ownership check and the reads of the other descriptor words?
dwmac4_wrback_get_rx_status() loads des1 and des2 before it checks
RDES3_OWN:
drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c:dwmac4_wrback_get_rx_status() {
u32 rdes1 = le32_to_cpu(p->des1);
u32 rdes2 = le32_to_cpu(p->des2);
u32 rdes3 = le32_to_cpu(p->des3);
...
if (unlikely(rdes3 & RDES3_OWN))
return dma_own;
...
}
stmmac_rx() has no dma_rmb() after its dma_own check either. On weakly
ordered CPUs such as arm64, could des1/des2 be read before the DMA
writeback and des3 after it?
If so, a stale rdes1 could be missing RDES1_IP_PAYLOAD_ERROR or
RDES1_IP_HDR_ERROR, and a frame with a bad checksum would be marked
CHECKSUM_UNNECESSARY. Stale rdes2 bits could also cause spurious
filter-fail drops.
>
> - 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;
> + }
[Severity: Low]
This frees buf->sec_page for every descriptor seen while error is set.
On GMAC4, dwmac4_wrback_get_rx_status() returns discard_frame for RX
timestamp context descriptors:
if (unlikely(rdes3 & RDES3_CONTEXT_DESCRIPTOR))
return discard_frame;
The DMA writes only descriptor words for those slots, so their pages are
still valid. With hwts_rx_en, every timestamped frame is followed by such
a slot.
Before this patch, discard_frame released only buf->page and sec_page
stayed attached. Continuation descriptors of an errored frame also kept
their pages.
Is it intended that each context slot now hands two DMA-mapped pages
back to the page_pool? stmmac_rx_refill() then has to allocate a new
sec_page with GFP_ATOMIC for each one. It stops at the first allocation
failure, so this adds one more way for ring re-arming to stall under
memory pressure.
> +
> + if (status & rx_not_ls)
> + goto read_again;
> +
> + goto error_free_frag;
> }
[ ... ]
> @@ -5913,131 +6037,89 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue)
[ ... ]
> + 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)
[Severity: Medium]
stmmac_build_skb() now calls skb_metadata_set(skb, metasize). That
assumes the metadata sits directly before the MAC header, because
skb_metadata_end() returns skb_mac_header(skb).
When hw_vlan_en is false, stmmac_rx_vlan() pops the tag by moving the
MAC addresses forward. This happens on dwmac100/dwmac1000 cores with
NETIF_F_HW_VLAN_CTAG_RX set, for example:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_rx_vlan() {
...
memmove(skb->data + VLAN_HLEN, veth, ETH_ALEN * 2);
skb_pull(skb, VLAN_HLEN);
...
}
The metadata stays where it is, and eth_type_trans() then sets the MAC
header VLAN_HLEN bytes further in. For VLAN-tagged frames, would TC/BPF
programs reading data_meta lose the first 4 metadata bytes and see 4
bytes of the original destination MAC in their place?
The core helper skb_reorder_vlan_header() moves the metadata along with
the header:
net/core/skbuff.c:skb_reorder_vlan_header() {
...
meta_len = skb_metadata_len(skb);
if (meta_len) {
meta = skb_metadata_end(skb) - meta_len;
memmove(meta + VLAN_HLEN, meta, meta_len);
}
...
}
Before this patch the metadata was dropped (meta_len 0) rather than
misaligned.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-stmmac-rx-mb-v5-0-c38fa4eaa138%40oss.qualcomm.com
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode
2026-10-09 10:26 ` [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode Lorenzo Bianconi
@ 2026-10-10 10:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-10 10:57 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: 1 · High: 2 (2) · Medium: 2 · Low: 0
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).
- [Critical] Out-of-bounds read in the AF_XDP zero-copy RX path,
triggerable by a remote peer.
- [High] XDP_TX and ndo_xdp_xmit frames longer than buffer1 of a legacy
(normal or enhanced) TX descriptor are programmed wrongly.
- [High] On XDP_TX of the head buffer, stmmac_xdp_xmit_xdpf() computes
the DMA address as `page_pool_get_dma_addr(virt_to_page(xdpf->data)) +…
- [Medium] On bugged_jumbo cores (snps,dwmac-3.40a, snps,dwmac-3.610,
snps,dwmac-3.710 per stmmac_probe_config_dt()), TX checksum insertion…
- [Medium] net/stmmac: silent drop of jumbo frames with non-SG XDP
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch
makes it much easier to hit.
- [High] This problem wasn't introduced by this patch, but the patch
exposes it to XDP and to XDP_TX/redirect.
--
net: stmmac: allow non-linear xdp_buff in XDP mode
This commit removes the check that rejected attaching an XDP program
when the MTU is above ETH_DATA_LEN. It also removes the check that
refused to raise the MTU above ETH_DATA_LEN while XDP is enabled. The
reason given is that the RX path now builds multi-buffer xdp_buffs.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> index be2cba8fcbc6d..cbeb43011c4bd 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
> @@ -6351,12 +6351,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu)
> txfifosz = priv->dma_cap.tx_fifo_size;
>
> txfifosz /= priv->plat->tx_queues_to_use;
> -
> - if (stmmac_xdp_is_enabled(priv) && new_mtu > ETH_DATA_LEN) {
> - netdev_dbg(priv->dev, "Jumbo frames not supported for XDP\n");
> - return -EINVAL;
> - }
> -
[Severity: Critical]
Can this lead to an out-of-bounds read in the AF_XDP zero-copy RX path?
ZC RX only runs while an XDP program is attached. Before this patch,
that kept dma_conf.dma_buf_sz at 1536.
With XDP attached and, say, MTU 9000, stmmac_setup_dma_desc() now picks
BUF_SIZE_16KiB. For a ZC queue, stmmac_set_queue_rx_buf_size() programs
the hardware buffer size from the pool:
buf_size = xsk_pool_get_rx_frame_size(rx_q->xsk_pool);
So a jumbo frame gets split across several descriptors. stmmac_rx_zc()
still assumes a 1:1 mapping. It drops the non-last buffers without
adding to 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;
}
When the last descriptor arrives, len is still 0, so stmmac_rx_buf1_len()
returns about the full frame length:
return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen - len);
stmmac_rx_zc() then sets:
buf->xdp->data_end = buf->xdp->data + buf1_len;
This puts data_end about 9000 bytes past data. The XSK chunk only holds
xsk_pool_get_rx_frame_size() bytes.
Three consumers would then read past the chunk:
- the BPF program
- the memcpy() in stmmac_construct_skb_zc() on XDP_PASS
- xdp_convert_zc_to_xdp_frame() on XDP_TX
They would read neighbouring UMEM chunks, or fault past the end of the
UMEM mapping in NAPI context.
Both orders seem to reach this state:
- bind the pool at MTU 1500, then raise the MTU here
- raise the MTU first, then attach the program
The mtu + ETH_PAD_LEN <= frame_size check in xp_assign_dev() only runs
at bind time. 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".
The commit message says "XDP can operate on frames bigger than
ETH_DATA_LEN". That doesn't seem to hold for the ZC path, and the driver
still advertises NETDEV_XDP_ACT_XSK_ZEROCOPY.
Should the MTU limit stay in place while an XSK pool is bound, or be
checked against xsk_pool_get_rx_frame_size()?
[Severity: High]
This is a pre-existing issue, but this patch exposes it to XDP programs
and to XDP_TX/redirect. In chain mode (forced for dwmac-sun8i, or with
chain_mode=1), does the RX length accounting match what the hardware
writes?
chain_mode_ops has no set_16kib_bfsize, so stmmac_set_bfsize() picks
dma_buf_sz = BUF_SIZE_16KiB for len > BUF_SIZE_8KiB. The descriptor
init caps hardware buffer1:
enh_desc_init_rx_desc():
bfsize1 = min(bfsize, BUF_SIZE_8KiB);
ndesc_init_rx_desc() caps it at 2047. Chain mode has no buffer2,
because des3 is the next-descriptor pointer.
stmmac_rx_buf1_len() still returns the full software size for every
non-last descriptor:
if (status & rx_not_ls)
return priv->dma_conf.dma_buf_sz;
For the last descriptor, the unsigned plen - len wraps (for example
9022 - 16368), so min_t() returns dma_buf_sz again.
Wouldn't a 9022-byte frame then become a ~32 KiB xdp_buff that is
mostly stale data from recycled page_pool pages? The XDP program would
see that data, and XDP_TX would send it back out. The non-XDP skb path
builds from the same xdp_buff and looks affected in the same way.
> new_mtu = STMMAC_ALIGN(new_mtu);
>
> /* If condition true, FIFO is too small or MTU too large */
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
> index 9af9e0cb366c3..f6b3e8628a7cf 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c
> @@ -109,14 +109,6 @@ int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog,
>
> if_running = netif_running(dev);
>
> - if (prog && dev->mtu > ETH_DATA_LEN) {
> - /* For now, the driver doesn't support XDP functionality with
> - * jumbo frames so we return error.
> - */
> - NL_SET_ERR_MSG_MOD(extack, "Jumbo frames not supported");
> - return -EOPNOTSUPP;
> - }
> -
[Severity: Medium]
Removing both checks entirely means a single-buffer XDP program can now
be combined with a jumbo MTU. Neither stmmac_xdp_set_prog() nor
stmmac_change_mtu() looks at prog->aux->xdp_has_frags. That allows the
setup in either order:
- attach a program without frags support at MTU 9000
- raise the MTU while such a program is attached
After that, any frame that spans more than one RX buffer arrives as an
xdp_buff with frags. stmmac_xdp_run_prog() then skips the program:
if (likely(!xdp_buff_has_frags(xdp) || prog->aux->xdp_has_frags))
res = __stmmac_xdp_run_prog(priv, prog, xdp);
res stays at STMMAC_XDP_CONSUMED, so the frame is dropped. All traffic
larger than one RX buffer is blackholed. The user got no error when
setting this up that would explain why.
Other multi-buffer capable drivers reject this combination up front.
mvneta, for example, refuses to attach a program without xdp_has_frags
when the MTU exceeds its single-buffer size. It also refuses the
matching MTU change while such a program is loaded.
Should these two checks be kept, but applied only when the program
lacks xdp_has_frags? If so, is ETH_DATA_LEN still the right threshold,
or should it be the largest frame that fits in one RX buffer?
[Severity: High]
With XDP now allowed at jumbo MTU, can stmmac_xdp_xmit_xdpf() program
legacy (normal or enhanced) TX descriptors with buffers longer than
buffer1?
stmmac_xdp_xmit_xdpf() writes one descriptor per buffer. It sets only
des2 and passes the full length:
stmmac_set_desc_addr(priv, desc, dma_addr);
stmmac_prepare_tx_desc(priv, desc, !i, len, csum,
priv->descriptor_mode, !!i, last_frame,
xdp_get_frame_len(xdpf));
In ring mode, enh_set_tx_desc_len_on_ring() moves the excess into the
buffer2 size field when len > 4096:
if (unlikely(len > buffer1_max_length)) {
p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER2_SIZE_MASK,
len - buffer1_max_length) |
norm_set_tx_desc_len_on_ring() does the same above 2047. The DMA engine
then reads buffer2 from des3, which the XDP path never sets. It is
usually 0, or a stale value.
The skb path handles this in stmmac_xmit() through
stmmac_is_jumbo_frm(), with jumbo_frm() in ring_mode.c doing:
desc->des3 = cpu_to_le32(lower_32_bits(des2) + BUF_SIZE_4KiB);
In chain mode, des3 is the next-descriptor pointer and the length is
masked with ETDES1_BUFFER1_SIZE_MASK. A 9018-byte frame would go out
truncated.
Should stmmac_xdp_xmit_xdpf() split oversized buffers the same way the
skb path does?
[Severity: High]
Does stmmac_xdp_xmit_xdpf() still compute the right DMA address for the
head buffer, now that RX pages can be compound?
page = frag ? skb_frag_page(frag)
: virt_to_page(xdpf->data);
dma_addr = page_pool_get_dma_addr(page);
The offset added afterwards (sizeof(*xdpf) + xdpf->headroom) is relative
to the head page. virt_to_page() returns the page that contains
xdpf->data.
At jumbo MTU, __alloc_dma_rx_desc_resources() now ends up with
pp_params.order = order_base_2(num_pages) > 0 while XDP is enabled.
Suppose a program moves data forward with bpf_xdp_adjust_head() by more
than about a page and returns XDP_TX. Then xdpf->data is in a tail page,
and the tail page doesn't hold the page_pool DMA address. Wouldn't
dma_sync_single_for_device() and the TX descriptor get a bogus bus
address?
stmmac_xdp_put_buff() in the same file already uses
virt_to_head_page(xdp->data) for the same buffer.
[Severity: Medium]
The bugged_jumbo cores are snps,dwmac-3.40a, snps,dwmac-3.610 and
snps,dwmac-3.710. On these, stmmac_fix_features() disables TX checksum
insertion at jumbo MTUs:
if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN))
features &= ~NETIF_F_CSUM_MASK;
stmmac_xdp_xmit_xdpf() makes its own decision:
bool csum = !priv->plat->tx_queues_cfg[queue].coe_unsupported;
enh_desc_prepare_tx_desc() then sets TX_CIC_FULL.
Now that XDP is allowed at MTU > ETH_DATA_LEN, wouldn't XDP_TX and
redirected jumbo frames on these cores go out with full checksum
insertion enabled? That is what the bugged_jumbo workaround is meant to
avoid.
[Severity: High]
This isn't a bug introduced by this patch, but the patch makes it much
easier to hit. Further down in stmmac_xdp_set_prog(), the result of the
reopen is ignored and the function returns 0:
if (if_running && need_update)
stmmac_xdp_open(dev);
If stmmac_xdp_open() fails, its error paths have already freed the DMA
resources:
init_error:
free_dma_desc_resources(priv, &priv->dma_conf);
The NAPIs that stmmac_xdp_release() disabled stay disabled, and the
device stays IFF_UP.
The next ndo_stop or XDP toggle reaches __stmmac_disable_all_queues():
if (queue < rx_queues_cnt)
napi_disable(&ch->rx_napi);
napi_disable() on a NAPI that is already disabled loops forever in
napi_disable_locked() while holding RTNL.
Any path that gets past that calls free_dma_desc_resources() again.
__free_dma_rx_desc_resources() doesn't clear dma_rx, buf_pool or
page_pool, so that would be a double free.
With this patch, attaching or detaching XDP at jumbo MTU makes the
reopen fill every RX ring entry with order-2/3 page_pool pages. These
are allocated with GFP_ATOMIC and can fail on a fragmented system.
Should the stmmac_xdp_open() error be propagated, with the device state
cleaned up?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261009-stmmac-rx-mb-v5-0-c38fa4eaa138%40oss.qualcomm.com
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-10 10:57 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09 10:26 [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support Lorenzo Bianconi
2026-10-09 10:26 ` [PATCH net-next v5 1/3] net: stmmac: take ownership of saved RX state at poll entry Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:26 ` [PATCH net-next v5 2/3] net: stmmac: rework stmmac_rx to support XDP rx multi-buff Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:26 ` [PATCH net-next v5 3/3] net: stmmac: allow non-linear xdp_buff in XDP mode Lorenzo Bianconi
2026-10-10 10:57 ` netdev-bot+sashiko
2026-10-09 10:30 ` [PATCH net-next v5 0/3] net: stmmac: introduce XDP rx multi-buff support netdev-bot+sinfo
2026-10-09 11:11 ` Lorenzo Bianconi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox