* [RFC 01/10] net: pass ubuf to custom sg_from_iter callbacks
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 02/10] net: reject zcrx skbs to not registered devices Pavel Begunkov
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
ubuf_info and callbacks for chunking zerocopy iterators into skbs comes
in pairs in msghdr, and in the future the callback will need to know
which ubuf_info it was called with. We can't derive it from the skb as
some paths set it after the call, so pass it in.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
include/linux/socket.h | 2 +-
io_uring/net.c | 8 ++++----
net/core/datagram.c | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f1d1..5ae24847f5c4 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -90,7 +90,7 @@ struct msghdr {
unsigned int msg_flags; /* flags on received message */
__kernel_size_t msg_controllen; /* ancillary data buffer length */
struct ubuf_info *msg_ubuf;
- int (*sg_from_iter)(struct sk_buff *skb,
+ int (*sg_from_iter)(struct sk_buff *skb, struct ubuf_info *ubuf,
struct iov_iter *from, size_t length);
};
diff --git a/io_uring/net.c b/io_uring/net.c
index 00a7df803b99..cf273d6f02b1 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -116,9 +116,9 @@ struct io_recvzc {
struct io_zcrx_ifq *ifq;
};
-static int io_sg_from_iter_iovec(struct sk_buff *skb,
+static int io_sg_from_iter_iovec(struct sk_buff *skb, struct ubuf_info *ubuf,
struct iov_iter *from, size_t length);
-static int io_sg_from_iter(struct sk_buff *skb,
+static int io_sg_from_iter(struct sk_buff *skb, struct ubuf_info *ubuf,
struct iov_iter *from, size_t length);
int io_shutdown_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
@@ -1447,14 +1447,14 @@ int io_send_zc_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
return 0;
}
-static int io_sg_from_iter_iovec(struct sk_buff *skb,
+static int io_sg_from_iter_iovec(struct sk_buff *skb, struct ubuf_info *ubuf,
struct iov_iter *from, size_t length)
{
skb_zcopy_downgrade_managed(skb);
return zerocopy_fill_skb_from_iter(skb, from, length);
}
-static int io_sg_from_iter(struct sk_buff *skb,
+static int io_sg_from_iter(struct sk_buff *skb, struct ubuf_info *ubuf,
struct iov_iter *from, size_t length)
{
struct skb_shared_info *shinfo = skb_shinfo(skb);
diff --git a/net/core/datagram.c b/net/core/datagram.c
index c285c6465923..f15886f40efc 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -753,7 +753,7 @@ int __zerocopy_sg_from_iter(struct msghdr *msg, struct sock *sk,
int ret;
if (msg && msg->msg_ubuf && msg->sg_from_iter)
- ret = msg->sg_from_iter(skb, from, length);
+ ret = msg->sg_from_iter(skb, msg->msg_ubuf, from, length);
else if (binding)
ret = zerocopy_fill_skb_from_devmem(skb, from, length, binding);
else
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 02/10] net: reject zcrx skbs to not registered devices
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
2026-07-11 10:48 ` [RFC 01/10] net: pass ubuf to custom sg_from_iter callbacks Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 03/10] io_uring/zcrx: switch to pcpu refcounting Pavel Begunkov
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
Tx of netmems that weren't created for the targeted device should be
rejected. Devmem TCP does it by looking up the net device in devmem TCP
private strucures. Introduce a netdev pointer in struct net_iov_area and
set it for zcrx so that we can check it in a more generic way. Keep the
existing devmem TCP path for the RFC version of the patch.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
include/net/netmem.h | 1 +
io_uring/zcrx.c | 3 +++
net/core/dev.c | 10 ++++++++--
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/include/net/netmem.h b/include/net/netmem.h
index bccacd21b6c3..71024c7ce884 100644
--- a/include/net/netmem.h
+++ b/include/net/netmem.h
@@ -103,6 +103,7 @@ struct net_iov_area {
struct net_iov *niovs;
size_t num_niovs;
+ struct net_device *netdev;
/* Offset into the dma-buf where this chunk starts. */
unsigned long base_virtual;
};
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index 7ad52f499f87..ef82e064e796 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -516,6 +516,7 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
goto err;
}
+ area->nia.netdev = ifq->netdev;
area->free_count = nr_iovs;
/* we're only supporting one area per ifq for now */
area->area_id = 0;
@@ -554,6 +555,7 @@ static void io_zcrx_drop_netdev(struct io_zcrx_ifq *ifq)
if (!ifq->netdev)
return;
+ WRITE_ONCE(ifq->area->nia.netdev, NULL);
netdev_put(ifq->netdev, &ifq->netdev_tracker);
ifq->netdev = NULL;
}
@@ -568,6 +570,7 @@ static void io_close_queue(struct io_zcrx_ifq *ifq)
};
scoped_guard(mutex, &ifq->pp_lock) {
+ WRITE_ONCE(ifq->area->nia.netdev, NULL);
netdev = ifq->netdev;
netdev_tracker = ifq->netdev_tracker;
ifq->netdev = NULL;
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..703b55778c32 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4004,9 +4004,15 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb,
shinfo = skb_shinfo(skb);
if (shinfo->nr_frags > 0) {
+ struct net_device *trgt_dev;
+
niov = netmem_to_net_iov(skb_frag_netmem(&shinfo->frags[0]));
- if (net_is_devmem_iov(niov) &&
- READ_ONCE(net_devmem_iov_binding(niov)->dev) != dev)
+ if (net_is_devmem_iov(niov))
+ trgt_dev = READ_ONCE(net_devmem_iov_binding(niov)->dev);
+ else
+ trgt_dev = READ_ONCE(net_iov_owner(niov)->netdev);
+
+ if (trgt_dev != dev)
goto out_free;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 03/10] io_uring/zcrx: switch to pcpu refcounting
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
2026-07-11 10:48 ` [RFC 01/10] net: pass ubuf to custom sg_from_iter callbacks Pavel Begunkov
2026-07-11 10:48 ` [RFC 02/10] net: reject zcrx skbs to not registered devices Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 04/10] io_uring/zcrx: prepare areas to be exported for tx Pavel Begunkov
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
We'll need a faster way to pin a zcrx instance for get_netmem(). Switch
refcounting to percpu_ref.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/zcrx.c | 36 ++++++++++++++++++++++++++++++------
io_uring/zcrx.h | 4 +++-
2 files changed, 33 insertions(+), 7 deletions(-)
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index ef82e064e796..f501fc75d7b6 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -32,6 +32,8 @@
#define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING)
+static void ifq_pcpu_release(struct percpu_ref *ref);
+
static inline struct io_zcrx_ifq *io_pp_to_ifq(struct page_pool *pp)
{
return pp->mp_priv;
@@ -535,16 +537,22 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx)
{
struct io_zcrx_ifq *ifq;
+ int ret;
ifq = kzalloc_obj(*ifq);
if (!ifq)
return NULL;
+ ret = percpu_ref_init(&ifq->refs, ifq_pcpu_release, 0, GFP_KERNEL_ACCOUNT);
+ if (ret) {
+ kfree(ifq);
+ return NULL;
+ }
+ percpu_ref_get(&ifq->refs);
ifq->if_rxq = -1;
spin_lock_init(&ifq->ctx_lock);
spin_lock_init(&ifq->rq.lock);
mutex_init(&ifq->pp_lock);
- refcount_set(&ifq->refs, 1);
refcount_set(&ifq->user_refs, 1);
return ifq;
}
@@ -606,13 +614,28 @@ static void io_zcrx_ifq_free(struct io_zcrx_ifq *ifq)
io_free_rbuf_ring(ifq);
free_uid(ifq->user);
mutex_destroy(&ifq->pp_lock);
+ percpu_ref_exit(&ifq->refs);
kfree(ifq);
}
+static inline void zcrx_release_work(struct work_struct *work)
+{
+ struct io_zcrx_ifq *ifq = container_of(work, struct io_zcrx_ifq, release_work);
+
+ io_zcrx_ifq_free(ifq);
+}
+
+static void ifq_pcpu_release(struct percpu_ref *ref)
+{
+ struct io_zcrx_ifq *ifq = container_of(ref, struct io_zcrx_ifq, refs);
+
+ INIT_WORK(&ifq->release_work, zcrx_release_work);
+ queue_work(system_wq, &ifq->release_work);
+}
+
static void io_put_zcrx_ifq(struct io_zcrx_ifq *ifq)
{
- if (refcount_dec_and_test(&ifq->refs))
- io_zcrx_ifq_free(ifq);
+ percpu_ref_put(&ifq->refs);
}
static void io_zcrx_return_niov_freelist(struct net_iov *niov)
@@ -683,6 +706,7 @@ static void zcrx_unregister_user(struct io_zcrx_ifq *ifq, struct io_ring_ctx *ct
if (refcount_dec_and_test(&ifq->user_refs)) {
io_close_queue(ifq);
io_zcrx_scrub(ifq);
+ percpu_ref_kill(&ifq->refs);
}
}
@@ -727,7 +751,7 @@ static int zcrx_export(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq,
if (!mem_is_zero(ce, sizeof(*ce)))
return -EINVAL;
- refcount_inc(&ifq->refs);
+ percpu_ref_get(&ifq->refs);
refcount_inc(&ifq->user_refs);
file = anon_inode_create_getfile("[zcrx]", &zcrx_box_fops,
@@ -784,7 +808,7 @@ static int import_zcrx(struct io_ring_ctx *ctx,
return -EBADF;
ifq = file->private_data;
- refcount_inc(&ifq->refs);
+ percpu_ref_get(&ifq->refs);
refcount_inc(&ifq->user_refs);
scoped_guard(mutex, &ctx->mmap_lock) {
@@ -1285,7 +1309,7 @@ static int io_pp_zc_init(struct page_pool *pp)
if (pp->p.dma_dir != DMA_FROM_DEVICE)
return -EOPNOTSUPP;
- refcount_inc(&ifq->refs);
+ percpu_ref_get(&ifq->refs);
return 0;
}
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index fa00900e479e..205b9b40c74d 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -7,6 +7,7 @@
#include <linux/socket.h>
#include <net/page_pool/types.h>
#include <net/net_trackers.h>
+#include <linux/percpu-refcount.h>
#define ZCRX_SUPPORTED_REG_FLAGS (ZCRX_REG_IMPORT | ZCRX_REG_NODEV)
#define ZCRX_FEATURES (ZCRX_FEATURE_RX_PAGE_SIZE |\
@@ -64,7 +65,7 @@ struct io_zcrx_ifq {
struct device *dev;
struct net_device *netdev;
netdevice_tracker netdev_tracker;
- refcount_t refs;
+ struct percpu_ref refs;
/* counts userspace facing users like io_uring */
refcount_t user_refs;
@@ -81,6 +82,7 @@ struct io_zcrx_ifq {
u32 fired_notifs;
u64 notif_data;
struct zcrx_notif_stats *notif_stats;
+ struct work_struct release_work;
};
#if defined(CONFIG_IO_URING_ZCRX)
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 04/10] io_uring/zcrx: prepare areas to be exported for tx
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (2 preceding siblings ...)
2026-07-11 10:48 ` [RFC 03/10] io_uring/zcrx: switch to pcpu refcounting Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 05/10] io_uring/rsrc: introduce buf registration structure Pavel Begunkov
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
We're going to piggy back tx on top of zcrx, for that we need a separate
niov list without the pp field set and make sure net core can reference
them via get_netmem, which pins the zcrx instance.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
include/linux/io_uring/net.h | 10 ++++++
io_uring/zcrx.c | 65 +++++++++++++++++++++++++++++++++---
io_uring/zcrx.h | 10 ++++++
net/core/skbuff.c | 3 ++
4 files changed, 83 insertions(+), 5 deletions(-)
diff --git a/include/linux/io_uring/net.h b/include/linux/io_uring/net.h
index b58f39fed4d5..19f31f0d38d5 100644
--- a/include/linux/io_uring/net.h
+++ b/include/linux/io_uring/net.h
@@ -2,6 +2,8 @@
#ifndef _LINUX_IO_URING_NET_H
#define _LINUX_IO_URING_NET_H
+#include <net/netmem.h>
+
struct io_uring_cmd;
#if defined(CONFIG_IO_URING)
@@ -15,4 +17,12 @@ static inline int io_uring_cmd_sock(struct io_uring_cmd *cmd,
}
#endif
+#if defined(CONFIG_IO_URING_ZCRX)
+void zcrx_ref_niov(struct net_iov *niov);
+#else
+static inline void zcrx_ref_niov(struct net_iov *niov)
+{
+}
+#endif
+
#endif
diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c
index f501fc75d7b6..28398f0d0014 100644
--- a/io_uring/zcrx.c
+++ b/io_uring/zcrx.c
@@ -9,6 +9,7 @@
#include <linux/rtnetlink.h>
#include <linux/skbuff_ref.h>
#include <linux/anon_inodes.h>
+#include <linux/io_uring/net.h>
#include <net/page_pool/helpers.h>
#include <net/page_pool/memory_provider.h>
@@ -100,6 +101,8 @@ static int io_populate_area_dma(struct io_zcrx_ifq *ifq,
if (net_mp_niov_set_dma_addr(niov, dma))
return -EFAULT;
+ if (net_mp_niov_set_dma_addr(&area->tx_niovs[niov_idx], dma))
+ return -EFAULT;
sg_len -= niov_size;
dma += niov_size;
niov_idx++;
@@ -118,7 +121,7 @@ static void io_release_dmabuf(struct io_zcrx_mem *mem)
if (mem->sgt)
dma_buf_unmap_attachment_unlocked(mem->attach, mem->sgt,
- DMA_FROM_DEVICE);
+ DMA_BIDIRECTIONAL);
if (mem->attach)
dma_buf_detach(mem->dmabuf, mem->attach);
if (mem->dmabuf)
@@ -162,7 +165,7 @@ static int io_import_dmabuf(struct io_zcrx_ifq *ifq,
goto err;
}
- mem->sgt = dma_buf_map_attachment_unlocked(mem->attach, DMA_FROM_DEVICE);
+ mem->sgt = dma_buf_map_attachment_unlocked(mem->attach, DMA_BIDIRECTIONAL);
if (IS_ERR(mem->sgt)) {
ret = PTR_ERR(mem->sgt);
mem->sgt = NULL;
@@ -226,7 +229,7 @@ static int io_import_umem(struct io_zcrx_ifq *ifq,
if (ifq->dev) {
ret = dma_map_sgtable(ifq->dev, &mem->page_sg_table,
- DMA_FROM_DEVICE, IO_DMA_ATTR);
+ DMA_BIDIRECTIONAL, IO_DMA_ATTR);
if (ret < 0)
goto out_err;
mapped = true;
@@ -247,7 +250,7 @@ static int io_import_umem(struct io_zcrx_ifq *ifq,
out_err:
if (mapped)
dma_unmap_sgtable(ifq->dev, &mem->page_sg_table,
- DMA_FROM_DEVICE, IO_DMA_ATTR);
+ DMA_BIDIRECTIONAL, IO_DMA_ATTR);
sg_free_table(&mem->page_sg_table);
unpin_user_pages(pages, nr_pages);
kvfree(pages);
@@ -310,7 +313,7 @@ static void io_zcrx_unmap_area(struct io_zcrx_ifq *ifq,
io_release_dmabuf(&area->mem);
} else {
dma_unmap_sgtable(ifq->dev, &area->mem.page_sg_table,
- DMA_FROM_DEVICE, IO_DMA_ATTR);
+ DMA_BIDIRECTIONAL, IO_DMA_ATTR);
}
}
@@ -494,6 +497,11 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
if (!area->nia.niovs)
goto err;
+ area->tx_niovs = kvmalloc_objs(area->tx_niovs[0], nr_iovs,
+ GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ if (!area->tx_niovs)
+ goto err;
+
area->freelist = kvmalloc_array(nr_iovs, sizeof(area->freelist[0]),
GFP_KERNEL_ACCOUNT | __GFP_ZERO);
if (!area->freelist)
@@ -510,6 +518,7 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,
net_iov_init(niov, &area->nia, NET_IOV_IOURING);
area->freelist[i] = i;
atomic_set(&area->user_refs[i], 0);
+ net_iov_init(&area->tx_niovs[i], &area->nia, NET_IOV_IOURING);
}
if (ifq->dev) {
@@ -741,6 +750,13 @@ static const struct file_operations zcrx_box_fops = {
.release = zcrx_box_release,
};
+void zcrx_ref_niov(struct net_iov *niov)
+{
+ struct io_zcrx_ifq *ifq = io_zcrx_iov_to_area(niov)->ifq;
+
+ percpu_ref_get(&ifq->refs);
+}
+
static int zcrx_export(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq,
struct zcrx_ctrl *ctrl, void __user *arg)
{
@@ -1817,3 +1833,42 @@ int io_zcrx_recv(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
sock_rps_record_flow(sk);
return io_zcrx_tcp_recvmsg(req, ifq, sk, flags, issue_flags, len);
}
+
+int io_zcrx_fill_tx_skb(struct sk_buff *skb, struct io_zcrx_ifq *zcrx,
+ struct iov_iter *from, size_t length)
+{
+ int i = skb_shinfo(skb)->nr_frags;
+ unsigned niovs_emitted = 0;
+ struct io_zcrx_area *area = zcrx->area;
+ unsigned niov_size = 1U << zcrx->niov_shift;
+
+ if (i && skb_frags_readable(skb))
+ return -EINVAL;
+ length = min(length, iov_iter_count(from));
+
+ while (length) {
+ struct net_iov *niov;
+ size_t offset, size, niov_off;
+
+ if (i == MAX_SKB_FRAGS) {
+ percpu_ref_get_many(&zcrx->refs, niovs_emitted);
+ return -EMSGSIZE;
+ }
+
+ offset = (size_t)iter_iov_addr(from);
+ niov = &area->tx_niovs[offset >> zcrx->niov_shift];
+ niov_off = offset & (niov_size - 1);
+ size = min(length, niov_size - niov_off);
+ size = min(size, iter_iov_len(from));
+
+ skb_add_rx_frag_netmem(skb, i, net_iov_to_netmem(niov), niov_off,
+ size, size);
+ iov_iter_advance(from, size);
+ length -= size;
+ i++;
+ niovs_emitted++;
+ }
+
+ percpu_ref_get_many(&zcrx->refs, niovs_emitted);
+ return 0;
+}
diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h
index 205b9b40c74d..406e0399bd7b 100644
--- a/io_uring/zcrx.h
+++ b/io_uring/zcrx.h
@@ -32,6 +32,7 @@ struct io_zcrx_area {
struct net_iov_area nia;
struct io_zcrx_ifq *ifq;
atomic_t *user_refs;
+ struct net_iov *tx_niovs;
bool is_mapped;
u16 area_id;
@@ -96,6 +97,10 @@ int io_zcrx_recv(struct io_kiocb *req, struct io_zcrx_ifq *ifq,
unsigned issue_flags, unsigned int *len);
struct io_mapped_region *io_zcrx_get_region(struct io_ring_ctx *ctx,
unsigned int id);
+
+int io_zcrx_fill_tx_skb(struct sk_buff *skb, struct io_zcrx_ifq *zcrx,
+ struct iov_iter *from, size_t length);
+
#else
static inline int io_register_zcrx(struct io_ring_ctx *ctx,
struct io_uring_zcrx_ifq_reg __user *arg)
@@ -124,6 +129,11 @@ static inline int io_zcrx_ctrl(struct io_ring_ctx *ctx,
{
return -EOPNOTSUPP;
}
+static inline int io_zcrx_fill_tx_skb(struct sk_buff *skb, struct io_zcrx_ifq *zcrx,
+ struct iov_iter *from, size_t length)
+{
+ return -EOPNOTSUPP;
+}
#endif
int io_recvzc(struct io_kiocb *req, unsigned int issue_flags);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 18dabb4e9cfa..f871e3f2299b 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -92,6 +92,7 @@
#include <linux/user_namespace.h>
#include <linux/indirect_call_wrapper.h>
#include <linux/textsearch.h>
+#include <linux/io_uring/net.h>
#include "dev.h"
#include "devmem.h"
@@ -7476,6 +7477,8 @@ void __get_netmem(netmem_ref netmem)
if (net_is_devmem_iov(niov))
net_devmem_get_net_iov(netmem_to_net_iov(netmem));
+ else if (niov->type == NET_IOV_IOURING)
+ zcrx_ref_niov(niov);
}
EXPORT_SYMBOL(__get_netmem);
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 05/10] io_uring/rsrc: introduce buf registration structure
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (3 preceding siblings ...)
2026-07-11 10:48 ` [RFC 04/10] io_uring/zcrx: prepare areas to be exported for tx Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 06/10] io_uring/rsrc: extend buffer update Pavel Begunkov
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
In preparation to following changes, instead of passing an iovec for
buffer registration introduce a new structure. It'll be moved to uapi
later, but for now it's initialised early from a user provided iovec.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/rsrc.c | 47 ++++++++++++++++++++++++++++++++---------------
1 file changed, 32 insertions(+), 15 deletions(-)
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 8d0f2ee24e0c..8af371ba6c06 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -27,8 +27,13 @@ struct io_rsrc_update {
u32 offset;
};
+struct io_uring_regbuf_desc {
+ __u64 uaddr;
+ __u64 size;
+};
+
static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
- struct iovec *iov);
+ struct io_uring_regbuf_desc *desc);
static int hpage_acct_ref(struct io_ring_ctx *ctx, struct page *hpage,
bool *acct_new)
@@ -81,6 +86,15 @@ static bool hpage_acct_unref(struct io_ring_ctx *ctx, struct page *hpage)
#define IO_CACHED_BVECS_SEGS 32
+static void io_iov_to_regbuf_desc(const struct iovec *iov,
+ struct io_uring_regbuf_desc *desc)
+{
+ *desc = (struct io_uring_regbuf_desc) {
+ .uaddr = (u64)(uintptr_t)iov->iov_base,
+ .size = iov->iov_len,
+ };
+}
+
int __io_account_mem(struct user_struct *user, unsigned long nr_pages)
{
unsigned long page_limit, cur_pages, new_pages;
@@ -381,6 +395,7 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
return -EINVAL;
for (done = 0; done < nr_args; done++) {
+ struct io_uring_regbuf_desc desc;
struct io_rsrc_node *node;
u64 tag = 0;
@@ -394,7 +409,9 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
err = -EFAULT;
break;
}
- node = io_sqe_buffer_register(ctx, iov);
+
+ io_iov_to_regbuf_desc(iov, &desc);
+ node = io_sqe_buffer_register(ctx, &desc);
if (IS_ERR(node)) {
err = PTR_ERR(node);
break;
@@ -853,26 +870,26 @@ bool io_check_coalesce_buffer(struct page **page_array, int nr_pages,
}
static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
- struct iovec *iov)
+ struct io_uring_regbuf_desc *desc)
{
+ unsigned long uaddr = (unsigned long)desc->uaddr;
+ size_t size = desc->size;
struct io_mapped_ubuf *imu = NULL;
struct page **pages = NULL;
struct io_rsrc_node *node;
unsigned long off;
- size_t size;
int ret, nr_pages, i;
struct io_imu_folio_data data;
bool coalesced = false;
- if (!iov->iov_base) {
- if (iov->iov_len)
+ if (!uaddr) {
+ if (size)
return ERR_PTR(-EFAULT);
/* remove the buffer without installing a new one */
return NULL;
}
- ret = io_validate_user_buf_range((unsigned long)iov->iov_base,
- iov->iov_len);
+ ret = io_validate_user_buf_range(uaddr, size);
if (ret)
return ERR_PTR(ret);
@@ -881,8 +898,7 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
return ERR_PTR(-ENOMEM);
ret = -ENOMEM;
- pages = io_pin_pages((unsigned long) iov->iov_base, iov->iov_len,
- &nr_pages);
+ pages = io_pin_pages(uaddr, size, &nr_pages);
if (IS_ERR(pages)) {
ret = PTR_ERR(pages);
pages = NULL;
@@ -904,10 +920,9 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
if (ret)
goto done;
- size = iov->iov_len;
/* store original address for later verification */
- imu->ubuf = (unsigned long) iov->iov_base;
- imu->len = iov->iov_len;
+ imu->ubuf = uaddr;
+ imu->len = size;
imu->folio_shift = PAGE_SHIFT;
imu->release = io_release_ubuf;
imu->priv = imu;
@@ -917,7 +932,7 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
imu->folio_shift = data.folio_shift;
refcount_set(&imu->refs, 1);
- off = (unsigned long)iov->iov_base & ~PAGE_MASK;
+ off = uaddr & ~PAGE_MASK;
if (coalesced)
off += data.first_folio_page_idx << PAGE_SHIFT;
@@ -969,6 +984,7 @@ int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg,
memset(iov, 0, sizeof(*iov));
for (i = 0; i < nr_args; i++) {
+ struct io_uring_regbuf_desc desc;
struct io_rsrc_node *node;
u64 tag = 0;
@@ -992,7 +1008,8 @@ int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg,
}
}
- node = io_sqe_buffer_register(ctx, iov);
+ io_iov_to_regbuf_desc(iov, &desc);
+ node = io_sqe_buffer_register(ctx, &desc);
if (IS_ERR(node)) {
ret = PTR_ERR(node);
break;
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 06/10] io_uring/rsrc: extend buffer update
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (4 preceding siblings ...)
2026-07-11 10:48 ` [RFC 05/10] io_uring/rsrc: introduce buf registration structure Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 07/10] io_uring/rsrc: add uncloneable regbuf flag Pavel Begunkov
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
We need to pass more information to buffer registration than we can fit
into a single struct iovec. This patch allows users to optionally pass
struct io_uring_regbuf_desc. Apart from having more space for future use
cases, it also introduces registration types.
Currently, the type can be either of IO_REGBUF_TYPE_UADDR, which mirrors
the iovec path, or IO_REGBUF_TYPE_EMPTY for leaving a buffer table slot
empty. The next patch introduces a dmabuf backed type, and can be useful
for other extensions like splicing a list of user addresses (i.e.
iovec[]), interoperability with zcrx, kernel allocated memory like was
brough up by Cristoph. Note, the type only represents a registration
option, which is distinct from how io_uring internally stores it.
The flags field is not used yet but always useful to have, e.g. we can
encode read-only / write-only restrictions using it.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
include/uapi/linux/io_uring.h | 27 +++++++++++++-
io_uring/rsrc.c | 69 ++++++++++++++++++++++-------------
2 files changed, 69 insertions(+), 27 deletions(-)
diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h
index 909fb7aea638..98b259901185 100644
--- a/include/uapi/linux/io_uring.h
+++ b/include/uapi/linux/io_uring.h
@@ -790,13 +790,38 @@ struct io_uring_rsrc_update {
struct io_uring_rsrc_update2 {
__u32 offset;
- __u32 resv;
+ __u32 flags;
__aligned_u64 data;
__aligned_u64 tags;
__u32 nr;
__u32 resv2;
};
+/* struct io_uring_rsrc_update2::flags */
+enum io_uring_rsrc_reg_flags {
+ /*
+ * Use the extended descriptor format for buffer updates,
+ * see struct io_uring_regbuf_desc
+ */
+ IORING_RSRC_UPDATE_EXTENDED = 1U << 1,
+};
+
+/* Buffer registration type, passed in struct io_uring_regbuf_desc::type */
+enum io_uring_regbuf_type {
+ IO_REGBUF_TYPE_EMPTY,
+ IO_REGBUF_TYPE_UADDR,
+
+ __IO_REGBUF_TYPE_MAX,
+};
+
+struct io_uring_regbuf_desc {
+ __u32 type; /* enum io_uring_regbuf_type */
+ __u32 flags;
+ __u64 size;
+ __u64 uaddr;
+ __u64 __resv[7];
+};
+
/* Skip updating fd indexes set to this value in the fd table */
#define IORING_REGISTER_FILES_SKIP (-2)
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 8af371ba6c06..24fc3232a66a 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -27,11 +27,6 @@ struct io_rsrc_update {
u32 offset;
};
-struct io_uring_regbuf_desc {
- __u64 uaddr;
- __u64 size;
-};
-
static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
struct io_uring_regbuf_desc *desc);
@@ -90,9 +85,12 @@ static void io_iov_to_regbuf_desc(const struct iovec *iov,
struct io_uring_regbuf_desc *desc)
{
*desc = (struct io_uring_regbuf_desc) {
+ .type = IO_REGBUF_TYPE_UADDR,
.uaddr = (u64)(uintptr_t)iov->iov_base,
.size = iov->iov_len,
};
+ if (!desc->uaddr)
+ desc->type = IO_REGBUF_TYPE_EMPTY;
}
int __io_account_mem(struct user_struct *user, unsigned long nr_pages)
@@ -323,6 +321,8 @@ static int __io_sqe_files_update(struct io_ring_ctx *ctx,
return -ENXIO;
if (up->offset + nr_args > ctx->file_table.data.nr)
return -EINVAL;
+ if (up->flags)
+ return -EINVAL;
for (done = 0; done < nr_args; done++) {
u64 tag = 0;
@@ -382,9 +382,8 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
struct io_uring_rsrc_update2 *up,
unsigned int nr_args)
{
+ bool extended = up->flags & IORING_RSRC_UPDATE_EXTENDED;
u64 __user *tags = u64_to_user_ptr(up->tags);
- struct iovec fast_iov, *iov;
- struct iovec __user *uvec;
u64 user_data = up->data;
__u32 done;
int i, err;
@@ -393,29 +392,49 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
return -ENXIO;
if (up->offset + nr_args > ctx->buf_table.nr)
return -EINVAL;
+ if (up->flags & ~IORING_RSRC_UPDATE_EXTENDED)
+ return -EINVAL;
for (done = 0; done < nr_args; done++) {
struct io_uring_regbuf_desc desc;
struct io_rsrc_node *node;
u64 tag = 0;
- uvec = u64_to_user_ptr(user_data);
- iov = iovec_from_user(uvec, 1, 1, &fast_iov, io_is_compat(ctx));
- if (IS_ERR(iov)) {
- err = PTR_ERR(iov);
- break;
- }
if (tags && copy_from_user(&tag, &tags[done], sizeof(tag))) {
err = -EFAULT;
break;
}
- io_iov_to_regbuf_desc(iov, &desc);
+ if (extended) {
+ if (copy_from_user(&desc, u64_to_user_ptr(user_data),
+ sizeof(desc))) {
+ err = -EFAULT;
+ break;
+ }
+ user_data += sizeof(desc);
+ } else {
+ struct iovec __user *uvec = u64_to_user_ptr(user_data);
+ struct iovec fast_iov, *iov;
+
+ if (io_is_compat(ctx))
+ user_data += sizeof(struct compat_iovec);
+ else
+ user_data += sizeof(struct iovec);
+
+ iov = iovec_from_user(uvec, 1, 1, &fast_iov, io_is_compat(ctx));
+ if (IS_ERR(iov)) {
+ err = PTR_ERR(iov);
+ break;
+ }
+ io_iov_to_regbuf_desc(iov, &desc);
+ }
+
node = io_sqe_buffer_register(ctx, &desc);
if (IS_ERR(node)) {
err = PTR_ERR(node);
break;
}
+
if (tag) {
if (!node) {
err = -EINVAL;
@@ -426,10 +445,6 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
i = array_index_nospec(up->offset + done, ctx->buf_table.nr);
io_reset_rsrc_node(ctx, &ctx->buf_table, i);
ctx->buf_table.nodes[i] = node;
- if (io_is_compat(ctx))
- user_data += sizeof(struct compat_iovec);
- else
- user_data += sizeof(struct iovec);
}
return done ? done : err;
}
@@ -464,7 +479,7 @@ int io_register_files_update(struct io_ring_ctx *ctx, void __user *arg,
memset(&up, 0, sizeof(up));
if (copy_from_user(&up, arg, sizeof(struct io_uring_rsrc_update)))
return -EFAULT;
- if (up.resv || up.resv2)
+ if (up.resv2)
return -EINVAL;
return __io_register_rsrc_update(ctx, IORING_RSRC_FILE, &up, nr_args);
}
@@ -478,7 +493,7 @@ int io_register_rsrc_update(struct io_ring_ctx *ctx, void __user *arg,
return -EINVAL;
if (copy_from_user(&up, arg, sizeof(up)))
return -EFAULT;
- if (!up.nr || up.resv || up.resv2)
+ if (!up.nr || up.resv2)
return -EINVAL;
return __io_register_rsrc_update(ctx, type, &up, up.nr);
}
@@ -578,12 +593,9 @@ int io_files_update(struct io_kiocb *req, unsigned int issue_flags)
struct io_uring_rsrc_update2 up2;
int ret;
+ memset(&up2, 0, sizeof(up2));
up2.offset = up->offset;
up2.data = up->arg;
- up2.nr = 0;
- up2.tags = 0;
- up2.resv = 0;
- up2.resv2 = 0;
if (up->offset == IORING_FILE_INDEX_ALLOC) {
ret = io_files_update_with_index_alloc(req, issue_flags);
@@ -882,8 +894,13 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
struct io_imu_folio_data data;
bool coalesced = false;
- if (!uaddr) {
- if (size)
+ if (desc->type >= __IO_REGBUF_TYPE_MAX)
+ return ERR_PTR(-EINVAL);
+ if (!mem_is_zero(&desc->__resv, sizeof(desc->__resv)))
+ return ERR_PTR(-EINVAL);
+
+ if (desc->type == IO_REGBUF_TYPE_EMPTY) {
+ if (uaddr || size)
return ERR_PTR(-EFAULT);
/* remove the buffer without installing a new one */
return NULL;
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 07/10] io_uring/rsrc: add uncloneable regbuf flag
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (5 preceding siblings ...)
2026-07-11 10:48 ` [RFC 06/10] io_uring/rsrc: extend buffer update Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 08/10] io_uring/rsrc: add regbuf import flags Pavel Begunkov
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
It's hard to implement cloning if the internal structure needs to be
mutable and/or relies on other ring resources. In preparation to such
buffer types, add a flag indicating that the buffer can't be cloned. It
might be possible to add cloning in the future for them, but that would
likely need reallocating the structure and reacquiring resources in case
by case manner.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/rsrc.c | 5 +++++
io_uring/rsrc.h | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 24fc3232a66a..d57e8a0380b5 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -1381,6 +1381,11 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctx
if (!src_node) {
dst_node = NULL;
} else {
+ if (src_node->buf->flags & IO_REGBUF_F_UNCLONEABLE) {
+ io_rsrc_data_free(ctx, &data);
+ return -ENOMEM;
+ }
+
dst_node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER);
if (!dst_node) {
io_rsrc_data_free(ctx, &data);
diff --git a/io_uring/rsrc.h b/io_uring/rsrc.h
index 98ae8ef51009..83aa86e6f320 100644
--- a/io_uring/rsrc.h
+++ b/io_uring/rsrc.h
@@ -29,7 +29,8 @@ enum {
};
enum {
- IO_REGBUF_F_KBUF = 1,
+ IO_REGBUF_F_KBUF = 1 << 0,
+ IO_REGBUF_F_UNCLONEABLE = 1 << 1,
};
struct io_mapped_ubuf {
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 08/10] io_uring/rsrc: add regbuf import flags
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (6 preceding siblings ...)
2026-07-11 10:48 ` [RFC 07/10] io_uring/rsrc: add uncloneable regbuf flag Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 09/10] io_uring/rsrc: add zcrx backed registered buffers Pavel Begunkov
2026-07-11 10:48 ` [RFC 10/10] io_uring/net: implement device memory send Pavel Begunkov
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
We'll have special registered buffer types that can't be used with all
opcodes and need special handling. Add separate flags to control
registered buffer import, which will be used to specify what kind of
buffers the caller can handle.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/rsrc.c | 8 ++++----
io_uring/rsrc.h | 24 ++++++++++++++++++++----
2 files changed, 24 insertions(+), 8 deletions(-)
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index d57e8a0380b5..05877b4c0ee5 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -1245,9 +1245,9 @@ inline struct io_rsrc_node *io_find_buf_node(struct io_kiocb *req,
return NULL;
}
-int io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
+int __io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
u64 buf_addr, size_t len, int ddir,
- unsigned issue_flags)
+ unsigned issue_flags, unsigned import_flags)
{
struct io_rsrc_node *node;
@@ -1656,9 +1656,9 @@ static int io_kern_bvec_size(struct iovec *iov, unsigned nr_iovs,
return 0;
}
-int io_import_reg_vec(int ddir, struct iov_iter *iter,
+int __io_import_reg_vec(int ddir, struct iov_iter *iter,
struct io_kiocb *req, struct iou_vec *vec,
- unsigned nr_iovs, unsigned issue_flags)
+ unsigned nr_iovs, unsigned issue_flags, unsigned import_flags)
{
struct io_rsrc_node *node;
struct io_mapped_ubuf *imu;
diff --git a/io_uring/rsrc.h b/io_uring/rsrc.h
index 83aa86e6f320..f12abaf63270 100644
--- a/io_uring/rsrc.h
+++ b/io_uring/rsrc.h
@@ -65,12 +65,28 @@ int io_rsrc_data_alloc(struct io_rsrc_data *data, unsigned nr);
struct io_rsrc_node *io_find_buf_node(struct io_kiocb *req,
unsigned issue_flags);
-int io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
+int __io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
u64 buf_addr, size_t len, int ddir,
- unsigned issue_flags);
-int io_import_reg_vec(int ddir, struct iov_iter *iter,
+ unsigned issue_flags, unsigned import_flags);
+int __io_import_reg_vec(int ddir, struct iov_iter *iter,
struct io_kiocb *req, struct iou_vec *vec,
- unsigned nr_iovs, unsigned issue_flags);
+ unsigned nr_iovs, unsigned issue_flags,
+ unsigned import_flags);
+
+static inline int io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
+ u64 buf_addr, size_t len, int ddir,
+ unsigned issue_flags)
+{
+ return __io_import_reg_buf(req, iter, buf_addr, len, ddir, issue_flags, 0);
+}
+
+static inline int io_import_reg_vec(int ddir, struct iov_iter *iter,
+ struct io_kiocb *req, struct iou_vec *vec,
+ unsigned nr_iovs, unsigned issue_flags)
+{
+ return __io_import_reg_vec(ddir, iter, req, vec, nr_iovs, issue_flags, 0);
+}
+
int io_prep_reg_iovec(struct io_kiocb *req, struct iou_vec *iv,
const struct iovec __user *uvec, size_t uvec_segs);
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 09/10] io_uring/rsrc: add zcrx backed registered buffers
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (7 preceding siblings ...)
2026-07-11 10:48 ` [RFC 08/10] io_uring/rsrc: add regbuf import flags Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
2026-07-11 10:48 ` [RFC 10/10] io_uring/net: implement device memory send Pavel Begunkov
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
Allow the user to take an existing zcrx instance and wrap it into a
registered buffer that will later be used for tx path. We don't want
leaking zcrx instances to other rings, so mark the buffer uncloneable.
The buffer also doesn't ping zcrx but relies on that zcrx can't be
unregistered.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
include/uapi/linux/io_uring.h | 1 +
io_uring/rsrc.c | 91 ++++++++++++++++++++++++++++++++++-
io_uring/rsrc.h | 5 ++
3 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h
index 98b259901185..4e86ad3ee977 100644
--- a/include/uapi/linux/io_uring.h
+++ b/include/uapi/linux/io_uring.h
@@ -810,6 +810,7 @@ enum io_uring_rsrc_reg_flags {
enum io_uring_regbuf_type {
IO_REGBUF_TYPE_EMPTY,
IO_REGBUF_TYPE_UADDR,
+ IO_REGBUF_TYPE_ZCRX,
__IO_REGBUF_TYPE_MAX,
};
diff --git a/io_uring/rsrc.c b/io_uring/rsrc.c
index 05877b4c0ee5..583e51748c62 100644
--- a/io_uring/rsrc.c
+++ b/io_uring/rsrc.c
@@ -19,6 +19,7 @@
#include "rsrc.h"
#include "memmap.h"
#include "register.h"
+#include "zcrx.h"
struct io_rsrc_update {
struct file *file;
@@ -881,6 +882,53 @@ bool io_check_coalesce_buffer(struct page **page_array, int nr_pages,
return true;
}
+static void io_release_zcrx(void *priv)
+{
+}
+
+static struct io_rsrc_node *io_register_zcrx_buffer(struct io_ring_ctx *ctx,
+ struct io_uring_regbuf_desc *desc)
+{
+ struct io_mapped_ubuf *imu = NULL;
+ struct io_rsrc_node *node;
+ struct io_zcrx_ifq *zcrx;
+ u32 ifq_idx = desc->uaddr;
+
+
+ if (ifq_idx != desc->uaddr)
+ return ERR_PTR(-EINVAL);
+ node = io_rsrc_node_alloc(ctx, IORING_RSRC_BUFFER);
+ if (!node)
+ return ERR_PTR(-ENOMEM);
+ zcrx = xa_load(&ctx->zcrx_ctxs, ifq_idx);
+ if (!zcrx)
+ return ERR_PTR(-EINVAL);
+
+ WARN_ON_ONCE(!zcrx->area);
+
+ if (zcrx->area->mem.size != desc->size)
+ return ERR_PTR(-EINVAL);
+
+ imu = io_alloc_imu(ctx, 0);
+ if (!imu) {
+ io_cache_free(&ctx->node_cache, node);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ imu->nr_bvecs = 0;
+ /* store original address for later verification */
+ imu->ubuf = 0;
+ imu->len = desc->size;
+ imu->folio_shift = PAGE_SHIFT;
+ imu->release = io_release_zcrx;
+ imu->priv = zcrx;
+ imu->flags = IO_REGBUF_F_UNCLONEABLE | IO_REGBUF_F_ZCRX;
+ imu->dir = IO_IMU_SOURCE;
+ refcount_set(&imu->refs, 1);
+ node->buf = imu;
+ return node;
+}
+
static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
struct io_uring_regbuf_desc *desc)
{
@@ -898,6 +946,8 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,
return ERR_PTR(-EINVAL);
if (!mem_is_zero(&desc->__resv, sizeof(desc->__resv)))
return ERR_PTR(-EINVAL);
+ if (desc->type == IO_REGBUF_TYPE_ZCRX)
+ return io_register_zcrx_buffer(ctx, desc);
if (desc->type == IO_REGBUF_TYPE_EMPTY) {
if (uaddr || size)
@@ -1170,9 +1220,17 @@ static int io_import_kbuf(int ddir, struct iov_iter *iter,
return 0;
}
+static int io_import_from_zcrx(int ddir, struct iov_iter *iter,
+ struct io_mapped_ubuf *imu,
+ u64 buf_addr, size_t len)
+{
+ iov_iter_ubuf(iter, ddir, (void *)(unsigned long)buf_addr, len);
+ return 0;
+}
+
static int io_import_fixed(int ddir, struct iov_iter *iter,
struct io_mapped_ubuf *imu,
- u64 buf_addr, size_t len)
+ u64 buf_addr, size_t len, unsigned import_flags)
{
const struct bio_vec *bvec;
size_t folio_mask;
@@ -1185,6 +1243,13 @@ static int io_import_fixed(int ddir, struct iov_iter *iter,
return ret;
if (!(imu->dir & (1 << ddir)))
return -EFAULT;
+
+ if (imu->flags & IO_REGBUF_F_ZCRX) {
+ if (unlikely(!(import_flags & IO_REGBUF_IMPORT_ALLOW_ZCRX)))
+ return -EINVAL;
+ return io_import_from_zcrx(ddir, iter, imu, buf_addr, len);
+ }
+
if (unlikely(!len)) {
iov_iter_bvec(iter, ddir, NULL, 0, 0);
return 0;
@@ -1254,7 +1319,7 @@ int __io_import_reg_buf(struct io_kiocb *req, struct iov_iter *iter,
node = io_find_buf_node(req, issue_flags);
if (!node)
return -EFAULT;
- return io_import_fixed(ddir, iter, node->buf, buf_addr, len);
+ return io_import_fixed(ddir, iter, node->buf, buf_addr, len, import_flags);
}
static int io_buffer_acct_cloned_hpages(struct io_ring_ctx *ctx,
@@ -1656,6 +1721,22 @@ static int io_kern_bvec_size(struct iovec *iov, unsigned nr_iovs,
return 0;
}
+static int import_reg_vec_zcrx(int ddir, struct iov_iter *iter, struct iovec *iov,
+ unsigned nr_iovs)
+{
+ size_t size;
+ unsigned i;
+
+ for (i = 0; i < nr_iovs; i++) {
+ if (check_add_overflow(size, (size_t)iov[i].iov_len, &size))
+ return -EOVERFLOW;
+ }
+ if (size > MAX_RW_COUNT)
+ return -EINVAL;
+ iov_iter_init(iter, ddir, iov, nr_iovs, size);
+ return 0;
+}
+
int __io_import_reg_vec(int ddir, struct iov_iter *iter,
struct io_kiocb *req, struct iou_vec *vec,
unsigned nr_iovs, unsigned issue_flags, unsigned import_flags)
@@ -1676,6 +1757,12 @@ int __io_import_reg_vec(int ddir, struct iov_iter *iter,
iovec_off = vec->nr - nr_iovs;
iov = vec->iovec + iovec_off;
+ if (imu->flags & IO_REGBUF_F_ZCRX) {
+ if (unlikely(!(import_flags & IO_REGBUF_IMPORT_ALLOW_ZCRX)))
+ return -EINVAL;
+ return import_reg_vec_zcrx(ddir, iter, iov, nr_iovs);
+ }
+
if (imu->flags & IO_REGBUF_F_KBUF) {
int ret = io_kern_bvec_size(iov, nr_iovs, imu, &nr_segs);
diff --git a/io_uring/rsrc.h b/io_uring/rsrc.h
index f12abaf63270..a44f7dfcd470 100644
--- a/io_uring/rsrc.h
+++ b/io_uring/rsrc.h
@@ -31,6 +31,11 @@ enum {
enum {
IO_REGBUF_F_KBUF = 1 << 0,
IO_REGBUF_F_UNCLONEABLE = 1 << 1,
+ IO_REGBUF_F_ZCRX = 1 << 2,
+};
+
+enum {
+ IO_REGBUF_IMPORT_ALLOW_ZCRX = 1 << 0,
};
struct io_mapped_ubuf {
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC 10/10] io_uring/net: implement device memory send
2026-07-11 10:48 [RFC 00/10] io_uring: prototype for device memory tx Pavel Begunkov
` (8 preceding siblings ...)
2026-07-11 10:48 ` [RFC 09/10] io_uring/rsrc: add zcrx backed registered buffers Pavel Begunkov
@ 2026-07-11 10:48 ` Pavel Begunkov
9 siblings, 0 replies; 11+ messages in thread
From: Pavel Begunkov @ 2026-07-11 10:48 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jamal Hadi Salim, io-uring, asml.silence
Enable zerocopy sends with device memory by teaching it how to work with
IO_REGBUF_TYPE_ZCRX. There is no iterator type to represent what we
need, so do a little hack, pass an iovec instead and let a custom
sg_from_iter implementation to fill skbs with netmems.
Signed-off-by: Pavel Begunkov <asml.silence@gmail.com>
---
io_uring/net.c | 29 ++++++++++++++++++++++++-----
io_uring/notif.h | 5 ++++-
2 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/io_uring/net.c b/io_uring/net.c
index cf273d6f02b1..2ffbb59ceee4 100644
--- a/io_uring/net.c
+++ b/io_uring/net.c
@@ -1497,6 +1497,14 @@ static int io_sg_from_iter(struct sk_buff *skb, struct ubuf_info *ubuf,
return ret;
}
+static int io_sg_from_zcrx_iter(struct sk_buff *skb, struct ubuf_info *ubuf,
+ struct iov_iter *from, size_t length)
+{
+ struct io_notif_data *nd = container_of(ubuf, struct io_notif_data, uarg);
+
+ return io_zcrx_fill_tx_skb(skb, nd->zcrx, from, length);
+}
+
static int io_send_zc_import(struct io_kiocb *req,
struct io_async_msghdr *kmsg,
unsigned int issue_flags)
@@ -1510,15 +1518,17 @@ static int io_send_zc_import(struct io_kiocb *req,
notif->buf_index = req->buf_index;
if (!(sr->flags & IORING_SEND_VECTORIZED)) {
- ret = io_import_reg_buf(notif, &kmsg->msg.msg_iter,
- (u64)(uintptr_t)sr->buf, sr->len,
- ITER_SOURCE, issue_flags);
+ ret = __io_import_reg_buf(notif, &kmsg->msg.msg_iter,
+ (u64)(uintptr_t)sr->buf, sr->len,
+ ITER_SOURCE, issue_flags,
+ IO_REGBUF_IMPORT_ALLOW_ZCRX);
} else {
unsigned uvec_segs = kmsg->msg.msg_iter.nr_segs;
- ret = io_import_reg_vec(ITER_SOURCE, &kmsg->msg.msg_iter,
+ ret = __io_import_reg_vec(ITER_SOURCE, &kmsg->msg.msg_iter,
notif, &kmsg->vec, uvec_segs,
- issue_flags);
+ issue_flags,
+ IO_REGBUF_IMPORT_ALLOW_ZCRX);
}
if (unlikely(ret))
@@ -1545,9 +1555,18 @@ int io_sendmsg_zc(struct io_kiocb *req, unsigned int issue_flags)
return -EAGAIN;
if (req->flags & REQ_F_IMPORT_BUFFER) {
+ struct io_mapped_ubuf *buf;
+
ret = io_send_zc_import(req, kmsg, issue_flags);
if (unlikely(ret))
return ret;
+
+ buf = sr->notif->buf_node->buf;
+
+ if (buf->flags & IO_REGBUF_TYPE_ZCRX) {
+ kmsg->msg.sg_from_iter = io_sg_from_zcrx_iter;
+ io_notif_to_data(sr->notif)->zcrx = buf->priv;
+ }
}
msg_flags = sr->msg_flags;
diff --git a/io_uring/notif.h b/io_uring/notif.h
index f3589cfef4a9..2dfd5bf23302 100644
--- a/io_uring/notif.h
+++ b/io_uring/notif.h
@@ -17,7 +17,10 @@ struct io_notif_data {
struct io_notif_data *next;
struct io_notif_data *head;
- unsigned account_pages;
+ union {
+ unsigned account_pages;
+ void *zcrx;
+ };
bool zc_report;
bool zc_used;
bool zc_copied;
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread