io_uring: uring_cmd: add multishot support

Add UAPI flag IORING_URING_CMD_MULTISHOT for supporting multishot
uring_cmd operations with provided buffer.

This enables drivers to post multiple completion events from a single
uring_cmd submission, which is useful for:

- Notifying userspace of device events (e.g., interrupt handling)
- Supporting devices with multiple event sources (e.g., multi-queue devices)
- Avoiding the need for device poll() support when events originate
  from multiple sources device-wide

The implementation adds two new APIs:
- io_uring_cmd_select_buffer(): selects a buffer from the provided
  buffer group for multishot uring_cmd
- io_uring_mshot_cmd_post_cqe(): posts a CQE after event data is
  pushed to the provided buffer

Multishot uring_cmd must be used with buffer select (IOSQE_BUFFER_SELECT)
and is mutually exclusive with IORING_URING_CMD_FIXED for now.

The ublk driver will be the first user of this functionality:

	https://github.com/ming1/linux/commits/ublk-devel/

Signed-off-by: Ming Lei <ming.lei@redhat.com>
Link: https://lore.kernel.org/r/20250821040210.1152145-3-ming.lei@redhat.com
[axboe: fold in fix for !CONFIG_IO_URING]
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Ming Lei 2025-08-21 12:02:07 +08:00 committed by Jens Axboe
parent d589bcddaa
commit 620a50c927
4 changed files with 102 additions and 2 deletions

View file

