Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec layer 0 — Module io_uring, Stage 2d: URING_CMD (passthrough)

Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS)

Position. Stage 2d specifies the IORING_OP_URING_CMD operation (46), a passthrough sub-protocol through which io_uring forwards an opaque command to the uring_cmd handler of the subsystem that owns the target file (socket, NVMe, ublk…). Dedicated submodule air-sys-syscall::io_uring::cmd. This is the most “raw” operation among the Stage 2x operations: its semantics depend on the target subsystem, not on io_uring. Reuses the core of Stage 1.


1. Nature and constraints

1.1 The mechanism

URING_CMD carries two things in the SQE:

  • cmd_op (__u32): the operation specific to the subsystem.
  • a command area cmd[]: 16 bytes in a standard SQE (the addr3/padding area), or 80 bytes if the ring is created with IORING_SETUP_SQE128.

The contents of cmd[] are opaque to io_uring: it is the file’s f_op->uring_cmd handler that interprets them. Consequence: the facade can only guarantee safety for the commands it knows in typed form (socket); everything else is a constrained generic mechanism.

1.2 Stage 2d conventions

  • SQE128 detection: if a command requires more than 16 bytes and the ring does not have SetupFlags::SQE128, submission fails upfront (Err(EINVAL), Principle 4) — no silent truncation.
  • FIXED flag: IORING_URING_CMD_FIXED (bit 0) allows using a registered buffer (index in buf_index) as the data area associated with the command. The only flag available in 6.12.
  • No multishot: IORING_URING_CMD_MULTISHOT is later than 6.12 → out of scope (master doc §1.2).

2. Socket commands (safe typed layer)

io_uring 6.12 exposes, via URING_CMD on a socket FD, four commands (enum io_uring_socket_op). The facade wraps them as typed and safe:

