use std::ffi::c_void;
use std::io;
use std::sync::atomic::{AtomicU64, Ordering};
use windows_sys::Win32::Storage::FileSystem::{
CloseIoRing, CreateIoRing, GetIoRingInfo, IORING_CQE, IORING_CREATE_ADVISORY_FLAGS_NONE,
IORING_CREATE_FLAGS, IORING_CREATE_REQUIRED_FLAGS_NONE, IORING_INFO, IORING_OP_CANCEL,
IORING_OP_CODE, IORING_OP_FLUSH, IORING_OP_NOP, IORING_OP_READ, IORING_OP_REGISTER_BUFFERS,
IORING_OP_REGISTER_FILES, IORING_OP_WRITE, IsIoRingOpSupported, PopIoRingCompletion,
SubmitIoRing,
};
use crate::capability::{RingVersion, capabilities};
use crate::error::check;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct RingId(u64);
impl RingId {
fn next() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(1);
Self(NEXT.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Op {
Nop,
Read,
Write,
Flush,
RegisterFiles,
RegisterBuffers,
Cancel,
}
impl Op {
const ALL: [Op; 7] = [
Op::Nop,
Op::Read,
Op::Write,
Op::Flush,
Op::RegisterFiles,
Op::RegisterBuffers,
Op::Cancel,
];
#[must_use]
pub fn code(self) -> IORING_OP_CODE {
match self {
Op::Nop => IORING_OP_NOP,
Op::Read => IORING_OP_READ,
Op::Write => IORING_OP_WRITE,
Op::Flush => IORING_OP_FLUSH,
Op::RegisterFiles => IORING_OP_REGISTER_FILES,
Op::RegisterBuffers => IORING_OP_REGISTER_BUFFERS,
Op::Cancel => IORING_OP_CANCEL,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct OpSupport(u8);
impl OpSupport {
unsafe fn probe(handle: *mut c_void) -> Self {
let mut mask = 0_u8;
for (index, op) in Op::ALL.iter().enumerate() {
let supported = unsafe { IsIoRingOpSupported(handle, op.code()) };
if supported != 0 {
mask |= 1 << index;
}
}
Self(mask)
}
fn contains(self, op: Op) -> bool {
let index = Op::ALL
.iter()
.position(|&candidate| candidate == op)
.expect("Op::ALL is exhaustive");
self.0 & (1 << index) != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RingInfo {
pub version: RingVersion,
pub submission_queue_size: u32,
pub completion_queue_size: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct Completion {
user_data: usize,
result_code: windows_sys::core::HRESULT,
information: usize,
ring_id: RingId,
}
impl Completion {
#[must_use]
pub fn user_data(&self) -> usize {
self.user_data
}
#[must_use]
pub(crate) fn ring_id(&self) -> RingId {
self.ring_id
}
pub fn result(&self) -> io::Result<usize> {
check(self.result_code)?;
Ok(self.information)
}
#[cfg(test)]
pub(crate) fn synthetic(
user_data: usize,
result_code: windows_sys::core::HRESULT,
ring_id: RingId,
) -> Self {
Self {
user_data,
result_code,
information: 0,
ring_id,
}
}
}
const S_FALSE: windows_sys::core::HRESULT = 1;
const RUN_DOWN_POLL_MS: u32 = 50;
#[derive(Debug)]
pub struct IoRing {
handle: *mut c_void,
version: RingVersion,
supported_ops: OpSupport,
ring_id: RingId,
next_user_data: usize,
outstanding: usize,
registered_files: u32,
registered_buffers: u32,
}
unsafe impl Send for IoRing {}
impl IoRing {
pub fn new(submission_queue_size: u32, completion_queue_size: u32) -> io::Result<Self> {
let caps = capabilities()?;
let version = RingVersion::HIGHEST_KNOWN.min(caps.max_version);
Self::with_version(version, submission_queue_size, completion_queue_size)
}
pub fn with_version(
version: RingVersion,
submission_queue_size: u32,
completion_queue_size: u32,
) -> io::Result<Self> {
let flags = IORING_CREATE_FLAGS {
Required: IORING_CREATE_REQUIRED_FLAGS_NONE,
Advisory: IORING_CREATE_ADVISORY_FLAGS_NONE,
};
let mut handle: *mut c_void = std::ptr::null_mut();
let hr = unsafe {
CreateIoRing(
version.raw(),
flags,
submission_queue_size,
completion_queue_size,
&raw mut handle,
)
};
check(hr)?;
let supported_ops = unsafe { OpSupport::probe(handle) };
Ok(Self {
handle,
version,
supported_ops,
ring_id: RingId::next(),
next_user_data: 0,
outstanding: 0,
registered_files: 0,
registered_buffers: 0,
})
}
#[must_use]
pub fn version(&self) -> RingVersion {
self.version
}
pub fn info(&self) -> io::Result<RingInfo> {
let mut raw = IORING_INFO::default();
let hr = unsafe { GetIoRingInfo(self.handle, &raw mut raw) };
check(hr)?;
Ok(RingInfo {
version: RingVersion::from_raw(raw.IoRingVersion),
submission_queue_size: raw.SubmissionQueueSize,
completion_queue_size: raw.CompletionQueueSize,
})
}
#[must_use]
pub fn supports(&self, op: Op) -> bool {
self.supported_ops.contains(op)
}
#[must_use]
pub fn supports_raw(&self, op_code: IORING_OP_CODE) -> bool {
unsafe { IsIoRingOpSupported(self.handle, op_code) != 0 }
}
#[must_use]
pub fn registered_file_count(&self) -> u32 {
self.registered_files
}
#[must_use]
pub fn registered_buffer_count(&self) -> u32 {
self.registered_buffers
}
pub(crate) fn reserve_registered_files(&mut self, count: u32) {
self.registered_files = self.registered_files.saturating_add(count);
}
pub(crate) fn reserve_registered_buffers(&mut self, count: u32) {
self.registered_buffers = self.registered_buffers.saturating_add(count);
}
#[must_use]
pub fn outstanding(&self) -> usize {
self.outstanding
}
pub(crate) fn reserve_user_data(&mut self) -> io::Result<usize> {
let id = self.next_user_data;
self.next_user_data = id
.checked_add(1)
.ok_or_else(|| io::Error::other("IoRing operation identity space exhausted"))?;
self.outstanding += 1;
Ok(id)
}
pub(crate) fn record_completion(&mut self) {
self.outstanding = self.outstanding.saturating_sub(1);
}
pub(crate) fn cancel_reservation(&mut self) {
self.outstanding = self.outstanding.saturating_sub(1);
}
pub(crate) fn raw_handle(&self) -> *mut c_void {
self.handle
}
pub(crate) fn ring_id(&self) -> RingId {
self.ring_id
}
pub unsafe fn push_raw(
&mut self,
build: impl FnOnce(*mut c_void, usize) -> windows_sys::core::HRESULT,
) -> io::Result<usize> {
let user_data = self.reserve_user_data()?;
let hr = build(self.handle, user_data);
if let Err(error) = check(hr) {
self.cancel_reservation();
return Err(error);
}
Ok(user_data)
}
pub fn run_down(&mut self) -> io::Result<()> {
while self.outstanding > 0 {
let mut submitted = 0_u32;
let hr = unsafe { SubmitIoRing(self.handle, 1, RUN_DOWN_POLL_MS, &raw mut submitted) };
check(hr)?;
self.drain_for_rundown()?;
}
Ok(())
}
fn drain_for_rundown(&mut self) -> io::Result<()> {
while self.try_pop()?.is_some() {}
Ok(())
}
pub fn try_pop(&mut self) -> io::Result<Option<Completion>> {
let mut cqe = IORING_CQE {
UserData: 0,
ResultCode: 0,
Information: 0,
};
let hr = unsafe { PopIoRingCompletion(self.handle, &raw mut cqe) };
if hr == S_FALSE {
return Ok(None);
}
check(hr)?;
self.record_completion();
Ok(Some(Completion {
user_data: cqe.UserData,
result_code: cqe.ResultCode,
information: cqe.Information,
ring_id: self.ring_id,
}))
}
}
impl Drop for IoRing {
fn drop(&mut self) {
if let Err(error) = self.run_down() {
debug_assert!(false, "IoRing rundown failed before close: {error}");
}
let hr = unsafe { CloseIoRing(self.handle) };
debug_assert!(hr >= 0, "CloseIoRing failed: 0x{:08X}", hr as u32);
}
}
#[cfg(test)]
mod tests;