pub const IORING_OP_NOP: u8 = 0;
pub const IORING_OP_READV: u8 = 1;
pub const IORING_OP_WRITEV: u8 = 2;
pub const IORING_OP_FSYNC: u8 = 3;
pub const IORING_OP_READ_FIXED: u8 = 4;
pub const IORING_OP_WRITE_FIXED: u8 = 5;
pub const IORING_OP_POLL_ADD: u8 = 6;
pub const IORING_OP_POLL_REMOVE: u8 = 7;
pub const IORING_OP_SYNC_FILE_RANGE: u8 = 8;
pub const IORING_OP_SENDMSG: u8 = 9;
pub const IORING_OP_RECVMSG: u8 = 10;
pub const IORING_OP_TIMEOUT: u8 = 11;
pub const IORING_OP_TIMEOUT_REMOVE: u8 = 12;
pub const IORING_OP_ACCEPT: u8 = 13;
pub const IORING_OP_ASYNC_CANCEL: u8 = 14;
pub const IORING_OP_LINK_TIMEOUT: u8 = 15;
pub const IORING_OP_CONNECT: u8 = 16;
pub const IORING_OP_FALLOCATE: u8 = 17;
pub const IORING_OP_OPENAT: u8 = 18;
pub const IORING_OP_CLOSE: u8 = 19;
pub const IORING_OP_FILES_UPDATE: u8 = 20;
pub const IORING_OP_STATX: u8 = 21;
pub const IORING_OP_READ: u8 = 22;
pub const IORING_OP_WRITE: u8 = 23;
pub const IORING_OP_FADVISE: u8 = 24;
pub const IORING_OP_MADVISE: u8 = 25;
pub const IORING_OP_SEND: u8 = 26;
pub const IORING_OP_RECV: u8 = 27;
pub const IORING_OP_OPENAT2: u8 = 28;
pub const IORING_OP_EPOLL_CTL: u8 = 29;
pub const IORING_OP_SPLICE: u8 = 30;
pub const IORING_OP_PROVIDE_BUFFERS: u8 = 31;
pub const IORING_OP_REMOVE_BUFFERS: u8 = 32;
pub const IORING_OP_TEE: u8 = 33;
pub const IORING_OP_SHUTDOWN: u8 = 34;
pub const IORING_OP_RENAMEAT: u8 = 35;
pub const IORING_OP_UNLINKAT: u8 = 36;
pub const IORING_OP_MKDIRAT: u8 = 37;
pub const IORING_OP_SYMLINKAT: u8 = 38;
pub const IORING_OP_LINKAT: u8 = 39;
pub const IORING_OP_MSG_RING: u8 = 40;
pub const IORING_OP_FGETXATTR: u8 = 41;
pub const IORING_OP_FSETXATTR: u8 = 42;
pub const IORING_OP_GETXATTR: u8 = 43;
pub const IORING_OP_SETXATTR: u8 = 44;
pub const IORING_OP_SOCKET: u8 = 45;
pub const IORING_OP_URING_CMD: u8 = 46;
pub const IORING_OP_SEND_ZC: u8 = 47;
pub const IORING_OP_SENDMSG_ZC: u8 = 48;
pub const IORING_OP_LAST: u8 = 49;
pub const IOSQE_FIXED_FILE: u8 = 1 << 0;
pub const IOSQE_IO_DRAIN: u8 = 1 << 1;
pub const IOSQE_IO_LINK: u8 = 1 << 2;
pub const IOSQE_IO_HARDLINK: u8 = 1 << 3;
pub const IOSQE_ASYNC: u8 = 1 << 4;
pub const IOSQE_BUFFER_SELECT: u8 = 1 << 5;
pub const IOSQE_CQE_SKIP_SUCCESS: u8 = 1 << 6;
pub const IORING_CQE_F_MORE: u32 = 1 << 1;
pub const IORING_CQE_F_SOCK_NONEMPTY: u32 = 1 << 2;
pub const IORING_CQE_F_NOTIF: u32 = 1 << 3;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IoUringSqe {
pub opcode: u8,
pub flags: u8,
pub ioprio: u16,
pub fd: i32,
pub off: u64,
pub addr: u64,
pub len: u32,
pub op_flags: u32,
pub user_data: u64,
pub buf_index: u16,
pub personality: u16,
pub splice_fd_in: i32,
pub addr3: u64,
pub __pad2: [u64; 1],
}
impl IoUringSqe {
#[must_use]
pub const fn new() -> Self {
Self {
opcode: 0,
flags: 0,
ioprio: 0,
fd: -1,
off: 0,
addr: 0,
len: 0,
op_flags: 0,
user_data: 0,
buf_index: 0,
personality: 0,
splice_fd_in: 0,
addr3: 0,
__pad2: [0],
}
}
#[must_use]
pub const fn nop(user_data: u64) -> Self {
let mut sqe = Self::new();
sqe.opcode = IORING_OP_NOP;
sqe.user_data = user_data;
sqe
}
#[must_use]
pub const fn uring_cmd(fd: i32, cmd_op: u32, user_data: u64) -> Self {
let mut sqe = Self::new();
sqe.opcode = IORING_OP_URING_CMD;
sqe.fd = fd;
sqe.op_flags = cmd_op;
sqe.user_data = user_data;
sqe
}
#[must_use]
pub const fn read(fd: i32, buf: u64, len: u32, offset: u64, user_data: u64) -> Self {
let mut sqe = Self::new();
sqe.opcode = IORING_OP_READ;
sqe.fd = fd;
sqe.addr = buf;
sqe.len = len;
sqe.off = offset;
sqe.user_data = user_data;
sqe
}
#[must_use]
pub const fn write(fd: i32, buf: u64, len: u32, offset: u64, user_data: u64) -> Self {
let mut sqe = Self::new();
sqe.opcode = IORING_OP_WRITE;
sqe.fd = fd;
sqe.addr = buf;
sqe.len = len;
sqe.off = offset;
sqe.user_data = user_data;
sqe
}
pub fn set_async(&mut self) {
self.flags |= IOSQE_ASYNC;
}
pub fn set_link(&mut self) {
self.flags |= IOSQE_IO_LINK;
}
pub fn set_fixed_file(&mut self) {
self.flags |= IOSQE_FIXED_FILE;
}
#[must_use]
pub const fn is_uring_cmd(&self) -> bool {
self.opcode == IORING_OP_URING_CMD
}
#[must_use]
pub const fn is_nop(&self) -> bool {
self.opcode == IORING_OP_NOP
}
}
impl Default for IoUringSqe {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IoUringCqe {
pub user_data: u64,
pub res: i32,
pub flags: u32,
}
impl IoUringCqe {
#[must_use]
pub const fn new(user_data: u64, res: i32, flags: u32) -> Self {
Self {
user_data,
res,
flags,
}
}
#[must_use]
pub const fn is_success(&self) -> bool {
self.res >= 0
}
#[must_use]
pub const fn is_error(&self) -> bool {
self.res < 0
}
#[must_use]
pub const fn errno(&self) -> i32 {
if self.res < 0 {
-self.res
} else {
0
}
}
#[must_use]
pub const fn result(&self) -> Option<u32> {
if self.res >= 0 {
Some(self.res as u32)
} else {
None
}
}
#[must_use]
pub const fn has_more(&self) -> bool {
(self.flags & IORING_CQE_F_MORE) != 0
}
}
impl Default for IoUringCqe {
fn default() -> Self {
Self::new(0, 0, 0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::{align_of, size_of};
#[test]
fn abi_io_uring_sqe_size() {
assert_eq!(
size_of::<IoUringSqe>(),
64,
"IoUringSqe must be exactly 64 bytes"
);
}
#[test]
fn abi_io_uring_cqe_size() {
assert_eq!(
size_of::<IoUringCqe>(),
16,
"IoUringCqe must be exactly 16 bytes"
);
}
#[test]
fn abi_io_uring_sqe_alignment() {
assert!(
align_of::<IoUringSqe>() >= 8,
"IoUringSqe must have at least 8-byte alignment"
);
}
#[test]
fn abi_io_uring_cqe_alignment() {
assert!(
align_of::<IoUringCqe>() >= 8,
"IoUringCqe must have at least 8-byte alignment"
);
}
#[test]
fn abi_sqe_offsets() {
let sqe = IoUringSqe::new();
let base = &sqe as *const _ as usize;
assert_eq!(
&sqe.opcode as *const _ as usize - base,
0,
"opcode at offset 0"
);
assert_eq!(
&sqe.flags as *const _ as usize - base,
1,
"flags at offset 1"
);
assert_eq!(
&sqe.ioprio as *const _ as usize - base,
2,
"ioprio at offset 2"
);
assert_eq!(&sqe.fd as *const _ as usize - base, 4, "fd at offset 4");
assert_eq!(&sqe.off as *const _ as usize - base, 8, "off at offset 8");
assert_eq!(
&sqe.addr as *const _ as usize - base,
16,
"addr at offset 16"
);
assert_eq!(&sqe.len as *const _ as usize - base, 24, "len at offset 24");
assert_eq!(
&sqe.op_flags as *const _ as usize - base,
28,
"op_flags at offset 28"
);
assert_eq!(
&sqe.user_data as *const _ as usize - base,
32,
"user_data at offset 32"
);
assert_eq!(
&sqe.buf_index as *const _ as usize - base,
40,
"buf_index at offset 40"
);
assert_eq!(
&sqe.personality as *const _ as usize - base,
42,
"personality at offset 42"
);
assert_eq!(
&sqe.splice_fd_in as *const _ as usize - base,
44,
"splice_fd_in at offset 44"
);
assert_eq!(
&sqe.addr3 as *const _ as usize - base,
48,
"addr3 at offset 48"
);
}
#[test]
fn abi_cqe_offsets() {
let cqe = IoUringCqe::default();
let base = &cqe as *const _ as usize;
assert_eq!(
&cqe.user_data as *const _ as usize - base,
0,
"user_data at offset 0"
);
assert_eq!(&cqe.res as *const _ as usize - base, 8, "res at offset 8");
assert_eq!(
&cqe.flags as *const _ as usize - base,
12,
"flags at offset 12"
);
}
#[test]
fn abi_uring_cmd_opcode() {
assert_eq!(
IORING_OP_URING_CMD, 46,
"IORING_OP_URING_CMD must be 46 for ublk"
);
}
#[test]
fn abi_opcodes_are_distinct() {
let opcodes: [u8; 10] = [
IORING_OP_NOP,
IORING_OP_READV,
IORING_OP_WRITEV,
IORING_OP_READ,
IORING_OP_WRITE,
IORING_OP_URING_CMD,
IORING_OP_FSYNC,
IORING_OP_CLOSE,
IORING_OP_SEND,
IORING_OP_RECV,
];
for i in 0..opcodes.len() {
for j in (i + 1)..opcodes.len() {
assert_ne!(opcodes[i], opcodes[j], "opcodes must be distinct");
}
}
}
#[test]
fn abi_opcodes_within_range() {
let opcodes = [
IORING_OP_NOP,
IORING_OP_READ,
IORING_OP_WRITE,
IORING_OP_URING_CMD,
];
for op in opcodes {
assert!(op < IORING_OP_LAST, "opcode {} must be < LAST", op);
}
}
#[test]
fn abi_sqe_flags_are_powers_of_two() {
let flags = [
IOSQE_FIXED_FILE,
IOSQE_IO_DRAIN,
IOSQE_IO_LINK,
IOSQE_IO_HARDLINK,
IOSQE_ASYNC,
IOSQE_BUFFER_SELECT,
IOSQE_CQE_SKIP_SUCCESS,
];
for flag in flags {
assert!(
flag.is_power_of_two(),
"flag 0x{:x} must be power of two",
flag
);
}
}
#[test]
fn test_sqe_new() {
let sqe = IoUringSqe::new();
assert_eq!(sqe.opcode, 0);
assert_eq!(sqe.fd, -1);
assert_eq!(sqe.user_data, 0);
}
#[test]
fn test_sqe_nop() {
let sqe = IoUringSqe::nop(12345);
assert!(sqe.is_nop());
assert_eq!(sqe.user_data, 12345);
}
#[test]
fn test_sqe_uring_cmd() {
let sqe = IoUringSqe::uring_cmd(10, 0x1234, 42);
assert!(sqe.is_uring_cmd());
assert_eq!(sqe.fd, 10);
assert_eq!(sqe.op_flags, 0x1234);
assert_eq!(sqe.user_data, 42);
}
#[test]
fn test_sqe_read() {
let sqe = IoUringSqe::read(5, 0x1000, 4096, 0, 100);
assert_eq!(sqe.opcode, IORING_OP_READ);
assert_eq!(sqe.fd, 5);
assert_eq!(sqe.addr, 0x1000);
assert_eq!(sqe.len, 4096);
assert_eq!(sqe.off, 0);
assert_eq!(sqe.user_data, 100);
}
#[test]
fn test_sqe_write() {
let sqe = IoUringSqe::write(5, 0x2000, 8192, 512, 200);
assert_eq!(sqe.opcode, IORING_OP_WRITE);
assert_eq!(sqe.fd, 5);
assert_eq!(sqe.addr, 0x2000);
assert_eq!(sqe.len, 8192);
assert_eq!(sqe.off, 512);
assert_eq!(sqe.user_data, 200);
}
#[test]
fn test_sqe_flags() {
let mut sqe = IoUringSqe::new();
assert_eq!(sqe.flags, 0);
sqe.set_async();
assert_eq!(sqe.flags & IOSQE_ASYNC, IOSQE_ASYNC);
sqe.set_link();
assert_eq!(sqe.flags & IOSQE_IO_LINK, IOSQE_IO_LINK);
sqe.set_fixed_file();
assert_eq!(sqe.flags & IOSQE_FIXED_FILE, IOSQE_FIXED_FILE);
}
#[test]
fn test_cqe_success() {
let cqe = IoUringCqe::new(42, 4096, 0);
assert!(cqe.is_success());
assert!(!cqe.is_error());
assert_eq!(cqe.result(), Some(4096));
assert_eq!(cqe.errno(), 0);
}
#[test]
fn test_cqe_error() {
let cqe = IoUringCqe::new(42, -5, 0); assert!(!cqe.is_success());
assert!(cqe.is_error());
assert_eq!(cqe.result(), None);
assert_eq!(cqe.errno(), 5);
}
#[test]
fn test_cqe_zero_result() {
let cqe = IoUringCqe::new(0, 0, 0);
assert!(cqe.is_success());
assert_eq!(cqe.result(), Some(0));
}
#[test]
fn test_cqe_has_more() {
let cqe_no_more = IoUringCqe::new(0, 0, 0);
assert!(!cqe_no_more.has_more());
let cqe_more = IoUringCqe::new(0, 0, IORING_CQE_F_MORE);
assert!(cqe_more.has_more());
}
#[test]
fn test_sqe_default() {
let sqe = IoUringSqe::default();
assert_eq!(sqe.opcode, 0);
assert_eq!(sqe.fd, -1);
}
#[test]
fn test_cqe_default() {
let cqe = IoUringCqe::default();
assert_eq!(cqe.user_data, 0);
assert_eq!(cqe.res, 0);
assert_eq!(cqe.flags, 0);
}
#[test]
fn test_sqe_copy() {
let sqe1 = IoUringSqe::nop(999);
let sqe2 = sqe1;
assert_eq!(sqe1, sqe2);
}
#[test]
fn test_cqe_copy() {
let cqe1 = IoUringCqe::new(1, 2, 3);
let cqe2 = cqe1;
assert_eq!(cqe1, cqe2);
}
}