#![allow(unused)]
fn main() {
impl IoUring {
    /// SIOCINQ: bytes available to read. cmd_op = SOCKET_URING_OP_SIOCINQ (0).
    pub fn submit_socket_inq(&mut self, sock: BorrowedFd<'_>)
        -> Result<SubmissionToken, Errno>;
    /// SIOCOUTQ: bytes pending transmission. cmd_op = SOCKET_URING_OP_SIOCOUTQ (1).
    pub fn submit_socket_outq(&mut self, sock: BorrowedFd<'_>)
        -> Result<SubmissionToken, Errno>;
    /// getsockopt. cmd_op = SOCKET_URING_OP_GETSOCKOPT (2).
    pub fn submit_getsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
                             optname: i32, value: Vec<u8>)
        -> Result<SubmissionToken, Errno>;
    /// setsockopt. cmd_op = SOCKET_URING_OP_SETSOCKOPT (3).
    pub fn submit_setsockopt(&mut self, sock: BorrowedFd<'_>, level: SocketOptionLevel,
                             optname: i32, value: Vec<u8>)
        -> Result<SubmissionToken, Errno>;
}
}
  • inq/outq: completion into_result() -> i32 (byte count). Equivalents: ioctl(SIOCINQ/SIOCOUTQ), but inside the ring, without a synchronous ioctl — useful for driving socket flow in a reactor.
  • getsockopt: value is the output buffer (ownership transferred, S1); the SQE carries level/optname/optlen/optval. Completion into_buffer_result() -> (Vec<u8>, usize) (value + effective length).
  • setsockopt: value (bytes, Principle 3) moved into the slot. Completion completed().
  • SocketOptionLevel: type shared with family-net (SOL_SOCKET, IPPROTO_TCP…).
  • Errors: ENOPROTOOPT, EINVAL, ENOTSOCK, EFAULT.

These four commands advantageously replace synchronous ioctl/getsockopt calls on an asynchronous path, without leaving the reactor.


3. Generic mechanism (arbitrary subsystems)

For subsystems the facade does not know in typed form (NVMe passthrough, ublk, future handlers), two levels are provided.

3.1 Typed level via the UringCommand trait

#![allow(unused)]
fn main() {
/// A passthrough command from a subsystem, serializable into the cmd[] area.
///
/// # Safety
/// The implementor guarantees that `encode` produces a valid command for the
/// `uring_cmd` handler of the targeted `fd`, and that `cmd_op` and any
/// associated buffers satisfy the subsystem's contract.
pub unsafe trait UringCommand {
    /// Operation specific to the subsystem (cmd_op).
    fn cmd_op(&self) -> u32;
    /// Required size of the command area (≤ 16, or ≤ 80 if SQE128).
    fn cmd_len(&self) -> usize;
    /// Serializes the command into `out` (length `cmd_len`).
    fn encode(&self, out: &mut [u8]);
    /// Typed interpretation of the completion.
    type Output;
    fn interpret(&self, completion: &Completion) -> Result<Self::Output, Errno>;
}

impl IoUring {
    pub fn submit_uring_cmd<C: UringCommand>(&mut self, fd: BorrowedFd<'_>, cmd: C)
        -> Result<SubmissionToken, Errno>;
}
}
  • The facade checks cmd.cmd_len() ≤ 16 (or ≤ 80 if SQE128), otherwise Err(EINVAL). The trait is unsafe because the validity of the command depends on the subsystem, outside io_uring’s control.
  • Example consumer: a storage crate (upper layer) defines a NvmePassthroughCmd: UringCommand (struct ~72 bytes ⇒ requires SQE128). Air layer 0 provides the mechanism, not the NVMe-specific structures (those live where they are used).

3.2 Raw unsafe level (escape hatch)

#![allow(unused)]
fn main() {
impl IoUring {
    /// # Safety
    /// `cmd_data` must be a valid command for the uring_cmd handler of `fd`.
    /// Any buffer referenced by the command must remain valid until completion.
    /// `cmd_data.len()` ≤ 16 (or ≤ 80 if SQE128).
    pub unsafe fn submit_uring_cmd_raw(&mut self, fd: BorrowedFd<'_>,
        cmd_op: u32, cmd_data: &[u8], flags: UringCmdFlags)
        -> Result<SubmissionToken, Errno>;
}
}
  • Last resort for subsystems not yet typed. Exhaustive # Safety documentation (layer 0 convention). UringCmdFlags: FIXED.

4. Summary

# opcodeOpcodeFacade (safe)Facade (generic)
46URING_CMDsubmit_socket_inq/outq, submit_getsockopt/setsockoptsubmit_uring_cmd<C> / submit_uring_cmd_raw (unsafe)

Socket cmd_op covered (6.12): SIOCINQ (0), SIOCOUTQ (1), GETSOCKOPT (2), SETSOCKOPT (3). TX_TIMESTAMP/GETSOCKNAME = post-6.12, out of scope.


5. Types added / shared

New: UringCmdFlags (bitflags, FIXED), trait UringCommand. Shared: SocketOptionLevel (family-net). Completion methods reused: into_result/into_buffer_result/completed.


6. Test strategy

  • Integration: inq/outq on a loopback socket with known data queued; getsockopt(SO_RCVBUF) / setsockopt(SO_REUSEADDR) round-trip; comparison with synchronous getsockopt/ioctl (family-net).
  • SQE128: command > 16 bytes rejected without SQE128 (EINVAL); accepted with it.
  • Safety: Miri on the submit_uring_cmd<C> path (serialization into bounded cmd[]); test that cmd_len > capacity is rejected.
  • Fuzzing: decoding of getsockopt completions (external kernel data).
  • Coverage: 100% lines + branches; the unsafe submit_uring_cmd_raw tested via a fake handler in a harness, or recorded in COVERAGE-EXCEPTIONS.md if not provokable.

7. Key decisions that emerged at Stage 2d

  1. Two levels of exposure: safe typed socket commands on one side; trait UringCommand (unsafe trait) + raw form unsafe for arbitrary subsystems on the other. We do not claim to type what only the subsystem knows.
  2. Upfront SQE128 detection — outright rejection rather than silent truncation.
  3. NVMe structures outside layer 0 — Air provides the passthrough mechanism; specific commands (NVMe, ublk) are defined by their consumers.
  4. No multishot uring_cmd — later than 6.12.

8. Next steps

Next spec: io-uring-3a-registration-en.md (modern fixed resources: FixedFdTable, RegisteredBuffers, registered ring fd, eventfd, personality, io-wq affinities, napi, clock, clone_buffers, alloc_range). Global English translation after validation of the French documents.


Document license: MPL 2.0 Status: Technical specification for Stage 2d (URING_CMD) of the air-sys-syscall::io_uring module, target kernel 6.12 LTS.