From: Alex Markuze <amarkuze@redhat.com>
To: Jens Axboe <axboe@kernel.dk>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Pavel Begunkov <asml.silence@gmail.com>,
Simon Horman <horms@kernel.org>,
Kuniyuki Iwashima <kuniyu@google.com>,
Willem de Bruijn <willemb@google.com>,
Neal Cardwell <ncardwell@google.com>,
John Fastabend <john.fastabend@gmail.com>,
Jakub Sitnicki <jakub@cloudflare.com>,
Jiayuan Chen <jiayuan.chen@linux.dev>,
io-uring@vger.kernel.org, netdev@vger.kernel.org,
bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC PATCH net-next 10/12] io_uring/opaque: add IORING_OPAQUE_CREATE
Date: Tue, 10 Nov 2026 13:20:04 +0000 [thread overview]
Message-ID: <20261110132006.3280986-11-amarkuze@redhat.com> (raw)
In-Reply-To: <20261110132006.3280986-1-amarkuze@redhat.com>
Allow an application to populate an immutable object from its own memory,
for example to retain a generated reply for repeated sends. Add CREATE
to IORING_OP_OPAQUE_OBJ; its objects support the same reads, sends and
FREE as received objects.
sqe->addr3 points to the source and len supplies 1 to INT_MAX bytes;
addr and off must be zero. Preparation rejects malformed fields, sizes
beyond the store's object or capture limits, and inaccessible or
overflowing source ranges. Source memory remains valid through execution.
Issue reserves an object slot and page-rounded backing charge, then
copies synchronously under the ring lock. It uses the compaction page
allocator, preferring compound allocations and falling back to smaller
orders. Each allocation owns an extent and part of the reservation.
Copying proceeds by base page and yields between destination allocations.
CREATE fails immediately on budget exhaustion rather than waiting.
Success returns the length, handle and forwarding-only flag; OBJECT_STAT
marks the dense version as compacted. STAT counts the copied bytes. On
failure no handle is published, and cleanup releases the slot, backing
and unused reservation. A large CREATE holds the ring lock through the
copy and can delay other requests of that ring. There is no per-store
pending-request limit.
Signed-off-by: Alex Markuze <amarkuze@redhat.com>
---
io_uring/opaque.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 127 insertions(+), 2 deletions(-)
diff --git a/io_uring/opaque.c b/io_uring/opaque.c
index 4610e212a800..78ae19a6ad82 100644
--- a/io_uring/opaque.c
+++ b/io_uring/opaque.c
@@ -1884,7 +1884,6 @@ int io_opaque_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
READ_ONCE(sqe->buf_index) || READ_ONCE(sqe->__pad2[0]) ||
cmd > IORING_OPAQUE_AVAILABLE ||
cmd == IORING_OPAQUE_COMPACT || cmd == IORING_OPAQUE_SET_POLICY ||
- cmd == IORING_OPAQUE_CREATE ||
(req->flags & (REQ_F_CQE_SKIP | REQ_F_FIXED_FILE)))
return -EINVAL;
op = io_opaque_req_alloc(req, store);
@@ -1899,6 +1898,21 @@ int io_opaque_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
op->offset = READ_ONCE(sqe->off);
op->length = READ_ONCE(sqe->len);
op->addr = u64_to_user_ptr(READ_ONCE(sqe->addr3));
+ if (cmd == IORING_OPAQUE_CREATE) {
+ u64 pointer = READ_ONCE(sqe->addr3);
+
+ if (op->handle || op->offset || !op->length ||
+ op->length > INT_MAX || !pointer)
+ return -EINVAL;
+ if (op->length > store->config.max_object_size ||
+ ALIGN((u64)op->length, PAGE_SIZE) > io_opaque_rx_limit(store))
+ return -EMSGSIZE;
+ if (check_add_overflow(pointer, (u64)op->length, &end) ||
+ pointer > ULONG_MAX || end > ULONG_MAX ||
+ !access_ok(op->addr, op->length))
+ return -EFAULT;
+ return 0;
+ }
if (check_add_overflow(op->offset, (u64)op->length, &end))
return -EOVERFLOW;
if (cmd <= IORING_OPAQUE_READ_OBJECT || cmd == IORING_OPAQUE_AVAILABLE) {
@@ -2046,12 +2060,110 @@ static int io_opaque_stat(struct io_opaque_req *op)
return io_opaque_stat_copy(op, &stat, sizeof(stat));
}
+/* Construction is synchronous under the ring lock, like READ_OBJECT copies. */
+static int io_opaque_create_build(struct io_opaque_req *op)
+{
+ struct io_opaque_store *store = op->store;
+ struct mem_cgroup *old = set_active_memcg(store->memcg);
+ u64 done = 0;
+ int ret = 0;
+
+ while (done < op->length) {
+ u64 pages_left = DIV_ROUND_UP_ULL(op->length - done, PAGE_SIZE);
+ struct io_opaque_extent *extent;
+ unsigned int order;
+ struct page *page;
+ u32 chunk, written = 0;
+
+ page = io_opaque_dest_page(store, pages_left, &order);
+ if (!page) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ chunk = min_t(u64, op->length - done, PAGE_SIZE << order);
+ extent = io_opaque_extent_new(store, page, 0, chunk, done, true);
+ if (IS_ERR(extent)) {
+ put_page(page);
+ ret = PTR_ERR(extent);
+ goto out;
+ }
+ op->create_reserved -= page_size(page);
+ extent->backing->copied = true;
+ list_add_tail(&extent->list, &op->data->extents);
+ while (written < chunk) {
+ u32 bytes = min_t(u32, chunk - written, PAGE_SIZE);
+ unsigned long left;
+ void *mapped;
+
+ mapped = kmap_local_page(page + (written >> PAGE_SHIFT));
+ left = copy_from_user(mapped, op->addr + done + written, bytes);
+ kunmap_local(mapped);
+ atomic64_add(bytes - left, &store->copied);
+ if (left) {
+ ret = -EFAULT;
+ goto out;
+ }
+ written += bytes;
+ }
+ done += chunk;
+ op->data->length = done;
+ cond_resched();
+ }
+ ret = io_opaque_vector(op->data);
+ if (!ret)
+ op->data->dense = true;
+out:
+ set_active_memcg(old);
+ return ret;
+}
+
+static int io_opaque_create(struct io_opaque_req *op, unsigned int issue_flags)
+{
+ struct io_ring_ctx *ctx = op->req->ctx;
+ u64 charge = ALIGN((u64)op->length, PAGE_SIZE);
+ u64 object_flags = 0;
+ struct mem_cgroup *old;
+ int ret;
+
+ io_ring_submit_lock(ctx, issue_flags);
+ if (READ_ONCE(op->store->dead)) {
+ ret = -ESHUTDOWN;
+ goto unlock;
+ }
+ old = set_active_memcg(op->store->memcg);
+ op->data = io_opaque_data_alloc();
+ set_active_memcg(old);
+ if (!op->data) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+ ret = io_opaque_slot_reserve(op);
+ if (ret)
+ goto unlock;
+ if (!io_opaque_charge(op->store, charge, true)) {
+ ret = -ENOBUFS;
+ goto unlock;
+ }
+ op->create_reserved = charge;
+ ret = io_opaque_create_build(op);
+ if (!ret)
+ ret = io_opaque_publish(op, &object_flags);
+unlock:
+ io_req_set_res32(op->req, ret, 0, ret >= 0 ? op->handle : 0, object_flags);
+ if (ret < 0)
+ req_set_fail(op->req);
+ io_ring_submit_unlock(ctx, issue_flags);
+ return IOU_COMPLETE;
+}
+
int io_opaque_issue(struct io_kiocb *req, unsigned int issue_flags)
{
struct io_opaque_req *op = req->async_data;
struct io_opaque_store *store = op->store;
int ret;
+ if (op->op == IORING_OPAQUE_CREATE)
+ return io_opaque_create(op, issue_flags);
io_ring_submit_lock(req->ctx, issue_flags);
/* Pair with the cancellation search before any opaque wait enrollment. */
if (io_wq_cancel_seen(req, issue_flags)) {
@@ -2224,6 +2336,16 @@ bool io_opaque_cancel_all(struct io_ring_ctx *ctx, struct io_uring_task *tctx,
return found;
}
+static void io_opaque_create_release(struct io_opaque_req *op)
+{
+ io_opaque_data_put(op->store, op->data);
+ op->data = NULL;
+ if (op->create_reserved) {
+ io_opaque_uncharge(op->store, op->create_reserved);
+ op->create_reserved = 0;
+ }
+}
+
void io_opaque_cleanup(struct io_kiocb *req)
{
struct io_opaque_req *op = req->async_data;
@@ -2287,7 +2409,10 @@ void io_opaque_cleanup(struct io_kiocb *req)
}
if (unlikely(op->vec))
io_opaque_vec_cleanup(op);
- io_opaque_data_put(store, op->data);
+ if (op->op == IORING_OPAQUE_CREATE)
+ io_opaque_create_release(op);
+ else
+ io_opaque_data_put(store, op->data);
io_opaque_stream_put(op->stream);
lockdep_assert_held(&req->ctx->uring_lock);
io_opaque_req_free(req->ctx, op);
--
2.34.1
next prev parent reply other threads:[~2026-11-10 13:20 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 13:19 [RFC PATCH net-next 00/12] io_uring: opaque TCP payload objects Alex Markuze
2026-11-10 13:19 ` [RFC PATCH net-next 01/12] net: add an exclusive TCP receive claim Alex Markuze
2026-11-10 13:19 ` [RFC PATCH net-next 02/12] io_uring/opaque: add data structures and the UAPI header Alex Markuze
2026-11-10 13:19 ` [RFC PATCH net-next 03/12] io_uring/opaque: add backing accounting and immutable versions Alex Markuze
2026-11-10 13:19 ` [RFC PATCH net-next 04/12] io_uring/opaque: add owned-page helpers for dense copies Alex Markuze
2026-11-10 13:19 ` [RFC PATCH net-next 05/12] io_uring/opaque: add store registration and IORING_OP_OPAQUE_OBJ Alex Markuze
2026-11-10 13:20 ` [RFC PATCH net-next 06/12] io_uring/opaque: allow rings to share a store Alex Markuze
2026-11-10 13:20 ` [RFC PATCH net-next 07/12] io_uring/opaque: add IORING_OP_OPAQUE_OBJ_SEND Alex Markuze
2026-11-10 13:20 ` [RFC PATCH net-next 08/12] io_uring/opaque: add vectored IORING_OP_OPAQUE_OBJ_SEND Alex Markuze
2026-11-10 13:20 ` [RFC PATCH net-next 09/12] io_uring/opaque: add IORING_OP_OPAQUE_COLLECT Alex Markuze
2026-11-10 13:20 ` Alex Markuze [this message]
2026-11-10 13:20 ` [RFC PATCH net-next 11/12] io_uring/opaque: add manual and automatic compaction Alex Markuze
2026-11-10 13:20 ` [RFC PATCH net-next 12/12] io_uring/opaque: add basic KUnit tests Alex Markuze
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110132006.3280986-11-amarkuze@redhat.com \
--to=amarkuze@redhat.com \
--cc=asml.silence@gmail.com \
--cc=axboe@kernel.dk \
--cc=bpf@vger.kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=io-uring@vger.kernel.org \
--cc=jakub@cloudflare.com \
--cc=jiayuan.chen@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=willemb@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®