@ -70,6 +70,21 @@ void io_uring_cmd_mark_cancelable(struct io_uring_cmd *cmd,
/* Execute the request from a blocking context */ /* Execute the request from a blocking context */
void io_uring_cmd_issue_blocking(struct io_uring_cmd *ioucmd); void io_uring_cmd_issue_blocking(struct io_uring_cmd *ioucmd);
/*
* Select a buffer from the provided buffer group for multishot uring_cmd.
* Returns the selected buffer address and size.
*/
struct io_br_sel io_uring_cmd_buffer_select(struct io_uring_cmd *ioucmd,
unsigned buf_group, size_t *len,
unsigned int issue_flags);
/*
* Complete a multishot uring_cmd event. This will post a CQE to the completion
* queue and update the provided buffer.
*/
bool io_uring_mshot_cmd_post_cqe(struct io_uring_cmd *ioucmd,
struct io_br_sel *sel, unsigned int issue_flags);
#else #else
static inline int static inline int
io_uring_cmd_import_fixed(u64 ubuf, unsigned long len, int rw, io_uring_cmd_import_fixed(u64 ubuf, unsigned long len, int rw,
@ -102,6 +117,17 @@ static inline void io_uring_cmd_mark_cancelable(struct io_uring_cmd *cmd,
static inline void io_uring_cmd_issue_blocking(struct io_uring_cmd *ioucmd) static inline void io_uring_cmd_issue_blocking(struct io_uring_cmd *ioucmd)
{ {
} }
static inline struct io_br_sel
io_uring_cmd_buffer_select(struct io_uring_cmd *ioucmd, unsigned buf_group,
size_t *len, unsigned int issue_flags)
{
return (struct io_br_sel) { .val = -EOPNOTSUPP };
}
static inline bool io_uring_mshot_cmd_post_cqe(struct io_uring_cmd *ioucmd,
ssize_t ret, unsigned int issue_flags)
{
return true;
}
#endif #endif
/* /*

View file

@ -298,9 +298,13 @@ enum io_uring_op {
* sqe->uring_cmd_flags top 8bits aren't available for userspace * sqe->uring_cmd_flags top 8bits aren't available for userspace
* IORING_URING_CMD_FIXED use registered buffer; pass this flag * IORING_URING_CMD_FIXED use registered buffer; pass this flag
* along with setting sqe->buf_index. * along with setting sqe->buf_index.
* IORING_URING_CMD_MULTISHOT must be used with buffer select, like other
* multishot commands. Not compatible with
* IORING_URING_CMD_FIXED, for now.
*/ */
#define IORING_URING_CMD_FIXED (1U << 0) #define IORING_URING_CMD_FIXED (1U << 0)
#define IORING_URING_CMD_MASK IORING_URING_CMD_FIXED #define IORING_URING_CMD_MULTISHOT (1U << 1)
#define IORING_URING_CMD_MASK (IORING_URING_CMD_FIXED | IORING_URING_CMD_MULTISHOT)
/* /*

View file

@ -413,6 +413,7 @@ const struct io_issue_def io_issue_defs[] = {
#endif #endif
}, },
[IORING_OP_URING_CMD] = { [IORING_OP_URING_CMD] = {
.buffer_select = 1,
.needs_file = 1, .needs_file = 1,
.plug = 1, .plug = 1,
.iopoll = 1, .iopoll = 1,

View file

@ -11,6 +11,7 @@
#include "io_uring.h" #include "io_uring.h"
#include "alloc_cache.h" #include "alloc_cache.h"
#include "rsrc.h" #include "rsrc.h"
#include "kbuf.h"
#include "uring_cmd.h" #include "uring_cmd.h"
#include "poll.h" #include "poll.h"
@ -194,8 +195,21 @@ int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
if (ioucmd->flags & ~IORING_URING_CMD_MASK) if (ioucmd->flags & ~IORING_URING_CMD_MASK)
return -EINVAL; return -EINVAL;
if (ioucmd->flags & IORING_URING_CMD_FIXED) if (ioucmd->flags & IORING_URING_CMD_FIXED) {
if (ioucmd->flags & IORING_URING_CMD_MULTISHOT)
return -EINVAL;
req->buf_index = READ_ONCE(sqe->buf_index); req->buf_index = READ_ONCE(sqe->buf_index);
}
if (ioucmd->flags & IORING_URING_CMD_MULTISHOT) {
if (ioucmd->flags & IORING_URING_CMD_FIXED)
return -EINVAL;
if (!(req->flags & REQ_F_BUFFER_SELECT))
return -EINVAL;
} else {
if (req->flags & REQ_F_BUFFER_SELECT)
return -EINVAL;
}
ioucmd->cmd_op = READ_ONCE(sqe->cmd_op); ioucmd->cmd_op = READ_ONCE(sqe->cmd_op);
@ -251,6 +265,10 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags)
} }
ret = file->f_op->uring_cmd(ioucmd, issue_flags); ret = file->f_op->uring_cmd(ioucmd, issue_flags);
if (ioucmd->flags & IORING_URING_CMD_MULTISHOT) {
if (ret >= 0)
return IOU_ISSUE_SKIP_COMPLETE;
}
if (ret == -EAGAIN) { if (ret == -EAGAIN) {
ioucmd->flags |= IORING_URING_CMD_REISSUE; ioucmd->flags |= IORING_URING_CMD_REISSUE;
return ret; return ret;
@ -333,3 +351,54 @@ bool io_uring_cmd_post_mshot_cqe32(struct io_uring_cmd *cmd,
return false; return false;
return io_req_post_cqe32(req, cqe); return io_req_post_cqe32(req, cqe);
} }
/*
* Work with io_uring_mshot_cmd_post_cqe() together for committing the
* provided buffer upfront
*/
struct io_br_sel io_uring_cmd_buffer_select(struct io_uring_cmd *ioucmd,
unsigned buf_group, size_t *len,
unsigned int issue_flags)
{
struct io_kiocb *req = cmd_to_io_kiocb(ioucmd);
if (!(ioucmd->flags & IORING_URING_CMD_MULTISHOT))
return (struct io_br_sel) { .val = -EINVAL };
if (WARN_ON_ONCE(!io_do_buffer_select(req)))
return (struct io_br_sel) { .val = -EINVAL };
return io_buffer_select(req, len, buf_group, issue_flags);
}
EXPORT_SYMBOL_GPL(io_uring_cmd_buffer_select);
/*
* Return true if this multishot uring_cmd needs to be completed, otherwise
* the event CQE is posted successfully.
*
* This function must use `struct io_br_sel` returned from
* io_uring_cmd_buffer_select() for committing the buffer in the same
* uring_cmd submission context.
*/
bool io_uring_mshot_cmd_post_cqe(struct io_uring_cmd *ioucmd,
struct io_br_sel *sel, unsigned int issue_flags)
{
struct io_kiocb *req = cmd_to_io_kiocb(ioucmd);
unsigned int cflags = 0;
if (!(ioucmd->flags & IORING_URING_CMD_MULTISHOT))
return true;
if (sel->val > 0) {
cflags = io_put_kbuf(req, sel->val, sel->buf_list);
if (io_req_post_cqe(req, sel->val, cflags | IORING_CQE_F_MORE))
return false;
}
io_kbuf_recycle(req, sel->buf_list, issue_flags);
if (sel->val < 0)
req_set_fail(req);
io_req_set_res(req, sel->val, cflags);
return true;
}
EXPORT_SYMBOL_GPL(io_uring_mshot_cmd_post_cqe);