* [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice
@ 2026-04-15 9:02 Sidong Yang
2026-04-15 9:02 ` [PATCH v5 1/4] rust: bindings: add io_uring headers in bindings_helper.h Sidong Yang
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Sidong Yang @ 2026-04-15 9:02 UTC (permalink / raw)
To: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin
Cc: Miguel Ojeda, Arnd Bergmann, Greg Kroah-Hartman, rust-for-linux,
linux-kernel, io-uring, Sidong Yang
This series introduces Rust abstractions for io_uring commands
(`IORING_OP_URING_CMD`) and wires them up to the miscdevice framework,
allowing Rust drivers to handle io_uring passthrough commands.
The series is structured as follows:
1. Add io_uring C headers to Rust bindings.
2. Core io_uring Rust abstractions (IoUringCmd, QueuedIoUringCmd,
IoUringSqe, UringCmdAction type-state pattern, IoUringTaskWork trait).
3. MiscDevice trait extension with uring_cmd callback.
4. Sample demonstrating async uring_cmd handling via workqueue.
The sample completes asynchronously using a workqueue combined with
`complete_in_task()`, which schedules task_work in the submitter's task
context before calling `io_uring_cmd_done()`. This two-step approach
ensures that completion always runs in the correct context regardless
of where the driver decides to finish the operation.
Copy-based `read_pdu()`/`write_pdu()` are kept instead of returning
`&T`/`&mut T` references because the PDU is a `[u8; 32]` byte array
whose alignment may not satisfy `T`'s requirements.
Changes since v4:
- Dropped patch 2/5 (C-side zero-init of PDU in io_uring_cmd_prep);
zero-initialisation is handled in the Rust miscdevice vtable wrapper
instead. (Greg)
- Placed io_uring headers in alphabetical order in
bindings_helper.h. (Miguel)
- Used `.cast()` / `core::ptr::from_ref()` instead of `as` pointer
casts throughout. (Daniel)
- Fixed workqueue completion bug: calling `io_uring_cmd_done()` directly
from a workqueue is unsafe because the worker does not hold uring_lock.
Added `complete_in_task()` / `IoUringTaskWork` trait so drivers
schedule task_work first, then call `done()` with the correct
`TASK_WORK_ISSUE_FLAGS` from the submitter's context.
- Removed `issue_flags` forwarding from sample; the workqueue now calls
`complete_in_task()` instead of `done()` directly.
- Improved all commit messages.
Changes since v3:
- read_pdu(): replaced MaybeUninit + copy_nonoverlapping(c_void) with
read_unaligned (Caleb, Benno).
- write_pdu(): fixed c_void cast to u8 in copy_nonoverlapping (Benno).
- IoUringSqe::opcode(): use read_volatile for SQE field access (Caleb).
- IoUringSqe::cmd_data(): removed unnecessary runtime opcode check;
safety is guaranteed by construction since IoUringSqe can only be
obtained from IoUringCmd::sqe() inside a uring_cmd callback (Caleb).
- Removed unused mut in sample WorkItem::run() (compiler warning).
Changes since v2:
- Adopted type-state pattern for IoUringCmd (IoUringCmd -> QueuedIoUringCmd)
to enforce correct completion flow at compile time.
- UringCmdAction enum with Complete/Queued variants prevents returning
Queued without holding a QueuedIoUringCmd handle.
- Fixed error code handling (use proper kernel error types).
- Suppressed unused result warning with `let _ = ...enqueue(work)`.
Sidong Yang (4):
rust: bindings: add io_uring headers in bindings_helper.h
rust: io_uring: introduce rust abstraction for io-uring cmd
rust: miscdevice: Add `uring_cmd` support
samples: rust: Add `uring_cmd` example to `rust_misc_device`
rust/bindings/bindings_helper.h | 2 +
rust/helpers/helpers.c | 1 +
rust/helpers/io_uring.c | 15 +
rust/kernel/io_uring.rs | 522 +++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
rust/kernel/miscdevice.rs | 81 +++++
samples/rust/rust_misc_device.rs | 62 +++-
7 files changed, 683 insertions(+), 1 deletion(-)
create mode 100644 rust/helpers/io_uring.c
create mode 100644 rust/kernel/io_uring.rs
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v5 1/4] rust: bindings: add io_uring headers in bindings_helper.h
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
@ 2026-04-15 9:02 ` Sidong Yang
2026-04-15 9:02 ` [PATCH v5 2/4] rust: io_uring: introduce rust abstraction for io-uring cmd Sidong Yang
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sidong Yang @ 2026-04-15 9:02 UTC (permalink / raw)
To: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin
Cc: Miguel Ojeda, Arnd Bergmann, Greg Kroah-Hartman, rust-for-linux,
linux-kernel, io-uring, Sidong Yang
Add io_uring.h and io_uring/cmd.h to the Rust bindings header,
placed in alphabetical order, to provide access to the io_uring
command infrastructure from Rust.
These are needed by the Rust io_uring abstraction introduced in
a subsequent patch.
Signed-off-by: Sidong Yang <sidong.yang@furiosa.ai>
---
rust/bindings/bindings_helper.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index faf3ee634ced..b7b0d549a061 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -60,6 +60,8 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/io-pgtable.h>
+#include <linux/io_uring.h>
+#include <linux/io_uring/cmd.h>
#include <linux/ioport.h>
#include <linux/jiffies.h>
#include <linux/jump_label.h>
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 2/4] rust: io_uring: introduce rust abstraction for io-uring cmd
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
2026-04-15 9:02 ` [PATCH v5 1/4] rust: bindings: add io_uring headers in bindings_helper.h Sidong Yang
@ 2026-04-15 9:02 ` Sidong Yang
2026-04-15 9:02 ` [PATCH v5 3/4] rust: miscdevice: Add `uring_cmd` support Sidong Yang
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sidong Yang @ 2026-04-15 9:02 UTC (permalink / raw)
To: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin
Cc: Miguel Ojeda, Arnd Bergmann, Greg Kroah-Hartman, rust-for-linux,
linux-kernel, io-uring, Sidong Yang
Implement io-uring abstractions for character devices that expose an
io_uring command interface via IORING_OP_URING_CMD.
The core types are:
- IoUringCmd: received by a driver's uring_cmd callback. Provides
access to cmd_op, flags, the associated file, and a typed PDU
(protocol data unit). Must be either completed synchronously
via complete() or queued for async completion via queue().
- QueuedIoUringCmd: obtained from IoUringCmd::queue(). The driver
calls done() on this handle to post the completion to userspace.
- IoUringSqe: the submission queue entry, available through
IoUringCmd::sqe(). Exposes the opcode and inline cmd_data.
- Opcode: a newtype wrapper around the u8 opcode field, with a
URING_CMD constant for driver-specific passthrough commands.
- UringCmdAction: a type-state enum (Complete | Queued) returned
by the driver callback to indicate the completion path taken.
Signed-off-by: Sidong Yang <sidong.yang@furiosa.ai>
---
rust/helpers/helpers.c | 1 +
rust/helpers/io_uring.c | 15 ++
rust/kernel/io_uring.rs | 522 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
4 files changed, 539 insertions(+)
create mode 100644 rust/helpers/io_uring.c
create mode 100644 rust/kernel/io_uring.rs
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index e05c6e7e4abb..3fa2b3d9f83a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -62,6 +62,7 @@
#include "irq.c"
#include "fs.c"
#include "io.c"
+#include "io_uring.c"
#include "jump_label.c"
#include "kunit.c"
#include "maple_tree.c"
diff --git a/rust/helpers/io_uring.c b/rust/helpers/io_uring.c
new file mode 100644
index 000000000000..154f67fb3637
--- /dev/null
+++ b/rust/helpers/io_uring.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/io_uring/cmd.h>
+
+__rust_helper void rust_helper_io_uring_cmd_done32(struct io_uring_cmd *cmd, s32 ret,
+ u64 res2, unsigned int issue_flags)
+{
+ io_uring_cmd_done32(cmd, ret, res2, issue_flags);
+}
+
+__rust_helper struct io_uring_cmd *
+rust_helper_io_uring_cmd_from_tw(struct io_tw_req tw_req)
+{
+ return io_uring_cmd_from_tw(tw_req);
+}
diff --git a/rust/kernel/io_uring.rs b/rust/kernel/io_uring.rs
new file mode 100644
index 000000000000..606d282e606b
--- /dev/null
+++ b/rust/kernel/io_uring.rs
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: (C) 2025 Furiosa AI
+
+//! Abstractions for io-uring.
+//!
+//! This module provides abstractions for the io-uring interface for character devices.
+//!
+//! C headers: [`include/linux/io_uring/cmd.h`](srctree/include/linux/io_uring/cmd.h) and
+//! [`include/linux/io_uring/io_uring.h`](srctree/include/linux/io_uring/io_uring.h)
+
+use core::ptr::NonNull;
+
+use crate::error::from_result;
+use crate::transmute::{AsBytes, FromBytes};
+use crate::{fs::File, types::Opaque};
+
+use crate::prelude::*;
+
+/// Size in bytes of the protocol data unit (PDU) embedded in `io_uring_cmd`.
+///
+/// Matches the size of the `pdu` field in `struct io_uring_cmd` as defined in
+/// `include/linux/io_uring/cmd.h`.
+pub(crate) const PDU_SIZE: usize = 32;
+
+/// `issue_flags` value for completions posted from task_work context.
+///
+/// Equivalent to the C `IO_URING_CMD_TASK_WORK_ISSUE_FLAGS` macro.
+/// Pass this to [`QueuedIoUringCmd::done`] when completing from the
+/// task_work callback scheduled by [`QueuedIoUringCmd::complete_in_task`].
+pub const TASK_WORK_ISSUE_FLAGS: u32 =
+ bindings::io_uring_cmd_flags_IO_URING_F_COMPLETE_DEFER as u32;
+
+/// Opcode of an [`IoUringSqe`].
+///
+/// Each submission queue entry in io_uring specifies an operation
+/// to perform, such as read, write, or a driver-specific `URING_CMD`.
+#[repr(transparent)]
+#[derive(PartialEq)]
+pub struct Opcode(u8);
+
+impl Opcode {
+ /// Driver-specific passthrough command.
+ pub const URING_CMD: Self = Self(bindings::io_uring_op_IORING_OP_URING_CMD as u8);
+}
+
+/// A fresh `io_uring_cmd` received from the driver callback.
+///
+/// Represents a submission received from userspace via `IORING_OP_URING_CMD`.
+/// A driver obtains this from the `uring_cmd` callback in [`crate::miscdevice::MiscDevice`].
+///
+/// The driver must either complete the command synchronously by calling
+/// [`Self::complete`], or queue it for asynchronous completion by calling
+/// [`Self::queue`], which yields a [`QueuedIoUringCmd`] handle.
+///
+/// # Invariants
+///
+/// `self.inner` is non-null, properly aligned, and points to a valid, live
+/// `bindings::io_uring_cmd` for the duration of the driver callback.
+pub struct IoUringCmd {
+ inner: NonNull<bindings::io_uring_cmd>,
+}
+
+// SAFETY: `io_uring_cmd` is a kernel-allocated structure. The kernel
+// guarantees that it remains alive until the driver either returns a
+// non-`EIOCBQUEUED` result or calls `io_uring_cmd_done32()`. Moving the
+// pointer to another thread is safe: the kernel object is not tied to any
+// particular CPU or task context.
+unsafe impl Send for IoUringCmd {}
+
+// SAFETY: All `&self` methods on `IoUringCmd` only read from the underlying
+// `io_uring_cmd` (cmd_op, flags, sqe, file). `write_pdu` takes `&mut self`,
+// so the borrow checker prevents concurrent mutable access. Sharing
+// `&IoUringCmd` across threads is therefore safe.
+unsafe impl Sync for IoUringCmd {}
+
+/// An [`IoUringCmd`] that has been queued for asynchronous completion.
+///
+/// The only way to obtain a `QueuedIoUringCmd` is through [`IoUringCmd::queue`],
+/// which ensures the command was properly handed off to the async path before
+/// [`UringCmdAction::Queued`] is returned to the vtable.
+///
+/// Call [`Self::done`] exactly once to post the completion to userspace.
+///
+/// # Invariants
+///
+/// `self.inner` is non-null, properly aligned, and points to a valid, live
+/// `bindings::io_uring_cmd` until [`Self::done`] is called.
+pub struct QueuedIoUringCmd {
+ inner: NonNull<bindings::io_uring_cmd>,
+}
+
+// SAFETY: Same reasoning as for `IoUringCmd`. After `queue()`, the handle is
+// intentionally moved to a different context (e.g. a workqueue) to call
+// `done()` later.
+unsafe impl Send for QueuedIoUringCmd {}
+
+// SAFETY: All `&self` methods on `QueuedIoUringCmd` only read from the
+// underlying `io_uring_cmd`.
+unsafe impl Sync for QueuedIoUringCmd {}
+
+/// Proof that a `uring_cmd` request completed synchronously.
+pub struct CompleteAction {
+ ret: i32,
+}
+
+impl CompleteAction {
+ /// Returns the userspace result for this synchronous completion.
+ #[inline]
+ pub fn ret(&self) -> i32 {
+ self.ret
+ }
+}
+
+/// Proof that a `uring_cmd` request was queued for asynchronous completion.
+///
+/// This type has a private field and can only be constructed inside this module,
+/// so it can only be obtained through [`IoUringCmd::queue`].
+pub struct QueuedAction {
+ _private: (),
+}
+
+/// Completion mode for `uring_cmd`.
+pub enum UringCmdAction {
+ /// Request is completed synchronously and returns this result to userspace.
+ Complete(CompleteAction),
+ /// Request is queued for asynchronous completion.
+ ///
+ /// This variant can only be constructed by calling [`IoUringCmd::queue`],
+ /// which enforces that the caller holds a [`QueuedIoUringCmd`] handle and
+ /// will eventually call [`QueuedIoUringCmd::done`].
+ Queued(QueuedAction),
+}
+
+impl IoUringCmd {
+ /// Returns the `cmd_op` associated with this command.
+ #[inline]
+ pub fn cmd_op(&self) -> u32 {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ unsafe { (*self.as_raw()).cmd_op }
+ }
+
+ /// Returns the flags field of this command.
+ ///
+ /// The returned value is `io_uring_cmd.flags`, which is a combination of:
+ /// - User-set flags from `sqe->uring_cmd_flags` (bits 0–1):
+ /// `IORING_URING_CMD_FIXED`, `IORING_URING_CMD_MULTISHOT`.
+ /// - Kernel-set flags (bits 30–31):
+ /// `IORING_URING_CMD_CANCELABLE`, `IORING_URING_CMD_REISSUE`.
+ ///
+ /// Note: this is **not** the `issue_flags` parameter passed to the
+ /// `uring_cmd` callback, which carries `IO_URING_F_*` flags such as
+ /// `IO_URING_F_NONBLOCK`.
+ #[inline]
+ pub fn flags(&self) -> u32 {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ unsafe { (*self.as_raw()).flags }
+ }
+
+ /// Reads the protocol data unit (PDU) as a value of type `T`.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`EINVAL`] if `size_of::<T>()` exceeds the PDU size.
+ #[inline]
+ pub fn read_pdu<T: FromBytes>(&self) -> Result<T> {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let inner = unsafe { &*self.inner.as_ref() };
+
+ if size_of::<T>() > inner.pdu.len() {
+ return Err(EINVAL);
+ }
+
+ let ptr = inner.pdu.as_ptr().cast::<T>();
+
+ // SAFETY: `ptr` is a valid pointer derived from `self.inner`, which
+ // is guaranteed by the type invariant. `size_of::<T>()` bytes are
+ // available in the PDU (checked above). `read_unaligned` is used
+ // because the PDU is a byte array and may not satisfy `T`'s alignment.
+ // `T: FromBytes` guarantees that every bit-pattern is a valid value.
+ Ok(unsafe { core::ptr::read_unaligned(ptr) })
+ }
+
+ /// Writes `value` to the PDU of this command.
+ ///
+ /// # Errors
+ ///
+ /// Returns [`EINVAL`] if `size_of::<T>()` exceeds the PDU size.
+ #[inline]
+ pub fn write_pdu<T: AsBytes>(&mut self, value: &T) -> Result<()> {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let inner = unsafe { self.inner.as_mut() };
+
+ let len = size_of::<T>();
+ if len > inner.pdu.len() {
+ return Err(EINVAL);
+ }
+
+ let src = core::ptr::from_ref(value).cast::<u8>();
+ let dst = (&raw mut inner.pdu).cast::<u8>();
+
+ // SAFETY:
+ // * `src` points to valid memory because `T: AsBytes`.
+ // * `dst` is valid and derived from `self.inner`, which is guaranteed
+ // by the type invariant.
+ // * The byte count does not exceed the PDU length (checked above).
+ unsafe {
+ core::ptr::copy_nonoverlapping(src, dst, len);
+ }
+
+ Ok(())
+ }
+
+ /// Constructs an [`IoUringCmd`] from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must guarantee that:
+ /// - `ptr` is non-null, properly aligned, and points to a valid, initialised
+ /// `bindings::io_uring_cmd`.
+ /// - The pointed-to object remains alive until the driver either returns a
+ /// non-`EIOCBQUEUED` value or calls [`QueuedIoUringCmd::done`].
+ /// - No other mutable reference to the same object exists for the duration
+ /// of the returned handle's lifetime.
+ #[inline]
+ pub(crate) unsafe fn from_raw(ptr: *mut bindings::io_uring_cmd) -> Result<Self> {
+ let Some(inner) = NonNull::new(ptr) else {
+ return Err(EINVAL);
+ };
+
+ Ok(Self { inner })
+ }
+
+ /// Returns a raw pointer to the underlying `io_uring_cmd`.
+ #[inline]
+ fn as_raw(&self) -> *mut bindings::io_uring_cmd {
+ self.inner.as_ptr()
+ }
+
+ /// Returns the file associated with this command.
+ ///
+ /// The returned reference is valid for the lifetime of `&self`. The kernel
+ /// holds a reference to the file for the entire lifetime of the enclosing
+ /// `io_kiocb`, so this is safe to call at any point while `IoUringCmd` is
+ /// alive.
+ #[inline]
+ pub fn file(&self) -> &File {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let file = unsafe { (*self.as_raw()).file };
+
+ // SAFETY:
+ // * The `io_kiocb` holds a reference to the file for its entire
+ // lifetime, so `file` is valid and has a positive refcount.
+ // * There is no active fdget_pos region on the file on this thread.
+ unsafe { File::from_raw_file(file) }
+ }
+
+ /// Returns a reference to the [`IoUringSqe`] associated with this command.
+ #[inline]
+ pub fn sqe(&self) -> &IoUringSqe {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let sqe = unsafe { self.inner.as_ref().sqe };
+ // SAFETY: `sqe` is a valid pointer set by the io_uring core during
+ // submission queue entry preparation and remains valid for the lifetime
+ // of the `io_uring_cmd`.
+ unsafe { IoUringSqe::from_raw(sqe) }
+ }
+
+ /// Marks this command as completed synchronously with the provided return value.
+ ///
+ /// The vtable will return `ret` directly to the io_uring core, which posts
+ /// the completion queue entry. No further action is needed from the driver.
+ #[inline]
+ pub fn complete(self, ret: i32) -> UringCmdAction {
+ UringCmdAction::Complete(CompleteAction { ret })
+ }
+
+ /// Queues this command for asynchronous completion.
+ ///
+ /// Returns a [`UringCmdAction::Queued`] token to return from the driver
+ /// callback and a [`QueuedIoUringCmd`] handle that must be used to call
+ /// [`QueuedIoUringCmd::done`] at a later point.
+ ///
+ /// Because [`QueuedAction`] has a private field, [`UringCmdAction::Queued`]
+ /// can **only** be constructed through this method. This prevents a driver
+ /// from accidentally returning `Queued` after already completing the command
+ /// via `done()`.
+ #[inline]
+ pub fn queue(self) -> (UringCmdAction, QueuedIoUringCmd) {
+ let queued = QueuedIoUringCmd { inner: self.inner };
+ (UringCmdAction::Queued(QueuedAction { _private: () }), queued)
+ }
+}
+
+impl QueuedIoUringCmd {
+ /// Returns the `cmd_op` associated with this command.
+ #[inline]
+ pub fn cmd_op(&self) -> u32 {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ unsafe { (*self.inner.as_ptr()).cmd_op }
+ }
+
+ /// Returns the file associated with this command.
+ ///
+ /// See [`IoUringCmd::file`] for safety details.
+ #[inline]
+ pub fn file(&self) -> &File {
+ // SAFETY: Same as `IoUringCmd::file`.
+ let file = unsafe { (*self.inner.as_ptr()).file };
+ // SAFETY: The `io_kiocb` holds a reference to the file for its entire
+ // lifetime, so `file` is valid and has a positive refcount.
+ unsafe { File::from_raw_file(file) }
+ }
+
+ /// Reads the PDU as a value of type `T`.
+ ///
+ /// See [`IoUringCmd::read_pdu`] for details and error conditions.
+ #[inline]
+ pub fn read_pdu<T: FromBytes>(&self) -> Result<T> {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let inner = unsafe { &*self.inner.as_ref() };
+
+ if size_of::<T>() > inner.pdu.len() {
+ return Err(EINVAL);
+ }
+
+ let ptr = inner.pdu.as_ptr().cast::<T>();
+
+ // SAFETY: Same as `IoUringCmd::read_pdu`.
+ Ok(unsafe { core::ptr::read_unaligned(ptr) })
+ }
+
+ /// Writes `value` to the PDU of this command.
+ ///
+ /// See [`IoUringCmd::write_pdu`] for details and error conditions.
+ #[inline]
+ pub fn write_pdu<T: AsBytes>(&mut self, value: &T) -> Result<()> {
+ // SAFETY: `self.inner` is guaranteed by the type invariant to point
+ // to a live `io_uring_cmd`, so dereferencing is safe.
+ let inner = unsafe { self.inner.as_mut() };
+
+ let len = size_of::<T>();
+ if len > inner.pdu.len() {
+ return Err(EINVAL);
+ }
+
+ let src = core::ptr::from_ref(value).cast::<u8>();
+ let dst = (&raw mut inner.pdu).cast::<u8>();
+
+ // SAFETY: Same as `IoUringCmd::write_pdu`.
+ unsafe {
+ core::ptr::copy_nonoverlapping(src, dst, len);
+ }
+
+ Ok(())
+ }
+
+ /// Posts the completion to userspace.
+ ///
+ /// This calls `io_uring_cmd_done()` directly, so the caller must be in a
+ /// context where that is safe. In practice this means the task_work
+ /// callback scheduled by [`Self::complete_in_task`].
+ ///
+ /// # Parameters
+ ///
+ /// - `ret`: Result to return to userspace.
+ /// - `res2`: Extra result word for `IORING_SETUP_CQE32` big-CQE rings;
+ /// pass `0` if not needed.
+ /// - `issue_flags`: Flags describing the current execution context.
+ /// Use [`TASK_WORK_ISSUE_FLAGS`] from a task_work callback.
+ #[inline]
+ pub fn done(self, ret: Result<i32>, res2: u64, issue_flags: u32) {
+ let ret = from_result(|| ret);
+ // SAFETY: `self.inner` is a valid `io_uring_cmd` that was previously
+ // queued (returned `EIOCBQUEUED` to io_uring). The kernel keeps the
+ // `io_kiocb` alive until this call completes.
+ unsafe {
+ bindings::io_uring_cmd_done32(self.inner.as_ptr(), ret, res2, issue_flags);
+ }
+ }
+
+ /// Schedules the completion to run in the submitter's task context.
+ ///
+ /// When the task_work fires, [`IoUringTaskWork::task_work`] is called
+ /// with a reconstructed [`QueuedIoUringCmd`]. The implementor must
+ /// call [`Self::done`] with [`TASK_WORK_ISSUE_FLAGS`] to finish the
+ /// request.
+ ///
+ /// This is safe to call from any context (workqueue, IRQ, softirq, etc.).
+ /// The PDU contents are preserved across this call, so the driver can
+ /// store arbitrary state in the PDU before calling this method and read
+ /// it back inside the task_work callback.
+ #[inline]
+ pub fn complete_in_task<T: IoUringTaskWork>(self) {
+ /// # Safety
+ ///
+ /// Called by the io_uring core in the submitter's task context.
+ unsafe extern "C" fn trampoline<T: IoUringTaskWork>(
+ tw_req: bindings::io_tw_req,
+ _tw: bindings::io_tw_token_t,
+ ) {
+ // SAFETY: `io_uring_cmd_from_tw` returns a valid `io_uring_cmd`
+ // pointer. The io_uring core keeps the `io_kiocb` alive until
+ // the task_work callback returns.
+ let ptr = unsafe { bindings::io_uring_cmd_from_tw(tw_req) };
+ let cmd = QueuedIoUringCmd {
+ inner: unsafe { NonNull::new_unchecked(ptr) },
+ };
+ T::task_work(cmd);
+ }
+
+ // SAFETY: `self.inner` is a valid `io_uring_cmd`.
+ // `IOU_F_TWQ_LAZY_WAKE` uses lazy wakeup semantics (same as
+ // `io_uring_cmd_do_in_task_lazy` in C).
+ unsafe {
+ bindings::__io_uring_cmd_do_in_task(
+ self.inner.as_ptr(),
+ Some(trampoline::<T>),
+ bindings::IOU_F_TWQ_LAZY_WAKE,
+ );
+ }
+ }
+}
+
+/// Trait for handling io_uring command completion in task_work context.
+///
+/// Implement this trait and pass the type to
+/// [`QueuedIoUringCmd::complete_in_task`] to schedule deferred completion.
+///
+/// The implementor must call [`QueuedIoUringCmd::done`] with
+/// [`TASK_WORK_ISSUE_FLAGS`] to complete the request.
+pub trait IoUringTaskWork {
+ /// Called in the submitter's task context.
+ fn task_work(cmd: QueuedIoUringCmd);
+}
+
+/// A Rust abstraction for `io_uring_sqe`.
+///
+/// Represents a Submission Queue Entry (SQE) that describes an I/O operation
+/// to be executed by the io_uring subsystem. Obtain an instance from
+/// [`IoUringCmd::sqe`].
+///
+/// This type should not be constructed directly by drivers.
+///
+/// # Invariants
+///
+/// `self.inner` always points to a valid, live `bindings::io_uring_sqe`.
+/// The `repr(transparent)` attribute guarantees the same memory layout as the
+/// underlying binding.
+#[repr(transparent)]
+pub struct IoUringSqe {
+ inner: Opaque<bindings::io_uring_sqe>,
+}
+
+impl IoUringSqe {
+ /// Returns the opcode of this SQE.
+ pub fn opcode(&self) -> Opcode {
+ // SAFETY: `self.inner` guaranteed by the type invariant to point
+ // to a live `io_uring_sqe`, so dereferencing is safe. Volatile
+ // read is used because the SQE may reside in memory shared with
+ // userspace.
+ Opcode(unsafe { core::ptr::addr_of!((*self.inner.get()).opcode).read_volatile() })
+ }
+
+ /// Reads the inline `cmd` data of this SQE as a value of type `T`.
+ ///
+ /// Only the standard `io_uring_sqe` layout is supported
+ /// (`IORING_SETUP_SQE128` is not handled here).
+ ///
+ /// # Errors
+ ///
+ /// Returns [`EINVAL`] if `size_of::<T>()` exceeds the inline command buffer.
+ pub fn cmd_data<T: FromBytes>(&self) -> Result<T> {
+ // SAFETY: `self.inner` guaranteed by the type invariant to point
+ // to a live `io_uring_sqe`, so dereferencing is safe.
+ let sqe = unsafe { &*self.inner.get() };
+
+ // SAFETY: Accessing the `sqe.cmd` union field is safe because
+ // `IoUringSqe` can only be obtained from `IoUringCmd::sqe()`, which
+ // is only available inside a `uring_cmd` callback where the opcode
+ // is guaranteed to be `IORING_OP_URING_CMD` by the io_uring core.
+ let cmd = unsafe { sqe.__bindgen_anon_6.cmd.as_ref() };
+ let cmd_len = size_of_val(&sqe.__bindgen_anon_6.bindgen_union_field);
+
+ if cmd_len < size_of::<T>() {
+ return Err(EINVAL);
+ }
+
+ let cmd_ptr = cmd.as_ptr().cast::<T>();
+
+ // SAFETY: `cmd_ptr` is valid, derived from `self.inner` which is
+ // guaranteed by the type invariant. `read_unaligned` is used because
+ // the cmd data may not satisfy `T`'s alignment requirements.
+ // `T: FromBytes` guarantees that every bit-pattern is a valid value.
+ Ok(unsafe { core::ptr::read_unaligned(cmd_ptr) })
+ }
+
+ /// Constructs an [`IoUringSqe`] reference from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must guarantee that:
+ /// - `ptr` is non-null, properly aligned, and points to a valid, initialised
+ /// `bindings::io_uring_sqe`.
+ /// - The pointed-to object remains valid for the entire lifetime `'a`.
+ /// - No mutable access to the same object occurs while the returned
+ /// reference is alive.
+ #[inline]
+ pub(crate) unsafe fn from_raw<'a>(ptr: *const bindings::io_uring_sqe) -> &'a IoUringSqe {
+ // SAFETY: The caller guarantees that the pointer is not dangling and
+ // stays valid for the duration of 'a. The cast is valid because
+ // `IoUringSqe` is `repr(transparent)` over `bindings::io_uring_sqe`.
+ unsafe { &*ptr.cast() }
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 0fa9d820fe7c..235d1d03dde2 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -76,6 +76,7 @@
pub mod impl_flags;
pub mod init;
pub mod io;
+pub mod io_uring;
pub mod ioctl;
pub mod iommu;
pub mod iov;
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 3/4] rust: miscdevice: Add `uring_cmd` support
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
2026-04-15 9:02 ` [PATCH v5 1/4] rust: bindings: add io_uring headers in bindings_helper.h Sidong Yang
2026-04-15 9:02 ` [PATCH v5 2/4] rust: io_uring: introduce rust abstraction for io-uring cmd Sidong Yang
@ 2026-04-15 9:02 ` Sidong Yang
2026-04-15 9:02 ` [PATCH v5 4/4] samples: rust: Add `uring_cmd` example to `rust_misc_device` Sidong Yang
2026-04-26 21:30 ` [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Greg Kroah-Hartman
4 siblings, 0 replies; 6+ messages in thread
From: Sidong Yang @ 2026-04-15 9:02 UTC (permalink / raw)
To: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin
Cc: Miguel Ojeda, Arnd Bergmann, Greg Kroah-Hartman, rust-for-linux,
linux-kernel, io-uring, Sidong Yang
Add a uring_cmd method to the MiscDevice trait and wire it up to
file_operations, allowing Rust misc device drivers to handle
IORING_OP_URING_CMD submissions from io_uring.
The vtable wrapper zero-initialises the PDU for fresh (non-reissued)
commands so that drivers always start from a clean state. On reissue
the PDU retains its contents from the previous attempt.
To enable uring_cmd for a specific misc device, set HAS_URING_CMD
to true in the MiscDevice implementation.
Signed-off-by: Sidong Yang <sidong.yang@furiosa.ai>
---
rust/kernel/miscdevice.rs | 81 +++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs
index c3c2052c9206..549693e6aea0 100644
--- a/rust/kernel/miscdevice.rs
+++ b/rust/kernel/miscdevice.rs
@@ -14,6 +14,7 @@
error::{to_result, Error, Result, VTABLE_DEFAULT_ERROR},
ffi::{c_int, c_long, c_uint, c_ulong},
fs::{File, Kiocb},
+ io_uring::{self, IoUringCmd, UringCmdAction},
iov::{IovIterDest, IovIterSource},
mm::virt::VmaNew,
prelude::*,
@@ -190,6 +191,31 @@ fn show_fdinfo(
) {
build_error!(VTABLE_DEFAULT_ERROR)
}
+
+ /// Handler for `uring_cmd`.
+ ///
+ /// Invoked when userspace submits an `IORING_OP_URING_CMD` entry to the
+ /// io-uring submission queue for a file backed by this driver.
+ ///
+ /// The driver must either complete the command synchronously by calling
+ /// [`IoUringCmd::complete`] and returning `Ok(UringCmdAction::Complete(_))`,
+ /// or queue it for asynchronous completion by calling [`IoUringCmd::queue`]
+ /// and returning `Ok(UringCmdAction::Queued(_))`. In the latter case the
+ /// driver must eventually call [`crate::io_uring::QueuedIoUringCmd::done`]
+ /// to post the completion to userspace.
+ ///
+ /// `issue_flags` carries `IO_URING_F_*` flags (e.g. `IO_URING_F_NONBLOCK`)
+ /// describing the current execution context. When completing
+ /// asynchronously, do **not** forward this value to
+ /// [`crate::io_uring::QueuedIoUringCmd::done`]; see its documentation for
+ /// the correct flags to use in each completion context.
+ fn uring_cmd(
+ _device: <Self::Ptr as ForeignOwnable>::Borrowed<'_>,
+ _io_uring_cmd: IoUringCmd,
+ _issue_flags: u32,
+ ) -> Result<UringCmdAction> {
+ build_error!(VTABLE_DEFAULT_ERROR)
+ }
}
/// A vtable for the file operations of a Rust miscdevice.
@@ -387,6 +413,56 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
T::show_fdinfo(device, m, file);
}
+ /// # Safety
+ ///
+ /// - The pointer `ioucmd` is not null and points to a valid `bindings::io_uring_cmd`.
+ unsafe extern "C" fn uring_cmd(
+ ioucmd: *mut bindings::io_uring_cmd,
+ issue_flags: ffi::c_uint,
+ ) -> c_int {
+ // SAFETY: `file` referenced by `ioucmd` is valid pointer. It's assigned in
+ // uring cmd preparation. So dereferencing is safe.
+ let raw_file = unsafe { (*ioucmd).file };
+
+ // SAFETY: `private_data` is guaranteed that it has valid pointer after
+ // this file opened. So dereferencing is safe.
+ let private = unsafe { (*raw_file).private_data }.cast();
+
+ // SAFETY: `ioucmd` is not null and points to valid memory `bindings::io_uring_cmd`
+ // and the memory pointed by `ioucmd` is valid and will not be moved or
+ // freed for the lifetime of returned value `ioucmd`
+ let ioucmd = unsafe { IoUringCmd::from_raw(ioucmd) };
+ let mut ioucmd = match ioucmd {
+ Ok(ioucmd) => ioucmd,
+ Err(e) => {
+ return e.to_errno();
+ }
+ };
+
+ // Zero-initialize the PDU for fresh (non-reissued) commands so that
+ // drivers reading from it always start from a clean state. On reissue
+ // the PDU retains its contents from the previous attempt, which is the
+ // expected behaviour (e.g. a driver may store state there across
+ // -EAGAIN retries).
+ if (ioucmd.flags() & bindings::IORING_URING_CMD_REISSUE) == 0 {
+ if let Err(e) = ioucmd.write_pdu(&[0u8; io_uring::PDU_SIZE]) {
+ return e.to_errno();
+ }
+ }
+
+ // SAFETY: This call is safe because `private` is returned by
+ // `into_foreign` in [`open`]. And it's guaranteed
+ // that `from_foreign` is called by [`release`] after the end of
+ // the lifetime of `device`
+ let device = unsafe { <T::Ptr as ForeignOwnable>::borrow(private) };
+
+ match T::uring_cmd(device, ioucmd, issue_flags) {
+ Ok(UringCmdAction::Complete(action)) => action.ret(),
+ Ok(UringCmdAction::Queued(_)) => EIOCBQUEUED.to_errno(),
+ Err(e) => e.to_errno(),
+ }
+ }
+
const VTABLE: bindings::file_operations = bindings::file_operations {
open: Some(Self::open),
release: Some(Self::release),
@@ -419,6 +495,11 @@ impl<T: MiscDevice> MiscdeviceVTable<T> {
} else {
None
},
+ uring_cmd: if T::HAS_URING_CMD {
+ Some(Self::uring_cmd)
+ } else {
+ None
+ },
..pin_init::zeroed()
};
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v5 4/4] samples: rust: Add `uring_cmd` example to `rust_misc_device`
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
` (2 preceding siblings ...)
2026-04-15 9:02 ` [PATCH v5 3/4] rust: miscdevice: Add `uring_cmd` support Sidong Yang
@ 2026-04-15 9:02 ` Sidong Yang
2026-04-26 21:30 ` [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Greg Kroah-Hartman
4 siblings, 0 replies; 6+ messages in thread
From: Sidong Yang @ 2026-04-15 9:02 UTC (permalink / raw)
To: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin
Cc: Miguel Ojeda, Arnd Bergmann, Greg Kroah-Hartman, rust-for-linux,
linux-kernel, io-uring, Sidong Yang
Extend the rust_misc_device sample to demonstrate uring_cmd handling.
The example completes asynchronously using a workqueue combined with
complete_in_task(), showing the full async completion flow:
IoUringCmd -> queue() -> workqueue -> complete_in_task() ->
task_work -> done().
Signed-off-by: Sidong Yang <sidong.yang@furiosa.ai>
---
samples/rust/rust_misc_device.rs | 62 +++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/samples/rust/rust_misc_device.rs b/samples/rust/rust_misc_device.rs
index 87a1fe63533a..4059348a56ad 100644
--- a/samples/rust/rust_misc_device.rs
+++ b/samples/rust/rust_misc_device.rs
@@ -98,13 +98,15 @@
use kernel::{
device::Device,
fs::{File, Kiocb},
+ io_uring::{self, IoUringCmd, IoUringTaskWork, QueuedIoUringCmd, UringCmdAction},
ioctl::{_IO, _IOC_SIZE, _IOR, _IOW},
iov::{IovIterDest, IovIterSource},
miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},
new_mutex,
prelude::*,
- sync::{aref::ARef, Mutex},
+ sync::{Arc, aref::ARef, Mutex},
uaccess::{UserSlice, UserSliceReader, UserSliceWriter},
+ workqueue::{impl_has_work, new_work, HasWork},
};
const RUST_MISC_DEV_HELLO: u32 = _IO('|' as u32, 0x80);
@@ -151,6 +153,51 @@ struct RustMiscDevice {
dev: ARef<Device>,
}
+#[pin_data]
+struct IoUringCmdWork {
+ #[pin]
+ ioucmd: Mutex<Option<QueuedIoUringCmd>>,
+ #[pin]
+ work: kernel::workqueue::Work<IoUringCmdWork>,
+}
+
+impl_has_work! {
+ impl HasWork<Self> for IoUringCmdWork { self.work }
+}
+
+/// Task-work completion handler for the sample device.
+struct RustMiscDeviceCompletion;
+
+impl IoUringTaskWork for RustMiscDeviceCompletion {
+ fn task_work(cmd: QueuedIoUringCmd) {
+ cmd.done(Ok(0), 0, io_uring::TASK_WORK_ISSUE_FLAGS);
+ }
+}
+
+impl kernel::workqueue::WorkItem for IoUringCmdWork {
+ type Pointer = Arc<IoUringCmdWork>;
+
+ fn run(work: Arc<IoUringCmdWork>) {
+ pr_info!("IoUringCmdWork::run()");
+
+ if let Some(ioucmd) = work.ioucmd.lock().take() {
+ ioucmd.complete_in_task::<RustMiscDeviceCompletion>();
+ }
+ }
+}
+
+impl IoUringCmdWork {
+ fn new(ioucmd: QueuedIoUringCmd) -> Result<Arc<Self>> {
+ Arc::pin_init(
+ pin_init!(Self {
+ ioucmd <- new_mutex!(Some(ioucmd)),
+ work <- new_work!("IoUringCmdWork::work"),
+ }),
+ GFP_KERNEL,
+ )
+ }
+}
+
#[vtable]
impl MiscDevice for RustMiscDevice {
type Ptr = Pin<KBox<Self>>;
@@ -220,6 +267,19 @@ fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result
Ok(0)
}
+
+ fn uring_cmd(
+ me: Pin<&RustMiscDevice>,
+ ioucmd: IoUringCmd,
+ _issue_flags: u32,
+ ) -> Result<UringCmdAction> {
+ dev_info!(me.dev, "UringCmd Rust Misc Device Sample\n");
+
+ let (action, queued_ioucmd) = ioucmd.queue();
+ let work = IoUringCmdWork::new(queued_ioucmd)?;
+ let _ = kernel::workqueue::system().enqueue(work);
+ Ok(action)
+ }
}
#[pinned_drop]
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
` (3 preceding siblings ...)
2026-04-15 9:02 ` [PATCH v5 4/4] samples: rust: Add `uring_cmd` example to `rust_misc_device` Sidong Yang
@ 2026-04-26 21:30 ` Greg Kroah-Hartman
4 siblings, 0 replies; 6+ messages in thread
From: Greg Kroah-Hartman @ 2026-04-26 21:30 UTC (permalink / raw)
To: Sidong Yang
Cc: Jens Axboe, Daniel Almeida, Caleb Sander Mateos, Benno Lossin,
Miguel Ojeda, Arnd Bergmann, rust-for-linux, linux-kernel,
io-uring
On Wed, Apr 15, 2026 at 09:02:11AM +0000, Sidong Yang wrote:
> This series introduces Rust abstractions for io_uring commands
> (`IORING_OP_URING_CMD`) and wires them up to the miscdevice framework,
> allowing Rust drivers to handle io_uring passthrough commands.
>
> The series is structured as follows:
>
> 1. Add io_uring C headers to Rust bindings.
> 2. Core io_uring Rust abstractions (IoUringCmd, QueuedIoUringCmd,
> IoUringSqe, UringCmdAction type-state pattern, IoUringTaskWork trait).
> 3. MiscDevice trait extension with uring_cmd callback.
> 4. Sample demonstrating async uring_cmd handling via workqueue.
Again, I can't take this until we have a "real" user. Please wait to
submit it at that point in time.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-27 3:51 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-15 9:02 [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Sidong Yang
2026-04-15 9:02 ` [PATCH v5 1/4] rust: bindings: add io_uring headers in bindings_helper.h Sidong Yang
2026-04-15 9:02 ` [PATCH v5 2/4] rust: io_uring: introduce rust abstraction for io-uring cmd Sidong Yang
2026-04-15 9:02 ` [PATCH v5 3/4] rust: miscdevice: Add `uring_cmd` support Sidong Yang
2026-04-15 9:02 ` [PATCH v5 4/4] samples: rust: Add `uring_cmd` example to `rust_misc_device` Sidong Yang
2026-04-26 21:30 ` [PATCH v5 0/4] Rust io_uring command abstraction for miscdevice Greg Kroah-Hartman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox