* [RFC 4/9] io_uring/zcrx: split frag handling loop
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
A preparation patch splitting the frag array handling loop into two,
where first we skip frags below the requested offset. It makes further
changes more readable.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 816a169b848e..162e67287916 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -1877,23 +1877,29 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
const skb_frag_t *frag = &shi->frags[i];
unsigned frag_end = start + skb_frag_size(frag);
+ if (offset < frag_end)
+ break;
+ start = frag_end;
+ }
+
+ for (; i < shi->nr_frags; i++) {
+ const skb_frag_t *frag = &shi->frags[i];
+ unsigned frag_end = start + skb_frag_size(frag);
+ unsigned copy = min(frag_end - offset, len);
+ unsigned frag_off = offset - start;
+
if (WARN_ON(start > offset + len))
return -EFAULT;
+ start = frag_end;
- if (offset < frag_end) {
- unsigned copy = min(frag_end - offset, len);
- unsigned frag_off = offset - start;
-
- ret = io_zcrx_recv_frag(req, ifq, frag, frag_off, copy);
- if (ret < 0)
- goto out;
+ ret = io_zcrx_recv_frag(req, ifq, frag, frag_off, copy);
+ if (ret < 0)
+ goto out;
- offset += ret;
- len -= ret;
- if (len == 0 || ret != copy)
- goto out;
- }
- start = frag_end;
+ offset += ret;
+ len -= ret;
+ if (len == 0 || ret != copy)
+ goto out;
}
skb_walk_frags(skb, frag_iter) {
--
2.54.0
^ permalink raw reply related
* [RFC 5/9] io_uring/zcrx: split io_zcrx_recv_frag()
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
In preparation for having more elaborate reference counting for niovs,
split normal pages handling (copy path) out of io_zcrx_recv_frag() and
inline it into callers. Also move refcounting out of it.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 44 +++++++++++++++++++++++---------------------
1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 162e67287916..80aa68ab9968 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -1800,30 +1800,16 @@ static int io_zcrx_copy_frag(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
return ret;
}
-static int io_zcrx_recv_frag(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
- const skb_frag_t *frag, int off, int len)
+static int zcrx_recv_niov(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
+ struct net_iov *niov, int off, int len)
{
- struct net_iov *niov;
- struct page_pool *pp;
-
- if (unlikely(!skb_frag_is_net_iov(frag)))
- return io_zcrx_copy_frag(req, ifq, frag, off, len);
-
- niov = netmem_to_net_iov(frag->netmem);
- pp = niov->desc.pp;
+ struct page_pool *pp = niov->desc.pp;
if (!pp || pp->mp_ops != &io_uring_pp_zc_ops || io_pp_to_ifq(pp) != ifq)
return -EFAULT;
- if (!io_zcrx_queue_cqe(req, niov, ifq, off + skb_frag_off(frag), len))
+ if (!io_zcrx_queue_cqe(req, niov, ifq, off, len))
return -ENOSPC;
-
- /*
- * Prevent it from being recycled while user is accessing it.
- * It has to be done before grabbing a user reference.
- */
- page_pool_ref_netmem(net_iov_to_netmem(niov));
- io_zcrx_get_niov_uref(niov);
return len;
}
@@ -1892,9 +1878,25 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
return -EFAULT;
start = frag_end;
- ret = io_zcrx_recv_frag(req, ifq, frag, frag_off, copy);
- if (ret < 0)
- goto out;
+ if (unlikely(!skb_frag_is_net_iov(frag))) {
+ ret = io_zcrx_copy_frag(req, ifq, frag, frag_off, copy);
+ if (ret < 0)
+ goto out;
+ } else {
+ struct net_iov *niov = netmem_to_net_iov(frag->netmem);
+
+ ret = zcrx_recv_niov(req, ifq, niov,
+ frag_off + skb_frag_off(frag),
+ copy);
+ if (ret < 0)
+ goto out;
+ /*
+ * Prevent it from being recycled while user is accessing it.
+ * It has to be done before grabbing a user reference.
+ */
+ page_pool_ref_netmem(net_iov_to_netmem(niov));
+ io_zcrx_get_niov_uref(niov);
+ }
offset += ret;
len -= ret;
--
2.54.0
^ permalink raw reply related
* [RFC 6/9] io_uring/zcrx: implement skb stealing
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
One of major hotspots for zcrx is handing buffers to the user space and
getting them back from the refill queue. For each niov we keep an atomic
reference counter, which is incremented from the syscall path when we
give the buffer to the user space, and decremented from NAPI when
processing the refill queue on page pool allocation. When user space and
NAPI run on different CPUs it causes cache bouncing.
Instead of bumping the ref counter for each frag on receive, try to steal
the entire skb and send it to NAPI for zcrx to process it, so that put
and gets happen on the same CPU. We trade a bunch of atomics with cache
bouncing with a single ptr_ring produce / consume. It achieves same goals
and replaces skb_attempt_defer_free() but also improves locality for zcrx
specific accounting.
It still uses atomics for "user" counting in this patch, but now it's
accessed by a single CPU only when the optimisation works. It also
improves temporal locality as well as we delay grabbing reference,
however we need to make sure that skbs are processed before the
corresponding RQEs.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 112 +++++++++++++++++++++++++++++++++++++++++-------
io_uring/zcrx.h | 3 ++
2 files changed, 99 insertions(+), 16 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 80aa68ab9968..80a26b5798d3 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -40,6 +40,8 @@
#define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING)
+static void zcrx_release_skbs(struct io_zcrx_ifq *ifq);
+
static inline u64 zcrx_area_id_to_token(u32 area_id)
{
return (u64)area_id << IORING_ZCRX_AREA_SHIFT;
@@ -677,6 +679,9 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq)
return;
if (WARN_ON_ONCE(ifq->master_ctx))
return;
+ if (WARN_ON_ONCE(!__ptr_ring_empty(&ifq->skb_ring)))
+ return;
+
for (i = 0; i < ifq->nr_areas; i++)
io_zcrx_free_area(ifq, ifq->areas[i]);
@@ -685,6 +690,7 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq)
if (ifq->dev)
put_device(ifq->dev);
+ ptr_ring_cleanup(&ifq->skb_ring, NULL);
io_free_rbuf_ring(ifq);
free_uid(ifq->user);
mutex_destroy(&ifq->pp_lock);
@@ -755,6 +761,9 @@ static void io_zcrx_scrub(struct io_zcrx_ifq *ifq)
{
int i;
+ scoped_guard(spinlock_bh, &ifq->rq.lock)
+ zcrx_release_skbs(ifq);
+
guard(mutex)(&ifq->pp_lock);
for (i = 0; i < ifq->nr_areas; i++)
io_zcrx_scrub_area(ifq, ifq->areas[i]);
@@ -1039,6 +1048,9 @@ int io_register_zcrx(struct io_ring_ctx *ctx,
ifq = io_zcrx_ifq_alloc(ctx);
if (!ifq)
return -ENOMEM;
+ ret = ptr_ring_init(&ifq->skb_ring, 1024, GFP_KERNEL_ACCOUNT);
+ if (ret < 0)
+ goto ifq_free;
ifq->notif_data = notif.user_data;
ifq->allowed_notif_mask = notif.type_mask;
@@ -1188,9 +1200,10 @@ static inline u32 __zcrx_rq_entries(struct zcrx_rq *rq)
return min(entries, rq->nr_entries);
}
-static inline u32 zcrx_rq_entries(struct zcrx_rq *rq)
+static inline u32 zcrx_rq_entries(struct zcrx_rq *rq, struct io_zcrx_ifq *ifq)
{
rq->cached_tail = smp_load_acquire(&rq->ring->tail);
+ zcrx_release_skbs(ifq);
return __zcrx_rq_entries(rq);
}
@@ -1209,6 +1222,7 @@ static inline void zcrx_rq_iter_init(struct zcrx_rq_iter *it,
}
static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it,
+ struct io_zcrx_ifq *ifq,
struct zcrx_rq *rq,
struct io_uring_zcrx_rqe **rqe)
{
@@ -1217,6 +1231,14 @@ static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it,
if (it->flushed)
return false;
rq->cached_tail = smp_load_acquire(&rq->ring->tail);
+
+ /*
+ * skbs carry user niov references from the syscall path,
+ * process them first before refilling will try to put
+ * them back down.
+ */
+ zcrx_release_skbs(ifq);
+
it->rqes_left = min_t(unsigned, __zcrx_rq_entries(rq),
ZCRX_REFILL_CAP);
it->flushed = true;
@@ -1270,6 +1292,42 @@ static bool zcrx_put_refill_niov(struct net_iov *niov, struct page_pool *pp,
return true;
}
+static void zcrx_user_ref_frags(struct io_zcrx_ifq *ifq, struct sk_buff *skb,
+ unsigned start, unsigned nr)
+{
+ struct skb_shared_info *shi = skb_shinfo(skb);
+ unsigned i;
+
+ nr = min_t(unsigned, nr, shi->nr_frags);
+ for (i = start; i < nr; i++) {
+ const skb_frag_t *frag = &shi->frags[i];
+ struct net_iov *niov = netmem_to_net_iov(frag->netmem);
+
+ /*
+ * Prevent it from being recycled while user is accessing it.
+ * It has to be done before grabbing a user reference.
+ */
+ page_pool_ref_netmem(net_iov_to_netmem(niov));
+ io_zcrx_get_niov_uref(niov);
+ }
+}
+
+static void zcrx_release_skbs(struct io_zcrx_ifq *ifq)
+{
+ while (1) {
+ struct sk_buff *skb = __ptr_ring_consume(&ifq->skb_ring);
+
+ if (!skb)
+ break;
+
+ zcrx_user_ref_frags(ifq, skb, 0, -1U);
+ if (skb->fclone != SKB_FCLONE_UNAVAILABLE)
+ __kfree_skb(skb);
+ else
+ __napi_kfree_skb(skb, SKB_CONSUMED);
+ }
+}
+
static unsigned io_zcrx_ring_refill(struct page_pool *pp,
struct io_zcrx_ifq *ifq,
netmem_ref *netmems, unsigned to_alloc)
@@ -1285,7 +1343,7 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp,
zcrx_rq_iter_init(&it, rq);
- while (allocated < to_alloc - 1 && zcrx_rq_iter_next(&it, rq, &rqe)) {
+ while (allocated < to_alloc - 1 && zcrx_rq_iter_next(&it, ifq, rq, &rqe)) {
struct net_iov *next_niov;
if (!io_parse_rqe(rqe, ifq, &next_niov))
@@ -1489,7 +1547,7 @@ static unsigned zcrx_parse_rq(netmem_ref *netmem_array, unsigned nr,
unsigned int mask = rq->nr_entries - 1;
unsigned int i;
- nr = min(nr, zcrx_rq_entries(rq));
+ nr = min(nr, zcrx_rq_entries(rq, zcrx));
for (i = 0; i < nr; i++) {
struct io_uring_zcrx_rqe *rqe = zcrx_next_rqe(rq, mask);
struct net_iov *niov;
@@ -1814,7 +1872,7 @@ static int zcrx_recv_niov(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
}
static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
- unsigned int offset, size_t len)
+ unsigned int offset, size_t len, bool frag_skb)
{
struct io_zcrx_args *args = desc->arg.data;
struct io_zcrx_ifq *ifq = args->ifq;
@@ -1822,6 +1880,8 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
struct sk_buff *frag_iter;
unsigned start, start_off = offset;
struct skb_shared_info *shi;
+ unsigned first_frag;
+ bool can_steal;
int i, ret = 0;
len = min_t(size_t, len, desc->count);
@@ -1868,6 +1928,8 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
start = frag_end;
}
+ first_frag = i;
+
for (; i < shi->nr_frags; i++) {
const skb_frag_t *frag = &shi->frags[i];
unsigned frag_end = start + skb_frag_size(frag);
@@ -1881,7 +1943,7 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
if (unlikely(!skb_frag_is_net_iov(frag))) {
ret = io_zcrx_copy_frag(req, ifq, frag, frag_off, copy);
if (ret < 0)
- goto out;
+ break;
} else {
struct net_iov *niov = netmem_to_net_iov(frag->netmem);
@@ -1889,20 +1951,38 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
frag_off + skb_frag_off(frag),
copy);
if (ret < 0)
- goto out;
- /*
- * Prevent it from being recycled while user is accessing it.
- * It has to be done before grabbing a user reference.
- */
- page_pool_ref_netmem(net_iov_to_netmem(niov));
- io_zcrx_get_niov_uref(niov);
+ break;
}
offset += ret;
len -= ret;
- if (len == 0 || ret != copy)
- goto out;
+ if (len == 0 || ret != copy) {
+ i++;
+ len = 0;
+ break;
+ }
+ }
+
+ if (start != offset || ret < 0) {
+ if (!skb_frags_readable(skb))
+ zcrx_user_ref_frags(ifq, skb, first_frag, i);
+ goto out;
+ }
+
+ can_steal = !skb_frags_readable(skb) && !skb_has_frag_list(skb) &&
+ start_off == 0 && !frag_skb;
+
+ if (can_steal && !__ptr_ring_full(&ifq->skb_ring) &&
+ tcp_read_sock_steal_skb(desc, skb, args->sock->sk)) {
+ ret = ptr_ring_produce(&ifq->skb_ring, skb);
+ if (ret) {
+ zcrx_user_ref_frags(ifq, skb, first_frag, i);
+ __kfree_skb(skb);
+ }
+ goto out;
}
+ if (!skb_frags_readable(skb))
+ zcrx_user_ref_frags(ifq, skb, first_frag, i);
skb_walk_frags(skb, frag_iter) {
unsigned frag_end;
@@ -1915,7 +1995,7 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
unsigned copy = min(frag_end - offset, len);
unsigned frag_off = offset - start;
- ret = __zcrx_recv_skb(desc, frag_iter, frag_off, copy);
+ ret = __zcrx_recv_skb(desc, frag_iter, frag_off, copy, true);
if (ret < 0)
goto out;
@@ -1939,7 +2019,7 @@ int io_zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
{
int ret;
- ret = __zcrx_recv_skb(desc, skb, offset, len);
+ ret = __zcrx_recv_skb(desc, skb, offset, len, false);
desc->count -= max(0, ret);
return ret;
}
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index 05598f08eda0..7fc12e53c8a1 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -5,6 +5,7 @@
#include <linux/io_uring_types.h>
#include <linux/dma-buf.h>
#include <linux/socket.h>
+#include <linux/ptr_ring.h>
#include <net/page_pool/types.h>
#include <net/net_trackers.h>
@@ -69,6 +70,8 @@ struct io_zcrx_ifq {
struct zcrx_rq rq ____cacheline_aligned_in_smp;
spinlock_t alloc_lock ____cacheline_aligned_in_smp;
+ struct ptr_ring skb_ring;
+
u32 if_rxq;
struct device *dev;
struct net_device *netdev;
--
2.54.0
^ permalink raw reply related
* [RFC 7/9] io_uring/zcrx: don't lock for single producer ptr ring
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
Normally, there is just one io_uring instance using zcrx and all
receiving happens under its lock. In this case we can avoid grabbing the
ptr ring lock on the production side.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 6 +++++-
io_uring/zcrx.h | 1 +
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 80a26b5798d3..3d5d5c9fd9a5 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -825,6 +825,7 @@ static int zcrx_export(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq,
if (!mem_is_zero(ce, sizeof(*ce)))
return -EINVAL;
+ ifq->shared = true;
refcount_inc(&ifq->refs);
refcount_inc(&ifq->user_refs);
@@ -1974,7 +1975,10 @@ static int __zcrx_recv_skb(read_descriptor_t *desc, struct sk_buff *skb,
if (can_steal && !__ptr_ring_full(&ifq->skb_ring) &&
tcp_read_sock_steal_skb(desc, skb, args->sock->sk)) {
- ret = ptr_ring_produce(&ifq->skb_ring, skb);
+ if (ifq->shared)
+ ret = ptr_ring_produce(&ifq->skb_ring, skb);
+ else
+ ret = __ptr_ring_produce(&ifq->skb_ring, skb);
if (ret) {
zcrx_user_ref_frags(ifq, skb, first_frag, i);
__kfree_skb(skb);
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index 7fc12e53c8a1..5ff4dabb0f68 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -71,6 +71,7 @@ struct io_zcrx_ifq {
spinlock_t alloc_lock ____cacheline_aligned_in_smp;
struct ptr_ring skb_ring;
+ bool shared;
u32 if_rxq;
struct device *dev;
--
2.54.0
^ permalink raw reply related
* [RFC 8/9] io_uring/zcrx: steal niov refs
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
In zcrx_release_skbs(), we reference all niovs of an skb and then
immediately put them down. Optimise it by stealing the frags.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 3d5d5c9fd9a5..23669471a8f0 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -1317,11 +1317,22 @@ static void zcrx_release_skbs(struct io_zcrx_ifq *ifq)
{
while (1) {
struct sk_buff *skb = __ptr_ring_consume(&ifq->skb_ring);
+ struct skb_shared_info *shi;
+ unsigned i;
if (!skb)
break;
- zcrx_user_ref_frags(ifq, skb, 0, -1U);
+ shi = skb_shinfo(skb);
+ for (i = 0; i < shi->nr_frags; i++) {
+ const skb_frag_t *frag = &shi->frags[i];
+ struct net_iov *niov = netmem_to_net_iov(frag->netmem);
+
+ /* Take niov references the skb holds */
+ io_zcrx_get_niov_uref(niov);
+ }
+ shi->nr_frags = 0;
+
if (skb->fclone != SKB_FCLONE_UNAVAILABLE)
__kfree_skb(skb);
else
--
2.54.0
^ permalink raw reply related
* [RFC 9/9] io_uring/zcrx: add rq_lock cache of "user" niov refs
From: Pavel Begunkov @ 2026-07-11 9:22 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev
Cc: io-uring, asml.silence
In-Reply-To: <cover.1783619193.git.asml.silence@gmail.com>
Now the "user" refs are acquired and released by NAPI/page pool for the
optimised path, cache them on the NAPI side and protect it by rq.lock.
Store it in net_iov as we'd be touching the cache line by refilling soon
anyway.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 23669471a8f0..04a80d1a2b3a 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -393,9 +393,20 @@ static inline atomic_t *io_get_user_counter(struct net_iov *niov)
static bool io_zcrx_put_niov_uref(struct net_iov *niov, unsigned refs)
{
- atomic_t *uref = io_get_user_counter(niov);
+ unsigned *cached_ref = &niov->mp_private;
+ atomic_t *uref;
int old;
+ lockdep_assert_held(&io_zcrx_iov_to_area(niov)->ifq->rq.lock);
+
+ if (likely(*cached_ref >= refs)) {
+ *cached_ref -= refs;
+ return true;
+ }
+ refs -= *cached_ref;
+ *cached_ref = 0;
+
+ uref = io_get_user_counter(niov);
old = atomic_read(uref);
do {
if (unlikely(old < refs))
@@ -744,6 +755,16 @@ static void io_zcrx_scrub_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area *are
{
int i;
+ scoped_guard(spinlock_bh, &ifq->rq.lock) {
+ for (i = 0; i < area->nia.num_niovs; i++) {
+ struct net_iov *niov = &area->nia.niovs[i];
+ unsigned *ref = &niov->mp_private;
+
+ atomic_add(*ref, &area->user_refs[i]);
+ *ref = 0;
+ }
+ }
+
/* Reclaim back all buffers given to the user space. */
for (i = 0; i < area->nia.num_niovs; i++) {
struct net_iov *niov = &area->nia.niovs[i];
@@ -1327,9 +1348,9 @@ static void zcrx_release_skbs(struct io_zcrx_ifq *ifq)
for (i = 0; i < shi->nr_frags; i++) {
const skb_frag_t *frag = &shi->frags[i];
struct net_iov *niov = netmem_to_net_iov(frag->netmem);
+ unsigned *ref = &niov->mp_private;
- /* Take niov references the skb holds */
- io_zcrx_get_niov_uref(niov);
+ *ref += 1;
}
shi->nr_frags = 0;
--
2.54.0
^ permalink raw reply related
* [PATCH net-next] net: dsa: realtek: rtl8366rb: Fix up port isolation
From: Linus Walleij @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alvin Šipraga, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, Linus Walleij
Sashiko reports that we incorrectly disable isolation in the setup
loop while what we want to do is to enable it.
Just delete this setting: the next loop in the setup code, over the
user ports, isolate all the ports from each other, so only the CPU
port can see them.
Fix up the comments so it is clear what is going on, including a
missing word in the helper function.
Reported-by: Paolo Abeni <pabeni@redhat.com>
Closes: https://sashiko.dev/#/patchset/20260630-rtl8366rb-improvements-v2-0-05eb9d6a37f5%40kernel.org
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/net/dsa/realtek/rtl8366rb.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c
index d2fa8ff6a5d0..e615814049c1 100644
--- a/drivers/net/dsa/realtek/rtl8366rb.c
+++ b/drivers/net/dsa/realtek/rtl8366rb.c
@@ -794,8 +794,8 @@ static int rtl8366rb_setup_all_leds_off(struct realtek_priv *priv)
static int rtl8366rb_port_set_isolation(struct realtek_priv *priv, int port,
u32 mask)
{
- /* Bit 0 enables isolation so set this if we enable isolation
- * any of the ports an clear it if we disable on all of them.
+ /* Bit 0 enables isolation so set this if we enable isolation on
+ * any of the ports and clear it if we disable on all of them.
*/
if (mask)
mask = RTL8366RB_PORT_ISO_PORTS(mask) | RTL8366RB_PORT_ISO_EN;
@@ -950,11 +950,6 @@ static int rtl8366rb_setup(struct dsa_switch *ds)
*/
rtl8366rb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED);
- /* Start with all ports completely isolated */
- ret = rtl8366rb_port_set_isolation(priv, dp->index, 0);
- if (ret)
- return ret;
-
/* Disable learning */
ret = rtl8366rb_port_set_learning(priv, dp->index, false);
if (ret)
@@ -974,7 +969,7 @@ static int rtl8366rb_setup(struct dsa_switch *ds)
if (!dsa_port_is_user(dp))
continue;
- /* Forward only to the CPU */
+ /* Forward only to the CPU, isolate from all other ports */
ret = rtl8366rb_port_set_isolation(priv, dp->index, upports_mask);
if (ret)
return ret;
---
base-commit: 23dad2d088dfc82cae1f5a936f8ff7ffebb38dd9
change-id: 20260702-rtl8366rb-fixes-a93f831ec2a4
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply related
* Re: [PATCH net-next v9 0/9] BIG TCP for UDP tunnels
From: Nikolay Aleksandrov @ 2026-07-11 9:47 UTC (permalink / raw)
To: Alice Mikityanska, Daniel Borkmann, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Xin Long, Willem de Bruijn,
Willem de Bruijn, David Ahern
Cc: Shuah Khan, Stanislav Fomichev, Andrew Lunn, Simon Horman,
Florian Westphal, netdev, Alice Mikityanska
In-Reply-To: <20260710134242.216538-1-alice.kernel@fastmail.im>
On 10/07/2026 16:42, Alice Mikityanska wrote:
> From: Alice Mikityanska <alice@isovalent.com>
>
> This series is a follow-up to "BIG TCP without HBH in IPv6", and it adds
> support for BIG TCP IPv4/IPv6 workloads in vxlan and geneve. Now that
> IPv6 BIG TCP doesn't require stripping the HBH in all various
> combinations in tunneled traffic, adding BIG TCP becomes feasible.
>
> Patch 01 adds accessors for the length field in the UDP header, as
> suggested by Paolo in review. The usage of udp_set_len is then added in
> the following patches that start using length=0 in BIG TCP UDP packets.
>
> Patches 02-04 close the gaps that prevent BIG TCP packets from going
> through UDP tunnel code.
>
> Patch 05 validates packets in udp_gro_receive to exclude packets with
> length=0 from GRO aggregation.
>
> Patch 06 is for proper formatting in tcpdump (set UDP len to 0 rather
> than a trimmed value on overflow).
>
> Patches 07-08 bump up tso_max_size for VXLAN and GENEVE.
>
> Patch 09 adds selftests.
>
> Thanks all!
>
> v9 changes: Converted the selftest to iptables counters to avoid the
> issue with tcpdump pcaps taking all space in /tmp.
>
> v8: https://lore.kernel.org/netdev/20260706181941.385672-1-alice.kernel@fastmail.im/
>
> v8 changes: Addressed Paolo's and Jason's review comments. Made the
> selftest more robust, added checks for SACK and mode with disabled tx
> checksum offload on lower veth netdevs. Added details to commit
> messages. Kept skb_segment logic unchanged, because the frags overflow
> issue is not specific to BIG TCP.
>
> v7: https://lore.kernel.org/netdev/20260611192955.604661-1-alice.kernel@fastmail.im/
>
> v7 changes: Addressed Paolo's comments to properly block malformed
> packets with UDP length=0 at udp_rcv level.
>
> v6: https://lore.kernel.org/netdev/20260602093931.516281-1-alice.kernel@fastmail.im/
>
> v6 changes: Lowered the packets threshold in the selftest to pass
> upstream CI on debug kernels, also made it configurable.
>
> v5: https://lore.kernel.org/netdev/20260526161200.1135899-1-alice.kernel@fastmail.im/
>
> v5 changes: Rebased, dropped one of the patches that came in via the net
> tree, addressed an overflow in nsim_do_psp found by Sashiko.
>
> v4: https://lore.kernel.org/netdev/20260512165648.386518-1-alice.kernel@fastmail.im/
>
> v4 changes: Rebased, addressed Sashiko AI review [1] and Willem's
> comment about netperf flags.
>
> My comments on Sashiko AI review per patch:
>
> 01: I'd prefer to keep the cases that I haven't tested outside of the
> scope of this series, which is for VXLAN/GENEVE tunnels, not for ESP.
>
> 02: The patch doesn't have behavioral changes other than fixing the
> checksum. The final uh->len assignment assigns the actual length, not
> 64k.
>
> 03: The check can't be loosened, because total_len is also assigned to
> UDP length. BIG TCP works in the mode without GRO hint option.
>
> 04: Fixed the fallback value for udplen. BIG TCP is fine, it's the
> uh->len = 0 case, uh->len can't exceed 64k.
>
> 05: In the BIG TCP case, partial GSO splits the SKB in two. If full
> segmentation is needed, the SKB is split in many MSS-sized SKBs without
> fragments.
>
> 06: Invalid packets from the wire are addressed in 08.
>
> 07: __udp_gso_segment only handles UDP GSO packets, which can't be
> bigger than 64k. Kept udp_set_len_short in nf_nat_mangle_udp_packet, as
> the function doesn't support BIG TCP packets anyway (see
> mangle_contents).
>
> 08: udp_gro_receive handles packets before aggregation, there are no BIG
> TCP packets at this point. RFC 768 doesn't say that padded UDP packets
> are valid. Real jumbograms don't seem to be supported in this path
> anyway.
>
> 09: The packet goes to skb_udp_tunnel_segment, not __udp_gso_segment.
> __skb_udp_tunnel_segment handles this case.
>
> 12: Improved process cleanup by killing everything inside netns before
> deleting them, and by avoiding killing netserver outside of netns.
> netperf with -r in TCP_STREAM mode works, and the option has the
> intended effect, but I replaced it with -m. Added dependency check for
> tcpdump.
>
> [1]: https://sashiko.dev/#/patchset/20260410150943.993350-1-alice.kernel%40fastmail.im
>
> v3: https://lore.kernel.org/netdev/20260410150943.993350-1-alice.kernel@fastmail.im/
>
> v3 changes: Fixed the redirect in the selftest, rebased over my L2TP fix
> [2] for the syzbot report [3].
>
> [2]: https://lore.kernel.org/netdev/20260403174949.843941-1-alice.kernel@fastmail.im/
> [3]: https://lore.kernel.org/netdev/69a1dfba.050a0220.3a55be.0026.GAE@google.com/
>
> v2: https://lore.kernel.org/netdev/20260226201600.222044-1-alice.kernel@fastmail.im/
>
> v2 changes: Addressed the review comments: added UDP len helpers,
> consolidated UDP len sanity checks in patch 08 into one, added
> selftests. Added fixups to related code (patch 01-03).
>
> v1: https://lore.kernel.org/netdev/20250923134742.1399800-1-maxtram95@gmail.com/
>
> Alice Mikityanska (8):
> net: Use helpers to get/set UDP len tree-wide
> net: Enable BIG TCP with partial GSO
> udp: Support BIG TCP GSO packets where they can occur
> udp: Support gro_ipv4_max_size > 65536
> udp: Validate UDP length in udp_gro_receive
> udp: Set length in UDP header to 0 for big GSO packets
> vxlan: Enable BIG TCP packets
> selftests: net: Add a test for BIG TCP in UDP tunnels
>
> Daniel Borkmann (1):
> geneve: Enable BIG TCP packets
>
> drivers/infiniband/core/lag.c | 2 +-
> drivers/infiniband/sw/rxe/rxe_net.c | 4 +-
> drivers/net/amt.c | 6 +-
> drivers/net/ethernet/intel/i40e/i40e_txrx.c | 2 +-
> drivers/net/ethernet/intel/iavf/iavf_txrx.c | 2 +-
> drivers/net/ethernet/intel/ice/ice_txrx.c | 2 +-
> drivers/net/ethernet/intel/idpf/idpf_txrx.c | 2 +-
> .../marvell/octeontx2/nic/otx2_txrx.c | 2 +-
> .../net/ethernet/mellanox/mlx5/core/en_rx.c | 4 +-
> .../ethernet/mellanox/mlx5/core/en_selftest.c | 2 +-
> drivers/net/ethernet/sfc/falcon/selftest.c | 4 +-
> drivers/net/ethernet/sfc/selftest.c | 4 +-
> drivers/net/ethernet/sfc/siena/selftest.c | 4 +-
> drivers/net/ethernet/sfc/tc_encap_actions.c | 2 +-
> .../stmicro/stmmac/stmmac_selftests.c | 4 +-
> drivers/net/geneve.c | 4 +-
> drivers/net/netconsole.c | 2 +-
> drivers/net/netdevsim/dev.c | 2 +-
> drivers/net/netdevsim/psample.c | 2 +-
> drivers/net/netdevsim/psp.c | 8 +-
> drivers/net/vxlan/vxlan_core.c | 2 +
> drivers/net/wireguard/receive.c | 2 +-
> include/linux/udp.h | 27 +++
> include/trace/events/icmp.h | 2 +-
> lib/tests/blackhole_dev_kunit.c | 2 +-
> net/6lowpan/nhc_udp.c | 10 +-
> net/core/pktgen.c | 4 +-
> net/core/selftests.c | 4 +-
> net/core/skbuff.c | 10 +-
> net/core/tso.c | 3 +-
> net/ipv4/esp4.c | 2 +-
> net/ipv4/fou_core.c | 2 +-
> net/ipv4/ipconfig.c | 6 +-
> net/ipv4/netfilter/nf_nat_snmp_basic_main.c | 4 +-
> net/ipv4/route.c | 2 +-
> net/ipv4/udp.c | 7 +-
> net/ipv4/udp_offload.c | 49 ++---
> net/ipv4/udp_tunnel_core.c | 2 +-
> net/ipv6/esp6.c | 5 +-
> net/ipv6/fou6.c | 2 +-
> net/ipv6/ip6_udp_tunnel.c | 2 +-
> net/ipv6/udp.c | 7 +-
> net/ipv6/udp_offload.c | 2 +-
> net/l2tp/l2tp_core.c | 2 +-
> net/netfilter/ipvs/ip_vs_xmit.c | 2 +-
> net/netfilter/nf_conntrack_proto_udp.c | 15 +-
> net/netfilter/nf_log_syslog.c | 2 +-
> net/netfilter/nf_nat_helper.c | 2 +-
> net/psp/psp_main.c | 2 +-
> net/sched/act_csum.c | 4 +-
> net/xfrm/xfrm_nat_keepalive.c | 2 +-
> tools/testing/selftests/net/Makefile | 1 +
> .../testing/selftests/net/big_tcp_tunnels.sh | 188 ++++++++++++++++++
> 53 files changed, 340 insertions(+), 102 deletions(-)
> create mode 100755 tools/testing/selftests/net/big_tcp_tunnels.sh
>
Very nice work. The set LGTM.
For the series:
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Thanks,
Nik
^ permalink raw reply
* [PATCH] af_unix: add cond_resched() when flushing receive queue on close
From: albin_yang @ 2026-07-11 9:51 UTC (permalink / raw)
To: kuniyu, davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-kernel, albinwyang
From: Wei Yang <albinwyang@tencent.com>
unix_release_sock() drains sk_receive_queue in a tight loop with no
rescheduling point. Each iteration may do extra work depending on
the socket type: kfree_skb() runs unix_destruct_scm()->fput() for
SCM_RIGHTS fds on data sockets, and a LISTEN socket recurses into
unix_release_sock() for each embryonic child.
A long receive queue can hog the CPU and trip the softlockup watchdog
on CONFIG_PREEMPT_NONE kernels. We have reproduced this multiple
times under stress-ng stress testing. Add cond_resched() as done in
similar teardown loops (inet_csk_listen_stop, inet_twsk_purge,
close_files).
Signed-off-by: Wei Yang <albinwyang@tencent.com>
---
net/unix/af_unix.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..8b4624ce4178 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -724,6 +724,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
/* passed fds are erased in the kfree_skb hook */
kfree_skb_reason(skb, SKB_DROP_REASON_SOCKET_CLOSE);
+ cond_resched();
}
if (path.dentry)
--
2.43.5
^ permalink raw reply related
* [PATCH ethtool-next v2 2/2] sfpid: print all compliance codes
From: Aleksander Jan Bajkowski @ 2026-07-11 9:54 UTC (permalink / raw)
To: danieller, mkubecek, andrew, davem, edumazet, kuba, pabeni, jbe,
netdev
Cc: Aleksander Jan Bajkowski
In-Reply-To: <20260711095803.257213-1-olek2@wp.pl>
SFP modules implement multiple compliance codes. This is common for
dual-rate modules. Before the `json` option was introduced, all
compliance codes were displayed. Currently, only the last code is
displayed. This commit fixes that bug. Compliance codes are
represented as array.
Before:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": "FC: 100 MBytes/sec",
...
} ]
After:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : Ethernet: 1000BASE-SX
Transceiver type : FC: intermediate distance (I)
Transceiver type : FC: Shortwave laser w/o OFC (SN)
Transceiver type : FC: Multimode, 62.5um (M6)
Transceiver type : FC: Multimode, 50um (M5)
Transceiver type : FC: 400 MBytes/sec
Transceiver type : FC: 200 MBytes/sec
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": [ "Ethernet: 1000BASE-SX","FC: intermediate distance (I)","FC: Shortwave laser w/o OFC (SN)","FC: Multimode, 62.5um (M6)","FC: Multimode, 50um (M5)","FC: 400 MBytes/sec","FC: 200 MBytes/sec","FC: 100 MBytes/sec" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
Changes in v2:
- drop </pre> leftover
- use single sfp module in Before/After
- rename module_print_array_string() -> module_print_any_array_string_entry()
---
sfpid.c | 291 ++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 187 insertions(+), 104 deletions(-)
diff --git a/sfpid.c b/sfpid.c
index ec5dd95..25ddd57 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -50,7 +50,6 @@ static void sff8079_show_connector(const __u8 *id)
static void sff8079_show_transceiver(const __u8 *id)
{
static const char *pfx = "Transceiver type";
- char value[140] = "";
if (is_json_context()) {
open_json_array("transceiver_codes", "");
@@ -70,242 +69,326 @@ static void sff8079_show_transceiver(const __u8 *id)
"Transceiver codes", id[3], id[4], id[5], id[6],
id[7], id[8], id[9], id[10], id[36]);
}
+
+ if (is_json_context())
+ open_json_array("transceiver_type", "");
+
/* 10G Ethernet Compliance Codes */
if (id[3] & (1 << 7))
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
if (id[3] & (1 << 6))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LRM");
+ module_print_any_array_string_entry(pfx,
+ "10G Ethernet: 10G Base-LRM");
if (id[3] & (1 << 5))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LR");
+ module_print_any_array_string_entry(pfx,
+ "10G Ethernet: 10G Base-LR");
if (id[3] & (1 << 4))
- sprintf(value, "%s", "10G Ethernet: 10G Base-SR");
+ module_print_any_array_string_entry(pfx,
+ "10G Ethernet: 10G Base-SR");
/* Infiniband Compliance Codes */
if (id[3] & (1 << 3))
- sprintf(value, "%s", "Infiniband: 1X SX");
+ module_print_any_array_string_entry(pfx,
+ "Infiniband: 1X SX");
if (id[3] & (1 << 2))
- sprintf(value, "%s", "Infiniband: 1X LX");
+ module_print_any_array_string_entry(pfx,
+ "Infiniband: 1X LX");
if (id[3] & (1 << 1))
- sprintf(value, "%s", "Infiniband: 1X Copper Active");
+ module_print_any_array_string_entry(pfx,
+ "Infiniband: 1X Copper Active");
if (id[3] & (1 << 0))
- sprintf(value, "%s", "Infiniband: 1X Copper Passive");
+ module_print_any_array_string_entry(pfx,
+ "Infiniband: 1X Copper Passive");
/* ESCON Compliance Codes */
if (id[4] & (1 << 7))
- sprintf(value, "%s", "ESCON: ESCON MMF, 1310nm LED");
+ module_print_any_array_string_entry(pfx,
+ "ESCON: ESCON MMF, 1310nm LED");
if (id[4] & (1 << 6))
- sprintf(value, "%s", "ESCON: ESCON SMF, 1310nm Laser");
+ module_print_any_array_string_entry(pfx,
+ "ESCON: ESCON SMF, 1310nm Laser");
/* SONET Compliance Codes */
if (id[4] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-192, short reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-192, short reach");
if (id[4] & (1 << 4))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 1");
+ module_print_any_array_string_entry(pfx,
+ "SONET: SONET reach specifier bit 1");
if (id[4] & (1 << 3))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 2");
+ module_print_any_array_string_entry(pfx,
+ "SONET: SONET reach specifier bit 2");
if (id[4] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-48, long reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-48, long reach");
if (id[4] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-48, intermediate reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-48, intermediate reach");
if (id[4] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-48, short reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-48, short reach");
if (id[5] & (1 << 6))
- sprintf(value, "%s", "SONET: OC-12, single mode, long reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-12, single mode, long reach");
if (id[5] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-12, single mode, inter. reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-12, single mode, inter. reach");
if (id[5] & (1 << 4))
- sprintf(value, "%s", "SONET: OC-12, short reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-12, short reach");
if (id[5] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-3, single mode, long reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-3, single mode, long reach");
if (id[5] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-3, single mode, inter. reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-3, single mode, inter. reach");
if (id[5] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-3, short reach");
+ module_print_any_array_string_entry(pfx,
+ "SONET: OC-3, short reach");
/* Ethernet Compliance Codes */
if (id[6] & (1 << 7))
- sprintf(value, "%s", "Ethernet: BASE-PX");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: BASE-PX");
if (id[6] & (1 << 6))
- sprintf(value, "%s", "Ethernet: BASE-BX10");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: BASE-BX10");
if (id[6] & (1 << 5))
- sprintf(value, "%s", "Ethernet: 100BASE-FX");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 100BASE-FX");
if (id[6] & (1 << 4))
- sprintf(value, "%s", "Ethernet: 100BASE-LX/LX10");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 100BASE-LX/LX10");
if (id[6] & (1 << 3))
- sprintf(value, "%s", "Ethernet: 1000BASE-T");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 1000BASE-T");
if (id[6] & (1 << 2))
- sprintf(value, "%s", "Ethernet: 1000BASE-CX");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 1000BASE-CX");
if (id[6] & (1 << 1))
- sprintf(value, "%s", "Ethernet: 1000BASE-LX");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 1000BASE-LX");
if (id[6] & (1 << 0))
- sprintf(value, "%s", "Ethernet: 1000BASE-SX");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (id[7] & (1 << 7))
- sprintf(value, "%s", "FC: very long distance (V)");
+ module_print_any_array_string_entry(pfx,
+ "FC: very long distance (V)");
if (id[7] & (1 << 6))
- sprintf(value, "%s", "FC: short distance (S)");
+ module_print_any_array_string_entry(pfx,
+ "FC: short distance (S)");
if (id[7] & (1 << 5))
- sprintf(value, "%s", "FC: intermediate distance (I)");
+ module_print_any_array_string_entry(pfx,
+ "FC: intermediate distance (I)");
if (id[7] & (1 << 4))
- sprintf(value, "%s", "FC: long distance (L)");
+ module_print_any_array_string_entry(pfx,
+ "FC: long distance (L)");
if (id[7] & (1 << 3))
- sprintf(value, "%s", "FC: medium distance (M)");
+ module_print_any_array_string_entry(pfx,
+ "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (id[7] & (1 << 2))
- sprintf(value, "%s", "FC: Shortwave laser, linear Rx (SA)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Shortwave laser, linear Rx (SA)");
if (id[7] & (1 << 1))
- sprintf(value, "%s", "FC: Longwave laser (LC)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Longwave laser (LC)");
if (id[7] & (1 << 0))
- sprintf(value, "%s", "FC: Electrical inter-enclosure (EL)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Electrical inter-enclosure (EL)");
if (id[8] & (1 << 7))
- sprintf(value, "%s", "FC: Electrical intra-enclosure (EL)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Electrical intra-enclosure (EL)");
if (id[8] & (1 << 6))
- sprintf(value, "%s", "FC: Shortwave laser w/o OFC (SN)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Shortwave laser w/o OFC (SN)");
if (id[8] & (1 << 5))
- sprintf(value, "%s", "FC: Shortwave laser with OFC (SL)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Shortwave laser with OFC (SL)");
if (id[8] & (1 << 4))
- sprintf(value, "%s", "FC: Longwave laser (LL)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Longwave laser (LL)");
if (id[8] & (1 << 3))
- sprintf(value, "%s", "Active Cable");
+ module_print_any_array_string_entry(pfx,
+ "Active Cable");
if (id[8] & (1 << 2))
- sprintf(value, "%s", "Passive Cable");
+ module_print_any_array_string_entry(pfx,
+ "Passive Cable");
if (id[8] & (1 << 1))
- sprintf(value, "%s", "FC: Copper FC-BaseT");
+ module_print_any_array_string_entry(pfx,
+ "FC: Copper FC-BaseT");
/* Fibre Channel transmission media */
if (id[9] & (1 << 7))
- sprintf(value, "%s", "FC: Twin Axial Pair (TW)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Twin Axial Pair (TW)");
if (id[9] & (1 << 6))
- sprintf(value, "%s", "FC: Twisted Pair (TP)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Twisted Pair (TP)");
if (id[9] & (1 << 5))
- sprintf(value, "%s", "FC: Miniature Coax (MI)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Miniature Coax (MI)");
if (id[9] & (1 << 4))
- sprintf(value, "%s", "FC: Video Coax (TV)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Video Coax (TV)");
if (id[9] & (1 << 3))
- sprintf(value, "%s", "FC: Multimode, 62.5um (M6)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Multimode, 62.5um (M6)");
if (id[9] & (1 << 2))
- sprintf(value, "%s", "FC: Multimode, 50um (M5)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Multimode, 50um (M5)");
if (id[9] & (1 << 0))
- sprintf(value, "%s", "FC: Single Mode (SM)");
+ module_print_any_array_string_entry(pfx,
+ "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (id[10] & (1 << 7))
- sprintf(value, "%s", "FC: 1200 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 1200 MBytes/sec");
if (id[10] & (1 << 6))
- sprintf(value, "%s", "FC: 800 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 800 MBytes/sec");
if (id[10] & (1 << 5))
- sprintf(value, "%s", "FC: 1600 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 1600 MBytes/sec");
if (id[10] & (1 << 4))
- sprintf(value, "%s", "FC: 400 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 400 MBytes/sec");
if (id[10] & (1 << 3))
- sprintf(value, "%s", "FC: 3200 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 3200 MBytes/sec");
if (id[10] & (1 << 2))
- sprintf(value, "%s", "FC: 200 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 200 MBytes/sec");
if (id[10] & (1 << 0))
- sprintf(value, "%s", "FC: 100 MBytes/sec");
+ module_print_any_array_string_entry(pfx,
+ "FC: 100 MBytes/sec");
/* Extended Specification Compliance Codes from SFF-8024 */
if (id[36] == 0x1)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
if (id[36] == 0x2)
- sprintf(value, "%s", "Extended: 100G Base-SR4 or 25GBase-SR");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G Base-SR4 or 25GBase-SR");
if (id[36] == 0x3)
- sprintf(value, "%s", "Extended: 100G Base-LR4 or 25GBase-LR");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G Base-LR4 or 25GBase-LR");
if (id[36] == 0x4)
- sprintf(value, "%s", "Extended: 100G Base-ER4 or 25GBase-ER");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G Base-ER4 or 25GBase-ER");
if (id[36] == 0x8)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
if (id[36] == 0xb)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G Base-CR4 or 25G Base-CR CA-L");
if (id[36] == 0xc)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-S");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 25G Base-CR CA-S");
if (id[36] == 0xd)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-N");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 25G Base-CR CA-N");
if (id[36] == 0x16)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 10Gbase-T with SFI electrical interface");
if (id[36] == 0x18)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
if (id[36] == 0x19)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
if (id[36] == 0x1a)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
if (id[36] == 0x1b)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G 1550nm WDM (4 wavelengths)");
if (id[36] == 0x1c)
- sprintf(value, "%s", "Extended: 10Gbase-T Short Reach");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 10Gbase-T Short Reach");
if (id[36] == 0x1d)
- sprintf(value, "%s", "Extended: 5GBASE-T");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 5GBASE-T");
if (id[36] == 0x1e)
- sprintf(value, "%s", "Extended: 2.5GBASE-T");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 2.5GBASE-T");
if (id[36] == 0x1f)
- sprintf(value, "%s", "Extended: 40G SWDM4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 40G SWDM4");
if (id[36] == 0x20)
- sprintf(value, "%s", "Extended: 100G SWDM4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G SWDM4");
if (id[36] == 0x21)
- sprintf(value, "%s", "Extended: 100G PAM4 BiDi");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G PAM4 BiDi");
if (id[36] == 0x22)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
if (id[36] == 0x23)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
if (id[36] == 0x24)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
if (id[36] == 0x25)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100GBASE-DR (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x26)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x27)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x30)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x31)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x32)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x33)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x40)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
if (id[36] == 0x41)
- sprintf(value, "%s",
+ module_print_any_array_string_entry(pfx,
"Extended: 50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
if (id[36] == 0x42)
- sprintf(value, "%s", "Extended: 50GBASE-FR or 200GBASE-DR4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 50GBASE-FR or 200GBASE-DR4");
if (id[36] == 0x43)
- sprintf(value, "%s", "Extended: 200GBASE-FR4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 200GBASE-FR4");
if (id[36] == 0x44)
- sprintf(value, "%s", "Extended: 200G 1550 nm PSM4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 200G 1550 nm PSM4");
if (id[36] == 0x45)
- sprintf(value, "%s", "Extended: 50GBASE-LR");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 50GBASE-LR");
if (id[36] == 0x46)
- sprintf(value, "%s", "Extended: 200GBASE-LR4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 200GBASE-LR4");
if (id[36] == 0x50)
- sprintf(value, "%s", "Extended: 64GFC EA");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 64GFC EA");
if (id[36] == 0x51)
- sprintf(value, "%s", "Extended: 64GFC SW");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 64GFC SW");
if (id[36] == 0x52)
- sprintf(value, "%s", "Extended: 64GFC LW");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 64GFC LW");
if (id[36] == 0x53)
- sprintf(value, "%s", "Extended: 128GFC EA");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 128GFC EA");
if (id[36] == 0x54)
- sprintf(value, "%s", "Extended: 128GFC SW");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 128GFC SW");
if (id[36] == 0x55)
- sprintf(value, "%s", "Extended: 128GFC LW");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 128GFC LW");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_encoding(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* [PATCH ethtool-next v2 1/2] sfpid: print all implemented options
From: Aleksander Jan Bajkowski @ 2026-07-11 9:54 UTC (permalink / raw)
To: danieller, mkubecek, andrew, davem, edumazet, kuba, pabeni, jbe,
netdev
Cc: Aleksander Jan Bajkowski
SFP modules implement multiple options. Before the “json” option was
introduced, all options were listed. Currently, only the last option
is listed. This commit fixes this bug. Options are represented as array.
Before:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": "RATE_SELECT implemented",
...
} ]
After:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RX_LOS implemented
Option : TX_DISABLE implemented
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": [ "RX_LOS implemented","TX_DISABLE implemented","RATE_SELECT implemented" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
Changes in v2:
- fix typo introduced -> introduced
- renamr module_print_array_string() -> module_print_any_array_string_entry()
---
module-common.c | 8 ++++++++
module-common.h | 1 +
sfpid.c | 48 ++++++++++++++++++++++++++++++++----------------
3 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/module-common.c b/module-common.c
index 42fccf6..43ff649 100644
--- a/module-common.c
+++ b/module-common.c
@@ -258,6 +258,14 @@ void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
printf("\t%-41s : %s\n", fn, str_value);
}
+void module_print_any_array_string_entry(const char *fn, const char *value)
+{
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", value);
+ else
+ printf("\t%-41s : %s\n", fn, value);
+}
+
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit)
diff --git a/module-common.h b/module-common.h
index 4063448..f3baf2a 100644
--- a/module-common.h
+++ b/module-common.h
@@ -281,6 +281,7 @@ void module_print_any_string(const char *fn, const char *value);
void module_print_any_float(const char *fn, float value, const char *unit);
void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
const char *str_value);
+void module_print_any_array_string_entry(const char *fn, const char *value);
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit);
diff --git a/sfpid.c b/sfpid.c
index 74a6f51..ec5dd95 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -396,7 +396,6 @@ static void sff8079_show_wavelength_or_copper_compliance(const __u8 *id)
static void sff8079_show_options(const __u8 *id)
{
static const char *pfx = "Option";
- char value[64] = "";
if (is_json_context()) {
open_json_array("option_values", "");
@@ -407,35 +406,52 @@ static void sff8079_show_options(const __u8 *id)
printf("\t%-41s : 0x%02x 0x%02x\n", "Option values", id[64],
id[65]);
}
+
+ if (is_json_context())
+ open_json_array("option", "");
+
if (id[65] & (1 << 1))
- sprintf(value, "%s", "RX_LOS implemented");
+ module_print_any_array_string_entry(pfx,
+ "RX_LOS implemented");
if (id[65] & (1 << 2))
- sprintf(value, "%s", "RX_LOS implemented, inverted");
+ module_print_any_array_string_entry(pfx,
+ "RX_LOS implemented, inverted");
if (id[65] & (1 << 3))
- sprintf(value, "%s", "TX_FAULT implemented");
+ module_print_any_array_string_entry(pfx,
+ "TX_FAULT implemented");
if (id[65] & (1 << 4))
- sprintf(value, "%s", "TX_DISABLE implemented");
+ module_print_any_array_string_entry(pfx,
+ "TX_DISABLE implemented");
if (id[65] & (1 << 5))
- sprintf(value, "%s", "RATE_SELECT implemented");
+ module_print_any_array_string_entry(pfx,
+ "RATE_SELECT implemented");
if (id[65] & (1 << 6))
- sprintf(value, "%s", "Tunable transmitter technology");
+ module_print_any_array_string_entry(pfx,
+ "Tunable transmitter technology");
if (id[65] & (1 << 7))
- sprintf(value, "%s", "Receiver decision threshold implemented");
+ module_print_any_array_string_entry(pfx,
+ "Receiver decision threshold implemented");
if (id[64] & (1 << 0))
- sprintf(value, "%s", "Linear receiver output implemented");
+ module_print_any_array_string_entry(pfx,
+ "Linear receiver output implemented");
if (id[64] & (1 << 1))
- sprintf(value, "%s", "Power level 2 requirement");
+ module_print_any_array_string_entry(pfx,
+ "Power level 2 requirement");
if (id[64] & (1 << 2))
- sprintf(value, "%s", "Cooled transceiver implemented");
+ module_print_any_array_string_entry(pfx,
+ "Cooled transceiver implemented");
if (id[64] & (1 << 3))
- sprintf(value, "%s", "Retimer or CDR implemented");
+ module_print_any_array_string_entry(pfx,
+ "Retimer or CDR implemented");
if (id[64] & (1 << 4))
- sprintf(value, "%s", "Paging implemented");
+ module_print_any_array_string_entry(pfx,
+ "Paging implemented");
if (id[64] & (1 << 5))
- sprintf(value, "%s", "Power level 3 requirement");
+ module_print_any_array_string_entry(pfx,
+ "Power level 3 requirement");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_all_common(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 net-next 13/14] ipvlan: Protect ipvl_port.ipvlans with mutex.
From: Paolo Abeni @ 2026-07-11 10:21 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Andrew Lunn,
Simon Horman, Kuniyuki Iwashima, netdev
In-Reply-To: <CAAVpQUDwa4cqLf9RJg2o25oN+xKyP+gv+asW4aK7LMNgpE3o2A@mail.gmail.com>
On 7/10/26 6:15 PM, Kuniyuki Iwashima wrote:
> On Fri, Jul 10, 2026 at 4:08 AM Paolo Abeni <pabeni@redhat.com> wrote:
>> On 7/3/26 2:09 AM, Kuniyuki Iwashima wrote:
>>> @@ -800,9 +831,15 @@ static int ipvlan_device_event(struct notifier_block *unused,
>>> if (dev->reg_state != NETREG_UNREGISTERING)
>>> break;
>>>
>>> - list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode)
>>> - ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
>>> - &lst_kill);
>>> + list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) {
>>> +#if IS_ENABLED(CONFIG_IPVTAP)
>>> + if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops)
>>> + __ipvtap_dellink_ptr(ipvlan->dev, &lst_kill);
>>> + else
>>> +#endif
>>> + __ipvlan_link_delete(ipvlan->dev, &lst_kill);
>>
>> I'm not sure if it's worthy a repost, but what about adding a
>> link_delete_unlocked() cb to `struct ipvl_dev *`? IMHO should make this
>> code more straight forward.
>
> Since port is allocated in ndo_init(), ipvtap needs to wrap it
> and overwrite port->ops everytime for one time (or at ->newlink),
> but I don't have strong opinion here.
>
> It would be nicer if I could follow up since this series gets 5-days
> penalty on Suie due to too many Sashiko false-positive, get_net(),
> and a build warning due to xchg() for veth, and we will likely lose
> context next week :)
FTR, I'm not strictly following Suie order - it's more a mix of Suie,
FIFO and common sense. What impact most in series processing is the
sheer amount of patches in PW vs avail time.
/P
^ permalink raw reply
* Re: [PATCH review-only 00/17] zcrx RQ improvements and dynamic memory provisioning
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
Looks like the last patch wasn't delivered, I'm going to
resend.
On 7/11/26 10:11, Pavel Begunkov wrote:
> Sending it out mainly to trigger review bots. The first half improves
> the refill queue implementation and improves refilling limits, which
> shows up when niovs are heavily fragmented like with large rx pages.
> The 2nd half adds dynamic backing memory provisioning.
>
> Pavel Begunkov (17):
> io_uring/zcrx: scale refilling with large pages
> io_uring/zcrx: move RQ head/tail to separate cache lines
> io_uring/zcrx: add RQ iterator
> io_uring/zcrx: cache RQ tail
> io_uring/zcrx: coalesce same-niov RQEs on refill
> io_uring/zcrx: constify area_reg on import
> io_uring/zcrx: add helper for deriving area token
> io_uring/zcrx: don't pass ifq_reg to area creation
> io_uring/zcrx: split dmabuf unmap and release
> io_uring/zcrx: unmap under netdev lock
> io_uring/zcrx: split append out of area creation
> io_uring/zcrx: move freelist lock to struct zcrx
> io_uring/zcrx: array of areas
> io_uring/zcrx: pass area_id to __zcrx_create_area()
> io_uring/zcrx: add dynamic area creation
> io_urint/zcrx: narrow var scope in io_zcrx_recv_skb()
> io_uring/zcrx: don't reload skb_shinfo
>
> include/uapi/linux/io_uring/zcrx.h | 7 +
> io_uring/query.c | 2 +-
> io_uring/zcrx.c | 445 +++++++++++++++++++++--------
> io_uring/zcrx.h | 15 +-
> 4 files changed, 345 insertions(+), 124 deletions(-)
>
--
Pavel Begunkov
^ permalink raw reply
* [PATCH RESEND review-only 00/17] zcrx RQ improvements and dynamic memory provisioning
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
Sending it out mainly to trigger review bots. The first half improves
the refill queue implementation and improves refilling limits, which
shows up when niovs are heavily fragmented like with large rx pages.
The 2nd half adds dynamic backing memory provisioning.
Pavel Begunkov (17):
io_uring/zcrx: scale refilling with large pages
io_uring/zcrx: move RQ head/tail to separate cache lines
io_uring/zcrx: add RQ iterator
io_uring/zcrx: cache RQ tail
io_uring/zcrx: coalesce same-niov RQEs on refill
io_uring/zcrx: constify area_reg on import
io_uring/zcrx: add helper for deriving area token
io_uring/zcrx: don't pass ifq_reg to area creation
io_uring/zcrx: split dmabuf unmap and release
io_uring/zcrx: unmap under netdev lock
io_uring/zcrx: split append out of area creation
io_uring/zcrx: move freelist lock to struct zcrx
io_uring/zcrx: array of areas
io_uring/zcrx: pass area_id to __zcrx_create_area()
io_uring/zcrx: add dynamic area creation
io_urint/zcrx: narrow var scope in io_zcrx_recv_skb()
io_uring/zcrx: don't reload skb_shinfo
include/uapi/linux/io_uring/zcrx.h | 7 +
io_uring/query.c | 2 +-
io_uring/zcrx.c | 445 +++++++++++++++++++++--------
io_uring/zcrx.h | 15 +-
4 files changed, 345 insertions(+), 124 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH review-only 01/17] io_uring/zcrx: scale refilling with large pages
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
io_zcrx_ring_refill() caps the loop by mixing the max number of
allocated netmems and the number of available RQEs together, which
caps the number of entries to process the pp cache size. As a result,
when niovs are heavily fragmented, the refilling logic allocates only a
small number of niovs per call on average and sometimes even none.
Keep a separate counter for the number of processed RQ entries, which is
capped by a roughly calculated from the page size value to keep the
cache full. And separately break if it allocates enough niovs.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 6bd71435e475..8348413d6d24 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -28,6 +28,13 @@
#include "zcrx.h"
#include "rsrc.h"
+#define ZCRX_MAX_FRAGS_PER_PAGE MAX(PAGE_SIZE / 1024, 1)
+/*
+ * We need a reasonable limit to be able to fill in 64 entries on average
+ * for 1500 byte MTU. Over-estimate it to keep it pow2.
+ */
+#define ZCRX_REFILL_CAP MIN(64 * ZCRX_MAX_FRAGS_PER_PAGE, 1024)
+
#define IO_ZCRX_AREA_SUPPORTED_FLAGS (IORING_ZCRX_AREA_DMABUF)
#define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING)
@@ -1125,17 +1132,15 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp,
{
struct zcrx_rq *rq = &ifq->rq;
unsigned int mask = rq->nr_entries - 1;
- unsigned int entries;
+ unsigned int rqes_left;
unsigned allocated = 0;
guard(spinlock_bh)(&rq->lock);
- entries = zcrx_rq_entries(rq);
- entries = min_t(unsigned, entries, to_alloc);
- if (unlikely(!entries))
- return 0;
+ rqes_left = zcrx_rq_entries(rq);
+ rqes_left = min_t(unsigned, rqes_left, ZCRX_REFILL_CAP);
- do {
+ for (; rqes_left; rqes_left--) {
struct io_uring_zcrx_rqe *rqe = zcrx_next_rqe(rq, mask);
struct net_iov *niov;
netmem_ref netmem;
@@ -1156,7 +1161,9 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp,
netmems[allocated] = netmem;
allocated++;
- } while (--entries);
+ if (allocated >= to_alloc)
+ break;
+ }
smp_store_release(&rq->ring->head, rq->cached_head);
return allocated;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 02/17] io_uring/zcrx: move RQ head/tail to separate cache lines
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
RQ head and tail are currently put into the same cache line, which can
cause false sharing problems when refill is run on another CPU. Put them
into separate cache lines.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/query.c | 2 +-
io_uring/zcrx.c | 8 ++++----
io_uring/zcrx.h | 7 ++++++-
3 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/io_uring/query.c b/io_uring/query.c
index d529d94aa8f4..2e7b893cc8f0 100644
--- a/io_uring/query.c
+++ b/io_uring/query.c
@@ -38,7 +38,7 @@ static ssize_t io_query_zcrx(union io_query_data *data)
e->register_flags = ZCRX_SUPPORTED_REG_FLAGS;
e->area_flags = IORING_ZCRX_AREA_DMABUF;
e->nr_ctrl_opcodes = __ZCRX_CTRL_LAST;
- e->rq_hdr_size = sizeof(struct io_uring);
+ e->rq_hdr_size = sizeof(struct zcrx_rq_hdr);
e->rq_hdr_alignment = L1_CACHE_BYTES;
e->features = ZCRX_FEATURES;
e->__resv2 = 0;
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 8348413d6d24..c4a9a663eba4 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -380,9 +380,9 @@ static void io_zcrx_get_niov_uref(struct net_iov *niov)
static void io_fill_zcrx_offsets(struct io_uring_zcrx_offsets *offsets)
{
- offsets->head = offsetof(struct io_uring, head);
- offsets->tail = offsetof(struct io_uring, tail);
- offsets->rqes = ALIGN(sizeof(struct io_uring), L1_CACHE_BYTES);
+ offsets->head = offsetof(struct zcrx_rq_hdr, head);
+ offsets->tail = offsetof(struct zcrx_rq_hdr, tail);
+ offsets->rqes = ALIGN(sizeof(struct zcrx_rq_hdr), L1_CACHE_BYTES);
}
static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx,
@@ -410,7 +410,7 @@ static int io_allocate_rbuf_ring(struct io_ring_ctx *ctx,
return ret;
ptr = io_region_get_ptr(&ifq->rq_region);
- ifq->rq.ring = (struct io_uring *)ptr;
+ ifq->rq.ring = (struct zcrx_rq_hdr *)ptr;
ifq->rq.rqes = (struct io_uring_zcrx_rqe *)(ptr + off);
memset(ifq->rq.ring, 0, sizeof(*ifq->rq.ring));
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index fa00900e479e..3cdfa4415d62 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -43,9 +43,14 @@ struct io_zcrx_area {
struct io_zcrx_mem mem;
};
+struct zcrx_rq_hdr {
+ u32 head ____cacheline_aligned_in_smp;
+ u32 tail ____cacheline_aligned_in_smp;
+};
+
struct zcrx_rq {
spinlock_t lock;
- struct io_uring *ring;
+ struct zcrx_rq_hdr *ring;
struct io_uring_zcrx_rqe *rqes;
u32 cached_head;
u32 nr_entries;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 03/17] io_uring/zcrx: add RQ iterator
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
Add a iterator structure and helper functions for the refill queue
processing to avoid polluting io_zcrx_ring_refill() with extra state
and logic once it's extended in following patches.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index c4a9a663eba4..45b178afbbc3 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -1088,6 +1088,10 @@ void io_unregister_zcrx(struct io_ring_ctx *ctx)
xa_destroy(&ctx->zcrx_ctxs);
}
+struct zcrx_rq_iter {
+ int rqes_left;
+};
+
static inline u32 zcrx_rq_entries(struct zcrx_rq *rq)
{
u32 entries;
@@ -1103,6 +1107,24 @@ static struct io_uring_zcrx_rqe *zcrx_next_rqe(struct zcrx_rq *rq, unsigned mask
return &rq->rqes[idx];
}
+static inline void zcrx_rq_iter_init(struct zcrx_rq_iter *it,
+ struct zcrx_rq *rq)
+{
+ it->rqes_left = min_t(unsigned, zcrx_rq_entries(rq), ZCRX_REFILL_CAP);
+}
+
+static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it,
+ struct zcrx_rq *rq,
+ struct io_uring_zcrx_rqe **rqe)
+{
+ it->rqes_left--;
+ if (unlikely(it->rqes_left < 0))
+ return false;
+
+ *rqe = zcrx_next_rqe(rq, rq->nr_entries - 1);
+ return true;
+}
+
static inline bool io_parse_rqe(struct io_uring_zcrx_rqe *rqe,
struct io_zcrx_ifq *ifq,
struct net_iov **ret_niov)
@@ -1131,17 +1153,15 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp,
netmem_ref *netmems, unsigned to_alloc)
{
struct zcrx_rq *rq = &ifq->rq;
- unsigned int mask = rq->nr_entries - 1;
- unsigned int rqes_left;
+ struct io_uring_zcrx_rqe *rqe;
+ struct zcrx_rq_iter it;
unsigned allocated = 0;
guard(spinlock_bh)(&rq->lock);
- rqes_left = zcrx_rq_entries(rq);
- rqes_left = min_t(unsigned, rqes_left, ZCRX_REFILL_CAP);
+ zcrx_rq_iter_init(&it, rq);
- for (; rqes_left; rqes_left--) {
- struct io_uring_zcrx_rqe *rqe = zcrx_next_rqe(rq, mask);
+ while (zcrx_rq_iter_next(&it, rq, &rqe)) {
struct net_iov *niov;
netmem_ref netmem;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 04/17] io_uring/zcrx: cache RQ tail
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
The RQ tail is updated by the user space. Cache it to reduce cache line
bouncing. Refilling now tries to exhaust the previous batch of rqes, but
since it could be too low, the iterator is allowed to recalculate the
rqes to process once after synching the tail value.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 27 +++++++++++++++++++++------
io_uring/zcrx.h | 1 +
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 45b178afbbc3..1b8d748b35e7 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -1090,16 +1090,22 @@ void io_unregister_zcrx(struct io_ring_ctx *ctx)
struct zcrx_rq_iter {
int rqes_left;
+ bool flushed;
};
-static inline u32 zcrx_rq_entries(struct zcrx_rq *rq)
+static inline u32 __zcrx_rq_entries(struct zcrx_rq *rq)
{
- u32 entries;
+ u32 entries = rq->cached_tail - rq->cached_head;
- entries = smp_load_acquire(&rq->ring->tail) - rq->cached_head;
return min(entries, rq->nr_entries);
}
+static inline u32 zcrx_rq_entries(struct zcrx_rq *rq)
+{
+ rq->cached_tail = smp_load_acquire(&rq->ring->tail);
+ return __zcrx_rq_entries(rq);
+}
+
static struct io_uring_zcrx_rqe *zcrx_next_rqe(struct zcrx_rq *rq, unsigned mask)
{
unsigned int idx = rq->cached_head++ & mask;
@@ -1110,7 +1116,8 @@ static struct io_uring_zcrx_rqe *zcrx_next_rqe(struct zcrx_rq *rq, unsigned mask
static inline void zcrx_rq_iter_init(struct zcrx_rq_iter *it,
struct zcrx_rq *rq)
{
- it->rqes_left = min_t(unsigned, zcrx_rq_entries(rq), ZCRX_REFILL_CAP);
+ it->rqes_left = min_t(unsigned, __zcrx_rq_entries(rq), ZCRX_REFILL_CAP);
+ it->flushed = false;
}
static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it,
@@ -1118,8 +1125,16 @@ static inline bool zcrx_rq_iter_next(struct zcrx_rq_iter *it,
struct io_uring_zcrx_rqe **rqe)
{
it->rqes_left--;
- if (unlikely(it->rqes_left < 0))
- return false;
+ if (unlikely(it->rqes_left < 0)) {
+ if (it->flushed)
+ return false;
+ rq->cached_tail = smp_load_acquire(&rq->ring->tail);
+ it->rqes_left = min_t(unsigned, __zcrx_rq_entries(rq),
+ ZCRX_REFILL_CAP);
+ it->flushed = true;
+ if (--it->rqes_left < 0)
+ return false;
+ }
*rqe = zcrx_next_rqe(rq, rq->nr_entries - 1);
return true;
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index 3cdfa4415d62..0eb7ea35a9ff 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -53,6 +53,7 @@ struct zcrx_rq {
struct zcrx_rq_hdr *ring;
struct io_uring_zcrx_rqe *rqes;
u32 cached_head;
+ u32 cached_tail;
u32 nr_entries;
};
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 05/17] io_uring/zcrx: coalesce same-niov RQEs on refill
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
With large rx piages I often see >10 sequential RQEs referring to the
same niov. Instead of putting them one by one, count such RQEs during
parsing and batch refcounting for the niov.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 56 +++++++++++++++++++++++++++++++------------------
1 file changed, 36 insertions(+), 20 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 1b8d748b35e7..cb73dca3c1ee 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -359,16 +359,16 @@ static inline atomic_t *io_get_user_counter(struct net_iov *niov)
return &area->user_refs[net_iov_idx(niov)];
}
-static bool io_zcrx_put_niov_uref(struct net_iov *niov)
+static bool io_zcrx_put_niov_uref(struct net_iov *niov, unsigned refs)
{
atomic_t *uref = io_get_user_counter(niov);
int old;
old = atomic_read(uref);
do {
- if (unlikely(old == 0))
+ if (unlikely(old < refs))
return false;
- } while (!atomic_try_cmpxchg(uref, &old, old - 1));
+ } while (!atomic_try_cmpxchg(uref, &old, old - refs));
return true;
}
@@ -1163,6 +1163,22 @@ static inline bool io_parse_rqe(struct io_uring_zcrx_rqe *rqe,
return true;
}
+static bool zcrx_put_refill_niov(struct net_iov *niov, struct page_pool *pp,
+ unsigned refs)
+{
+ netmem_ref netmem = net_iov_to_netmem(niov);
+
+ if (!io_zcrx_put_niov_uref(niov, refs))
+ return false;
+ if (page_pool_unref_netmem(netmem, refs) != 0)
+ return false;
+ if (unlikely(niov->desc.pp != pp)) {
+ io_zcrx_return_niov(niov);
+ return false;
+ }
+ return true;
+}
+
static unsigned io_zcrx_ring_refill(struct page_pool *pp,
struct io_zcrx_ifq *ifq,
netmem_ref *netmems, unsigned to_alloc)
@@ -1170,34 +1186,34 @@ static unsigned io_zcrx_ring_refill(struct page_pool *pp,
struct zcrx_rq *rq = &ifq->rq;
struct io_uring_zcrx_rqe *rqe;
struct zcrx_rq_iter it;
+ struct net_iov *niov = NULL;
+ unsigned niov_refs = 0;
unsigned allocated = 0;
guard(spinlock_bh)(&rq->lock);
zcrx_rq_iter_init(&it, rq);
- while (zcrx_rq_iter_next(&it, rq, &rqe)) {
- struct net_iov *niov;
- netmem_ref netmem;
+ while (allocated < to_alloc - 1 && zcrx_rq_iter_next(&it, rq, &rqe)) {
+ struct net_iov *next_niov;
- if (!io_parse_rqe(rqe, ifq, &niov))
- continue;
- if (!io_zcrx_put_niov_uref(niov))
+ if (!io_parse_rqe(rqe, ifq, &next_niov))
continue;
-
- netmem = net_iov_to_netmem(niov);
- if (!page_pool_unref_and_test(netmem))
- continue;
-
- if (unlikely(niov->desc.pp != pp)) {
- io_zcrx_return_niov(niov);
+ if (niov == next_niov) {
+ niov_refs++;
continue;
}
+ if (niov && zcrx_put_refill_niov(niov, pp, niov_refs)) {
+ netmems[allocated] = net_iov_to_netmem(niov);
+ allocated++;
+ }
+ niov = next_niov;
+ niov_refs = 1;
+ }
- netmems[allocated] = netmem;
+ if (niov && zcrx_put_refill_niov(niov, pp, niov_refs)) {
+ netmems[allocated] = net_iov_to_netmem(niov);
allocated++;
- if (allocated >= to_alloc)
- break;
}
smp_store_release(&rq->ring->head, rq->cached_head);
@@ -1401,7 +1417,7 @@ static void zcrx_return_buffers(netmem_ref *netmems, unsigned nr)
netmem_ref netmem = netmems[i];
struct net_iov *niov = netmem_to_net_iov(netmem);
- if (!io_zcrx_put_niov_uref(niov))
+ if (!io_zcrx_put_niov_uref(niov, 1))
continue;
if (!page_pool_unref_and_test(netmem))
continue;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 06/17] io_uring/zcrx: constify area_reg on import
From: Pavel Begunkov @ 2026-07-11 10:39 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
io_import_area() doesn't modify its struct io_uring_zcrx_area_reg
argument, add const to enforce that, it'll make later modifications
easier.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index cb73dca3c1ee..9f21ae61b862 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -136,7 +136,7 @@ static void io_release_dmabuf(struct io_zcrx_mem *mem)
static int io_import_dmabuf(struct io_zcrx_ifq *ifq,
struct io_zcrx_mem *mem,
- struct io_uring_zcrx_area_reg *area_reg)
+ const struct io_uring_zcrx_area_reg *area_reg)
{
unsigned long off = (unsigned long)area_reg->addr;
unsigned long len = (unsigned long)area_reg->len;
@@ -208,7 +208,7 @@ static unsigned long io_count_account_pages(struct page **pages, unsigned nr_pag
static int io_import_umem(struct io_zcrx_ifq *ifq,
struct io_zcrx_mem *mem,
- struct io_uring_zcrx_area_reg *area_reg)
+ const struct io_uring_zcrx_area_reg *area_reg)
{
struct page **pages;
int nr_pages, ret;
@@ -274,7 +274,7 @@ static void io_release_area_mem(struct io_zcrx_mem *mem)
static int io_import_area(struct io_zcrx_ifq *ifq,
struct io_zcrx_mem *mem,
- struct io_uring_zcrx_area_reg *area_reg)
+ const struct io_uring_zcrx_area_reg *area_reg)
{
int ret;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 07/17] io_uring/zcrx: add helper for deriving area token
From: Pavel Begunkov @ 2026-07-11 10:40 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
Add zcrx_area_id_to_token() to deduplicate the way the area token is
calculated out of the area index.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 9f21ae61b862..cfbfbd262f90 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -39,6 +39,11 @@
#define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING)
+static inline u64 zcrx_area_id_to_token(u32 area_id)
+{
+ return (u64)area_id << IORING_ZCRX_AREA_SHIFT;
+}
+
static inline struct io_zcrx_ifq *io_pp_to_ifq(struct page_pool *pp)
{
return pp->mp_priv;
@@ -527,7 +532,7 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
area->free_count = nr_iovs;
/* we're only supporting one area per ifq for now */
area->area_id = 0;
- area_reg->rq_area_token = (u64)area->area_id << IORING_ZCRX_AREA_SHIFT;
+ area_reg->rq_area_token = zcrx_area_id_to_token(area->area_id);
spin_lock_init(&area->freelist_lock);
ret = io_zcrx_append_area(ifq, area);
@@ -1525,7 +1530,7 @@ static bool io_zcrx_queue_cqe(struct io_kiocb *req, struct net_iov *niov,
area = io_zcrx_iov_to_area(niov);
offset = off + (net_iov_idx(niov) << ifq->niov_shift);
rcqe = (struct io_uring_zcrx_cqe *)(cqe + 1);
- rcqe->off = offset + ((u64)area->area_id << IORING_ZCRX_AREA_SHIFT);
+ rcqe->off = offset + zcrx_area_id_to_token(area->area_id);
rcqe->__pad = 0;
return true;
}
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 08/17] io_uring/zcrx: don't pass ifq_reg to area creation
From: Pavel Begunkov @ 2026-07-11 10:40 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
We might want to create an area without having an instance of struct
io_uring_zcrx_ifq_reg. Extract a helper that doesn't have the ifq
registration structure as an argument but takes the buf length
explicitly.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index cfbfbd262f90..79099a78f8cd 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -460,21 +460,22 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq,
return 0;
}
-static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
+static int __zcrx_create_area(struct io_zcrx_ifq *ifq,
struct io_uring_zcrx_area_reg *area_reg,
- struct io_uring_zcrx_ifq_reg *reg)
+ u32 rx_buf_len)
{
int buf_size_shift = PAGE_SHIFT;
struct io_zcrx_area *area;
unsigned nr_iovs;
int i, ret;
- if (reg->rx_buf_len) {
- if (!is_power_of_2(reg->rx_buf_len) ||
- reg->rx_buf_len < PAGE_SIZE)
+ if (rx_buf_len) {
+ if (!is_power_of_2(rx_buf_len) || rx_buf_len < PAGE_SIZE)
return -EINVAL;
- buf_size_shift = ilog2(reg->rx_buf_len);
+ buf_size_shift = ilog2(rx_buf_len);
}
+ if (WARN_ON_ONCE(ifq->niov_shift))
+ return -EINVAL;
if (!ifq->dev && buf_size_shift != PAGE_SHIFT)
return -EOPNOTSUPP;
@@ -544,6 +545,13 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
return ret;
}
+static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
+ struct io_uring_zcrx_area_reg *area_reg,
+ struct io_uring_zcrx_ifq_reg *reg)
+{
+ return __zcrx_create_area(ifq, area_reg, reg->rx_buf_len);
+}
+
static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx)
{
struct io_zcrx_ifq *ifq;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 09/17] io_uring/zcrx: split dmabuf unmap and release
From: Pavel Begunkov @ 2026-07-11 10:40 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
Until now unmapping and destroying dmabuf were the same thing. To keep
it consistent with non-dmabuf, split it into two separate helpers. Unmap
destroys mappings and attachements as it should, and release only
putting down the dmabuf fd reference.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 79099a78f8cd..86e8046e98c4 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -121,21 +121,25 @@ static int io_populate_area_dma(struct io_zcrx_ifq *ifq,
return 0;
}
-static void io_release_dmabuf(struct io_zcrx_mem *mem)
+static void io_unmap_dmabuf(struct io_zcrx_mem *mem)
{
if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER))
return;
-
if (mem->sgt)
dma_buf_unmap_attachment_unlocked(mem->attach, mem->sgt,
DMA_FROM_DEVICE);
if (mem->attach)
dma_buf_detach(mem->dmabuf, mem->attach);
- if (mem->dmabuf)
- dma_buf_put(mem->dmabuf);
-
mem->sgt = NULL;
mem->attach = NULL;
+}
+
+static void io_release_dmabuf(struct io_zcrx_mem *mem)
+{
+ if (!IS_ENABLED(CONFIG_DMA_SHARED_BUFFER))
+ return;
+ if (mem->dmabuf)
+ dma_buf_put(mem->dmabuf);
mem->dmabuf = NULL;
}
@@ -190,6 +194,7 @@ static int io_import_dmabuf(struct io_zcrx_ifq *ifq,
mem->size = len;
return 0;
err:
+ io_unmap_dmabuf(mem);
io_release_dmabuf(mem);
return ret;
}
@@ -317,7 +322,7 @@ static void io_zcrx_unmap_area(struct io_zcrx_ifq *ifq,
}
if (area->mem.is_dmabuf) {
- io_release_dmabuf(&area->mem);
+ io_unmap_dmabuf(&area->mem);
} else {
dma_unmap_sgtable(ifq->dev, &area->mem.page_sg_table,
DMA_FROM_DEVICE, IO_DMA_ATTR);
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 10/17] io_uring/zcrx: unmap under netdev lock
From: Pavel Begunkov @ 2026-07-11 10:40 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
Make sure we unmap areas while closing a queue.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 86e8046e98c4..4936d92f6339 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -311,6 +311,9 @@ static void io_zcrx_unmap_area(struct io_zcrx_ifq *ifq,
{
int i;
+ if (!area)
+ return;
+
guard(mutex)(&ifq->pp_lock);
if (!area->is_mapped)
return;
@@ -438,7 +441,8 @@ static void io_free_rbuf_ring(struct io_zcrx_ifq *ifq)
static void io_zcrx_free_area(struct io_zcrx_ifq *ifq,
struct io_zcrx_area *area)
{
- io_zcrx_unmap_area(ifq, area);
+ if (WARN_ON_ONCE(area->is_mapped))
+ return;
io_release_area_mem(&area->mem);
if (area->mem.account_pages)
@@ -545,8 +549,10 @@ static int __zcrx_create_area(struct io_zcrx_ifq *ifq,
if (!ret)
return 0;
err:
- if (area)
+ if (area) {
+ io_zcrx_unmap_area(ifq, area);
io_zcrx_free_area(ifq, area);
+ }
return ret;
}
@@ -600,11 +606,12 @@ static void io_close_queue(struct io_zcrx_ifq *ifq)
}
if (netdev) {
- if (ifq->if_rxq != -1) {
- netdev_lock(netdev);
+ netdev_lock(netdev);
+ if (ifq->if_rxq != -1)
netif_mp_close_rxq(netdev, ifq->if_rxq, &p);
- netdev_unlock(netdev);
- }
+
+ io_zcrx_unmap_area(ifq, ifq->area);
+ netdev_unlock(netdev);
netdev_put(netdev, &netdev_tracker);
}
ifq->if_rxq = -1;
@@ -1389,8 +1396,7 @@ static void io_pp_uninstall(void *mp_priv, struct netdev_rx_queue *rxq)
struct io_zcrx_ifq *ifq = mp_priv;
io_zcrx_drop_netdev(ifq);
- if (ifq->area)
- io_zcrx_unmap_area(ifq, ifq->area);
+ io_zcrx_unmap_area(ifq, ifq->area);
p->mp_ops = NULL;
p->mp_priv = NULL;
--
2.54.0
^ permalink raw reply related
* [PATCH review-only 11/17] io_uring/zcrx: split append out of area creation
From: Pavel Begunkov @ 2026-07-11 10:40 UTC (permalink / raw)
To: io-uring; +Cc: asml.silence, netdev
In-Reply-To: <cover.1783616211.git.asml.silence@gmail.com>
A preparation patch, move appending an area from __zcrx_create_area()
to the caller.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 4936d92f6339..40cabf4384d1 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -471,6 +471,7 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq,
static int __zcrx_create_area(struct io_zcrx_ifq *ifq,
struct io_uring_zcrx_area_reg *area_reg,
+ struct io_zcrx_area **res_area,
u32 rx_buf_len)
{
int buf_size_shift = PAGE_SHIFT;
@@ -544,10 +545,8 @@ static int __zcrx_create_area(struct io_zcrx_ifq *ifq,
area->area_id = 0;
area_reg->rq_area_token = zcrx_area_id_to_token(area->area_id);
spin_lock_init(&area->freelist_lock);
-
- ret = io_zcrx_append_area(ifq, area);
- if (!ret)
- return 0;
+ *res_area = area;
+ return 0;
err:
if (area) {
io_zcrx_unmap_area(ifq, area);
@@ -560,7 +559,19 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
struct io_uring_zcrx_area_reg *area_reg,
struct io_uring_zcrx_ifq_reg *reg)
{
- return __zcrx_create_area(ifq, area_reg, reg->rx_buf_len);
+ struct io_zcrx_area *area;
+ int ret;
+
+ ret = __zcrx_create_area(ifq, area_reg, &area, reg->rx_buf_len);
+ if (ret)
+ return ret;
+
+ ret = io_zcrx_append_area(ifq, area);
+ if (ret) {
+ io_zcrx_free_area(ifq, area);
+ return ret;
+ }
+ return 0;
}
static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx)
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox