[RFC PATCH v3 5/6] io_uring/cmd: allow reusing SQE storage for completion data

From: Yingjie Wang

Date: Sat Nov 07 2026 - 04:52:47 EST


From: Yingjie Wang <x-professor@xxxxxxxxxxx>

uring_cmd requests already allocate enough asynchronous state to hold a
copied 128-byte SQE. Once a driver has consumed every SQE field and
committed to asynchronous completion, that storage is no longer needed.

Add a helper that transfers the storage to the driver until command
completion without growing the request or adding an allocation. Invalidate
the SQE pointer on transfer and fail a subsequent -EAGAIN return with -EIO
so a command cannot be reissued after overwriting its SQE copy.

Signed-off-by: Yingjie Wang <x-professor@xxxxxxxxxxx>
---
include/linux/io_uring/cmd.h | 24 ++++++++++++++++++++++++
io_uring/uring_cmd.c | 22 ++++++++++++++++++++++
2 files changed, 46 insertions(+)

diff --git a/include/linux/io_uring/cmd.h b/include/linux/io_uring/cmd.h
index e18d4af62e1a..6a9c06bcccc6 100644
--- a/include/linux/io_uring/cmd.h
+++ b/include/linux/io_uring/cmd.h
@@ -8,6 +8,7 @@
#include <linux/blk-mq.h>

/* only top 8 bits of sqe->uring_cmd_flags for kernel internal use */
+#define IORING_URING_CMD_SQE_CONSUMED BIT(29)
#define IORING_URING_CMD_CANCELABLE (1U << 30)
/* io_uring_cmd is being issued again */
#define IORING_URING_CMD_REISSUE (1U << 31)
@@ -82,6 +83,22 @@ int io_uring_cmd_import_fixed_vec(struct io_uring_cmd *ioucmd,
*/
void __io_uring_cmd_done(struct io_uring_cmd *, unsigned issue_flags);

+/**
+ * io_uring_cmd_get_completion_data - reuse the command's SQE copy storage
+ * @ioucmd: command that has consumed all of its SQE fields
+ * @len: amount of completion storage required
+ *
+ * Transfer the per-request storage reserved for an SQE copy to the command.
+ * After a successful call, @ioucmd->sqe is invalid and the command must not be
+ * reissued. The returned storage remains valid until io_uring_cmd_done*().
+ * Multishot commands cannot use this helper.
+ *
+ * Return: completion storage on success, or NULL if it is unavailable or too
+ * large.
+ */
+void *io_uring_cmd_get_completion_data(struct io_uring_cmd *ioucmd,
+ size_t len);
+
void __io_uring_cmd_do_in_task(struct io_uring_cmd *ioucmd,
io_req_tw_func_t task_work_cb,
unsigned flags);
@@ -140,6 +157,13 @@ static inline void __io_uring_cmd_done(struct io_uring_cmd *cmd,
unsigned issue_flags)
{
}
+
+static inline void *
+io_uring_cmd_get_completion_data(struct io_uring_cmd *ioucmd, size_t len)
+{
+ return NULL;
+}
+
static inline void __io_uring_cmd_do_in_task(struct io_uring_cmd *ioucmd,
io_req_tw_func_t task_work_cb, unsigned flags)
{
diff --git a/io_uring/uring_cmd.c b/io_uring/uring_cmd.c
index b5486b56ee0c..87b1b300ae20 100644
--- a/io_uring/uring_cmd.c
+++ b/io_uring/uring_cmd.c
@@ -166,6 +166,23 @@ void __io_uring_cmd_done(struct io_uring_cmd *ioucmd, unsigned issue_flags)
}
EXPORT_SYMBOL_GPL(__io_uring_cmd_done);

+void *io_uring_cmd_get_completion_data(struct io_uring_cmd *ioucmd, size_t len)
+{
+ struct io_kiocb *req = cmd_to_io_kiocb(ioucmd);
+ struct io_async_cmd *ac = req->async_data;
+
+ if (!ac || !ioucmd->sqe ||
+ ioucmd->flags & (IORING_URING_CMD_MULTISHOT |
+ IORING_URING_CMD_SQE_CONSUMED) ||
+ len > sizeof(ac->sqes))
+ return NULL;
+
+ ioucmd->flags |= IORING_URING_CMD_SQE_CONSUMED;
+ ioucmd->sqe = NULL;
+ return ac->sqes;
+}
+EXPORT_SYMBOL_GPL(io_uring_cmd_get_completion_data);
+
int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
{
struct io_uring_cmd *ioucmd = io_kiocb_to_cmd(req, struct io_uring_cmd);
@@ -257,11 +274,16 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags)

ret = file->f_op->uring_cmd(ioucmd, issue_flags);
if (ret == -EAGAIN) {
+ if (ioucmd->flags & IORING_URING_CMD_SQE_CONSUMED) {
+ ret = -EIO;
+ goto done;
+ }
ioucmd->flags |= IORING_URING_CMD_REISSUE;
return ret;
}
if (ret == -EIOCBQUEUED)
return ret;
+done:
if (ret < 0)
req_set_fail(req);
io_req_uring_cleanup(req, issue_flags);
--
2.43.0