blob: 7dcdfdc2ced62183fb8d5829d5d9fc0f84e4d7c8 [file]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![allow(non_upper_case_globals)]
// Expects are used for programming errors.
#![allow(clippy::unwrap_in_result)]
use bitflags::bitflags;
use starnix_core::mm::memory::MemoryObject;
use starnix_core::mm::{
DesiredAddress, IOVecPtr, MappingName, MappingOptions, MemoryAccessor, MemoryAccessorExt,
PAGE_SIZE, ProtectionFlags, read_to_object_as_bytes,
};
use starnix_core::task::CurrentTask;
use starnix_core::vfs::socket::syscalls::{
MsgHdrPtr, MsgHdrRef, WithAlternateBuffer, recvmsg_impl, sys_recvfrom, sys_sendmsg, sys_sendto,
};
use starnix_core::vfs::syscalls::{
sys_pread64, sys_preadv2, sys_pwrite64, sys_pwritev2, sys_read, sys_write,
};
use starnix_core::vfs::{
Anon, FdNumber, FileHandle, FileObject, FileOps, NamespaceNode, fileops_impl_dataless,
fileops_impl_nonseekable, fileops_impl_noop_sync,
};
use starnix_logging::{set_zx_name, track_stub};
use starnix_sync::{IoUringStateLock, LockDepMutex};
use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
use starnix_types::user_buffer::{UserBuffer, UserBuffers};
use starnix_uapi::errors::Errno;
use starnix_uapi::file_mode::Access;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::user_address::{ArchSpecific, UserAddress, UserRef};
use starnix_uapi::user_value::UserValue;
use starnix_uapi::{
IORING_FEAT_SINGLE_MMAP, IORING_OFF_CQ_RING, IORING_OFF_SQ_RING, IORING_OFF_SQES, errno, error,
io_cqring_offsets, io_sqring_offsets, io_uring_cqe, io_uring_op, io_uring_op_IORING_OP_ACCEPT,
io_uring_op_IORING_OP_ASYNC_CANCEL, io_uring_op_IORING_OP_CLOSE, io_uring_op_IORING_OP_CONNECT,
io_uring_op_IORING_OP_EPOLL_CTL, io_uring_op_IORING_OP_FADVISE,
io_uring_op_IORING_OP_FALLOCATE, io_uring_op_IORING_OP_FILES_UPDATE,
io_uring_op_IORING_OP_FSYNC, io_uring_op_IORING_OP_LINK_TIMEOUT, io_uring_op_IORING_OP_MADVISE,
io_uring_op_IORING_OP_NOP, io_uring_op_IORING_OP_OPENAT, io_uring_op_IORING_OP_OPENAT2,
io_uring_op_IORING_OP_POLL_ADD, io_uring_op_IORING_OP_POLL_REMOVE, io_uring_op_IORING_OP_READ,
io_uring_op_IORING_OP_READ_FIXED, io_uring_op_IORING_OP_READV, io_uring_op_IORING_OP_RECV,
io_uring_op_IORING_OP_RECVMSG, io_uring_op_IORING_OP_SEND, io_uring_op_IORING_OP_SENDMSG,
io_uring_op_IORING_OP_STATX, io_uring_op_IORING_OP_SYNC_FILE_RANGE,
io_uring_op_IORING_OP_TIMEOUT, io_uring_op_IORING_OP_TIMEOUT_REMOVE,
io_uring_op_IORING_OP_WRITE, io_uring_op_IORING_OP_WRITE_FIXED, io_uring_op_IORING_OP_WRITEV,
io_uring_params, io_uring_sqe, io_uring_sqe_flags_bit_IOSQE_ASYNC_BIT,
io_uring_sqe_flags_bit_IOSQE_BUFFER_SELECT_BIT,
io_uring_sqe_flags_bit_IOSQE_CQE_SKIP_SUCCESS_BIT, io_uring_sqe_flags_bit_IOSQE_FIXED_FILE_BIT,
io_uring_sqe_flags_bit_IOSQE_IO_DRAIN_BIT, io_uring_sqe_flags_bit_IOSQE_IO_HARDLINK_BIT,
io_uring_sqe_flags_bit_IOSQE_IO_LINK_BIT, off_t, socklen_t, uapi,
};
use std::sync::Arc;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
// See https://github.com/google/gvisor/blob/master/pkg/abi/linux/iouring.go#L47
pub const IORING_MAX_ENTRIES: u32 = 1 << 15; // 32768
const IORING_MAX_CQ_ENTRIES: u32 = 2 * IORING_MAX_ENTRIES;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IoRingSetupFlags: u32 {
const IoPoll = starnix_uapi::IORING_SETUP_IOPOLL;
const SqPoll = starnix_uapi::IORING_SETUP_SQPOLL;
const SqAff = starnix_uapi::IORING_SETUP_SQ_AFF;
const CqSize = starnix_uapi::IORING_SETUP_CQSIZE;
const Clamp = starnix_uapi::IORING_SETUP_CLAMP;
const AttachWq = starnix_uapi::IORING_SETUP_ATTACH_WQ;
const RDisabled = starnix_uapi::IORING_SETUP_R_DISABLED;
const SubmitAll = starnix_uapi::IORING_SETUP_SUBMIT_ALL;
const CoopTaskRun = starnix_uapi::IORING_SETUP_COOP_TASKRUN;
const TaskRunFlag = starnix_uapi::IORING_SETUP_TASKRUN_FLAG;
const SqE128 = starnix_uapi::IORING_SETUP_SQE128;
const CqE32 = starnix_uapi::IORING_SETUP_CQE32;
const SingleIssuer = starnix_uapi::IORING_SETUP_SINGLE_ISSUER;
const DeferTaskRun = starnix_uapi::IORING_SETUP_DEFER_TASKRUN;
const NoMmap = starnix_uapi::IORING_SETUP_NO_MMAP;
const RegisteredFdOnly = starnix_uapi::IORING_SETUP_REGISTERED_FD_ONLY;
const NoSqArray = starnix_uapi::IORING_SETUP_NO_SQARRAY;
/// The flags that we support. Specifying a flag outside of this set will generate an
/// error.
const SupportedFlags = starnix_uapi::IORING_SETUP_CQSIZE |
starnix_uapi::IORING_SETUP_COOP_TASKRUN |
starnix_uapi::IORING_SETUP_TASKRUN_FLAG |
starnix_uapi::IORING_SETUP_SINGLE_ISSUER |
starnix_uapi::IORING_SETUP_DEFER_TASKRUN;
/// The flags that we ignore. Specifying a flags in this set will not generate an
/// error but will have no effect.
// TODO(https://fxbug.dev/297431387): Implement these flags.
const IgnoredFlags = starnix_uapi::IORING_SETUP_COOP_TASKRUN |
starnix_uapi::IORING_SETUP_TASKRUN_FLAG |
starnix_uapi::IORING_SETUP_SINGLE_ISSUER |
starnix_uapi::IORING_SETUP_DEFER_TASKRUN;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct SqEntryFlags: u8 {
const FIXED_FILE = 1 << io_uring_sqe_flags_bit_IOSQE_FIXED_FILE_BIT;
const IO_DRAIN = 1 << io_uring_sqe_flags_bit_IOSQE_IO_DRAIN_BIT;
const IO_LINK = 1 << io_uring_sqe_flags_bit_IOSQE_IO_LINK_BIT;
const IO_HARDLINK = 1 << io_uring_sqe_flags_bit_IOSQE_IO_HARDLINK_BIT;
const ASYNC = 1 << io_uring_sqe_flags_bit_IOSQE_ASYNC_BIT;
const BUFFER_SELECT = 1 << io_uring_sqe_flags_bit_IOSQE_BUFFER_SELECT_BIT;
const CQE_SKIP_SUCCESS = 1 << io_uring_sqe_flags_bit_IOSQE_CQE_SKIP_SUCCESS_BIT;
}
}
impl IoRingSetupFlags {
fn build_and_validate_from(value: u32) -> Result<Self, Errno> {
let Some(flags) = IoRingSetupFlags::from_bits(value) else {
track_stub!(
TODO("https://fxbug.dev/297431387"),
"io_uring_setup undefined flag(s)",
value
);
return error!(EINVAL);
};
let unsupported_flags = flags.difference(IoRingSetupFlags::SupportedFlags);
if !unsupported_flags.is_empty() {
track_stub!(
TODO("https://fxbug.dev/297431387"),
"io_uring_setup unsupported flags",
unsupported_flags.bits()
);
return error!(EINVAL);
}
let ignored_flags = flags.intersection(IoRingSetupFlags::IgnoredFlags);
if !ignored_flags.is_empty() {
track_stub!(
TODO("https://fxbug.dev/297431387"),
"io_uring_setup ignored flags",
ignored_flags.bits()
);
}
// IORING_SETUP_COOP_TASKRUN requires IORING_SETUP_SINGLE_ISSUER
if flags.contains(IoRingSetupFlags::DeferTaskRun)
&& !flags.contains(IoRingSetupFlags::SingleIssuer)
{
return error!(EINVAL);
}
return Ok(flags);
}
}
type RingIndex = u32;
type UserRingBufferHeader = uapi::io_uring_buf_ring__bindgen_ty_1__bindgen_ty_1;
type UserRingBufferEntry = uapi::io_uring_buf;
static_assertions::const_assert_eq!(
std::mem::size_of::<u16>(),
uapi::size_of_field!(UserRingBufferHeader, tail)
);
static_assertions::const_assert_eq!(
std::mem::size_of::<UserRingBufferHeader>(),
std::mem::size_of::<UserRingBufferEntry>()
);
/// The control header at the start of the shared buffer.
///
/// This structure is not declared in the Linux UAPI. Instead, userspace learns about its structure
/// from the SQ and CQ offsets returned by `io_uring_setup()`.
///
/// We determined this structure by running `io_uring_setup()` and observing the placement of each
/// field. The total size of the structure is 64 bytes, which we determined by looking at the
/// offset of the cqes offset. It's likely that many of the bytes at the end of this structure are
/// just padding for alignment.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, IntoBytes, FromBytes, KnownLayout, Immutable)]
struct ControlHeader {
/// The index of the first element in the submission queue.
///
/// These values use the full range of u32, wrapping around on overflow. To find the entry in
/// the ring buffer, you need to take this index modulo `sq_ring_entries` or, equivalently,
/// mask this value with `sq_ring_mask`.
sq_head: u32,
/// The index of the first element beyond the end of the submission queue.
///
/// The number of items in the queue is defined to be `sq_tail` - `sq_head`, which means the
/// queue is empty if the head and tail are equal.
sq_tail: u32,
/// The index of the first element in the completion queue.
///
/// These values use the full range of u32, wrapping around on overflow. To find the entry in
/// the ring buffer, you need to take this index modulo `cq_ring_entries` or, equivalently,
/// mask this value with `cq_ring_mask`.
cq_head: u32,
/// The index of the first element beyond the end of the completion queue.
///
/// The number of items in the queue is defined to be `cq_tail` - `cq_head`, which means the
/// queue is empty if the head and tail are equal.
cq_tail: u32,
/// The mask to apply to map `sq_head` and `sq_tail` into the ring buffer.
sq_ring_mask: u32,
/// The mask to apply to map `cq_head` and `cq_tail` into the ring buffer.
cq_ring_mask: u32,
/// The number of entries in the submission queue.
sq_ring_entries: u32,
/// The number of entries in the completion queue.
cq_ring_entries: u32,
/// The number of submission queue entries that were dropped for being malformed.
sq_dropped: u32,
sq_flags: u32,
cq_flags: u32,
/// The number of completion queue entries that were not placed in the completion queue because
/// there were no slots available in the ring buffer.
cq_overflow: u32,
_padding: [u8; 16],
}
const RING_ALIGNMENT: usize = 64;
// From params.cq_off.cqes reported by sys_io_uring_setup.
static_assertions::const_assert_eq!(std::mem::size_of::<ControlHeader>(), RING_ALIGNMENT);
/// An entry in the submission queue.
///
/// We cannot use the bindgen type generated for `io_uring_sqe` directly because that type contains
/// unions. Instead, we redefine the type here and assert that the layout matches the one that
/// defined by bindgen.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, IntoBytes, FromBytes, KnownLayout, Immutable)]
struct SqEntry {
opcode: u8,
flags: u8,
ioprio: u16,
raw_fd: i32,
field0: u64,
field1: u64,
len: u32,
op_flags: u32,
user_data: u64,
buf_index_or_group: u16,
personality: u16,
field2: u32,
field3: [u64; 2usize],
}
uapi::check_arch_independent_same_layout! {
SqEntry = io_uring_sqe {
opcode => opcode,
flags => flags,
ioprio => ioprio,
raw_fd => fd,
field0 => __bindgen_anon_1,
field1 => __bindgen_anon_2,
len => len,
op_flags => __bindgen_anon_3,
user_data => user_data,
buf_index_or_group => __bindgen_anon_4,
personality => personality,
field2 => __bindgen_anon_5,
field3 => __bindgen_anon_6,
}
}
uapi::check_arch_independent_layout! {
io_uring_recvmsg_out{
namelen,
controllen,
payloadlen,
flags,
}
}
impl SqEntry {
fn complete(&self, result: Result<SyscallResult, Errno>, flags: u32) -> CqEntry {
let res = match result {
Ok(return_value) => return_value.value() as i32,
Err(errno) => errno.return_value() as i32,
};
CqEntry { user_data: self.user_data, res, flags }
}
fn fd(&self) -> FdNumber {
FdNumber::from_raw(self.raw_fd)
}
fn iovec_addr<Arch: ArchSpecific>(&self, arch: &Arch) -> IOVecPtr {
IOVecPtr::new(arch, self.field1)
}
fn iovec_count(&self) -> UserValue<i32> {
(self.len as i32).into()
}
fn address(&self) -> UserAddress {
self.field1.into()
}
fn length(&self) -> usize {
self.len as usize
}
fn offset(&self) -> off_t {
self.field0 as off_t
}
fn buf_index(&self) -> usize {
self.buf_index_or_group as usize
}
fn group(&self) -> u16 {
self.buf_index_or_group
}
}
/// An entry in the completion queue.
///
/// We cannot use the bindgen type generated for `io_uring_cqe` directly because that type contains
/// a variable length array. Instead, we redefine the type here and assert that the layout matches
/// the one that defined by bindgen.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, IntoBytes, FromBytes, KnownLayout, Immutable)]
struct CqEntry {
pub user_data: u64,
pub res: i32,
pub flags: u32,
}
static_assertions::assert_eq_size!(CqEntry, io_uring_cqe);
static_assertions::const_assert_eq!(
std::mem::offset_of!(CqEntry, user_data),
std::mem::offset_of!(io_uring_cqe, user_data)
);
static_assertions::const_assert_eq!(
std::mem::offset_of!(CqEntry, res),
std::mem::offset_of!(io_uring_cqe, res)
);
static_assertions::const_assert_eq!(
std::mem::offset_of!(CqEntry, flags),
std::mem::offset_of!(io_uring_cqe, flags)
);
const CQES_OFFSET: usize = std::mem::size_of::<ControlHeader>();
#[inline]
fn align_ring_field(offset: usize) -> usize {
offset.next_multiple_of(RING_ALIGNMENT)
}
struct IoUringMetadata {
/// The number of entries in the submission queue.
sq_entries: u32,
/// The number of entries in the completion queue.
cq_entries: u32,
}
impl IoUringMetadata {
/// The offset of the compleition queue entry with the given index.
///
/// The offset is from the start of the `ring_buffer` VMO.
fn cq_entry_offset(&self, index: u32) -> u64 {
let index = index % self.cq_entries;
(CQES_OFFSET + index as usize * std::mem::size_of::<io_uring_cqe>()) as u64
}
/// The offset of first completion queue entry in the `ring_buffer` VMO.
fn cqes_offset(&self) -> usize {
CQES_OFFSET
}
/// The offset of submission queue indirection array in the `ring_buffer` VMO.
fn array_offset(&self) -> usize {
CQES_OFFSET
+ align_ring_field(self.cq_entries as usize * std::mem::size_of::<io_uring_cqe>())
}
/// The offset of submission queue indirection array entry with the given index in the
/// `ring_buffer` VMO.
fn array_entry_offset(&self, index: u32) -> u64 {
let index = index % self.sq_entries;
(self.array_offset() + index as usize * std::mem::size_of::<RingIndex>()) as u64
}
/// The number of bytes in the `ring_buffer` VMO.
fn ring_buffer_size(&self) -> usize {
self.array_offset() + self.sq_entries as usize * std::mem::size_of::<RingIndex>()
}
/// The offset of the submission queue entry with the given index in the `sq_entries` VMO.
///
/// This index is the actual index of the submission queue entry, after indirecting through the
/// indirecton array.
fn sq_entry_offset(&self, index: u32) -> u64 {
let index = index % self.sq_entries;
(index as usize * std::mem::size_of::<io_uring_sqe>()) as u64
}
/// The number of bytes in the `sq_entries` VMO.
fn sq_entries_size(&self) -> usize {
self.sq_entries as usize * std::mem::size_of::<io_uring_sqe>()
}
}
#[repr(u32)]
enum Op {
Accept = io_uring_op_IORING_OP_ACCEPT,
AsyncCancel = io_uring_op_IORING_OP_ASYNC_CANCEL,
Close = io_uring_op_IORING_OP_CLOSE,
Connect = io_uring_op_IORING_OP_CONNECT,
EpollCtl = io_uring_op_IORING_OP_EPOLL_CTL,
FAdvise = io_uring_op_IORING_OP_FADVISE,
FAllocate = io_uring_op_IORING_OP_FALLOCATE,
FilesUpdate = io_uring_op_IORING_OP_FILES_UPDATE,
FSync = io_uring_op_IORING_OP_FSYNC,
LinkTimeout = io_uring_op_IORING_OP_LINK_TIMEOUT,
MAdvise = io_uring_op_IORING_OP_MADVISE,
NOP = io_uring_op_IORING_OP_NOP,
OpenAt = io_uring_op_IORING_OP_OPENAT,
OpenAt2 = io_uring_op_IORING_OP_OPENAT2,
PollAdd = io_uring_op_IORING_OP_POLL_ADD,
PollRemove = io_uring_op_IORING_OP_POLL_REMOVE,
Read = io_uring_op_IORING_OP_READ,
ReadV = io_uring_op_IORING_OP_READV,
ReadFixed = io_uring_op_IORING_OP_READ_FIXED,
Recv = io_uring_op_IORING_OP_RECV,
RecvMsg = io_uring_op_IORING_OP_RECVMSG,
Send = io_uring_op_IORING_OP_SEND,
SendMsg = io_uring_op_IORING_OP_SENDMSG,
StatX = io_uring_op_IORING_OP_STATX,
SyncFileRange = io_uring_op_IORING_OP_SYNC_FILE_RANGE,
Timeout = io_uring_op_IORING_OP_TIMEOUT,
TimeoutRemove = io_uring_op_IORING_OP_TIMEOUT_REMOVE,
Write = io_uring_op_IORING_OP_WRITE,
WriteV = io_uring_op_IORING_OP_WRITEV,
WriteFixed = io_uring_op_IORING_OP_WRITE_FIXED,
}
impl Op {
fn from_code(opcode: io_uring_op) -> Result<Op, Errno> {
match opcode {
io_uring_op_IORING_OP_ACCEPT => Ok(Self::Accept),
io_uring_op_IORING_OP_ASYNC_CANCEL => Ok(Self::AsyncCancel),
io_uring_op_IORING_OP_CLOSE => Ok(Self::Close),
io_uring_op_IORING_OP_CONNECT => Ok(Self::Connect),
io_uring_op_IORING_OP_EPOLL_CTL => Ok(Self::EpollCtl),
io_uring_op_IORING_OP_FADVISE => Ok(Self::FAdvise),
io_uring_op_IORING_OP_FALLOCATE => Ok(Self::FAllocate),
io_uring_op_IORING_OP_FILES_UPDATE => Ok(Self::FilesUpdate),
io_uring_op_IORING_OP_FSYNC => Ok(Self::FSync),
io_uring_op_IORING_OP_LINK_TIMEOUT => Ok(Self::LinkTimeout),
io_uring_op_IORING_OP_MADVISE => Ok(Self::MAdvise),
io_uring_op_IORING_OP_NOP => Ok(Self::NOP),
io_uring_op_IORING_OP_OPENAT => Ok(Self::OpenAt),
io_uring_op_IORING_OP_OPENAT2 => Ok(Self::OpenAt2),
io_uring_op_IORING_OP_POLL_ADD => Ok(Self::PollAdd),
io_uring_op_IORING_OP_POLL_REMOVE => Ok(Self::PollRemove),
io_uring_op_IORING_OP_READ => Ok(Self::Read),
io_uring_op_IORING_OP_READV => Ok(Self::ReadV),
io_uring_op_IORING_OP_READ_FIXED => Ok(Self::ReadFixed),
io_uring_op_IORING_OP_RECV => Ok(Self::Recv),
io_uring_op_IORING_OP_RECVMSG => Ok(Self::RecvMsg),
io_uring_op_IORING_OP_SEND => Ok(Self::Send),
io_uring_op_IORING_OP_SENDMSG => Ok(Self::SendMsg),
io_uring_op_IORING_OP_STATX => Ok(Self::StatX),
io_uring_op_IORING_OP_SYNC_FILE_RANGE => Ok(Self::SyncFileRange),
io_uring_op_IORING_OP_TIMEOUT => Ok(Self::Timeout),
io_uring_op_IORING_OP_TIMEOUT_REMOVE => Ok(Self::TimeoutRemove),
io_uring_op_IORING_OP_WRITE => Ok(Self::Write),
io_uring_op_IORING_OP_WRITEV => Ok(Self::WriteV),
io_uring_op_IORING_OP_WRITE_FIXED => Ok(Self::WriteFixed),
_ => error!(EINVAL),
}
}
}
// Currently, we read and write the memory shared with userspace via the VMOs. In the future, we
// will likely want to map the memory for these VMOs into the kernel address space so that we can
// access their contents more efficiently and so that we can perform the appropriate atomic
// operations.
// TODO(https://fxbug.dev/297431387): Map `ring_buffer` and `sq_entries` into kernel memory so that
// this operation becomes memcpy.
fn read_object<T: FromBytes>(memory_object: &MemoryObject, offset: u64) -> Result<T, Errno> {
// SAFETY: read_uninit returns an error if not all the bytes were read.
unsafe {
read_to_object_as_bytes(|buf| {
memory_object.read_uninit(buf, offset).map_err(|_| errno!(EFAULT))?;
Ok(())
})
}
}
// TODO(https://fxbug.dev/297431387): Map `ring_buffer` and `sq_entries` into kernel memory so that
// this operation becomes memcpy.
fn write_object<T: IntoBytes + Immutable>(
memory_object: &MemoryObject,
offset: u64,
value: &T,
) -> Result<(), Errno> {
memory_object.write(value.as_bytes(), offset).map_err(|_| errno!(EFAULT))
}
/// The memory the IoUring shares with userspace.
struct IoUringQueue {
/// Metadata about the layout of this memory.
metadata: IoUringMetadata,
/// The primary ring buffer.
///
/// The ring buffer's memory layout is as follows:
///
/// ControlHeader
/// N completion queue entries
/// An array of u32 values used to indirect indices to the submission queue entries
///
/// The ControlHeader is a fixed size, which means the completion queue entries always start
/// at the same offset in this VMO.
ring_buffer: Arc<MemoryObject>,
/// A separate VMO for the submission queue entries.
///
/// This entries are not necessarily populated in order. Instead, userspace uses the array of
/// submission queue indices in the `ring_buffer` in order. That array gives the indices of
/// the actual submission queue entries.
///
/// IoUring uses this index indirection scheme because submission queue entries do not always
/// complete in the same order they were submitted.
sq_entries: Arc<MemoryObject>,
}
impl IoUringQueue {
fn new(metadata: IoUringMetadata) -> Result<Self, Errno> {
let ring_buffer =
zx::Vmo::create(metadata.ring_buffer_size() as u64).map_err(|_| errno!(ENOMEM))?;
set_zx_name(&ring_buffer, b"io_uring:ring");
let sq_entries =
zx::Vmo::create(metadata.sq_entries_size() as u64).map_err(|_| errno!(ENOMEM))?;
set_zx_name(&sq_entries, b"io_uring:sqes");
Ok(Self {
metadata,
ring_buffer: Arc::new(ring_buffer.into()),
sq_entries: Arc::new(sq_entries.into()),
})
}
fn write_header(&self, header: ControlHeader) -> Result<(), Errno> {
write_object(&self.ring_buffer, 0, &header).map_err(|_| errno!(ENOMEM))
}
fn read_sq_head(&self) -> Result<u32, Errno> {
read_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, sq_head) as u64)
}
fn write_sq_head(&self, value: u32) -> Result<(), Errno> {
write_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, sq_head) as u64, &value)
}
fn read_sq_tail(&self) -> Result<u32, Errno> {
// TODO(https://fxbug.dev/297431387): Reading the tail field should be atomic with ordering
// acquire once we map these buffers into kernel memory.
read_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, sq_tail) as u64)
}
fn read_cq_head(&self) -> Result<u32, Errno> {
// TODO(https://fxbug.dev/297431387): Reading the head field should be atomic with ordering
// acquire once we map these buffers into kernel memory.
read_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, cq_head) as u64)
}
fn read_cq_tail(&self) -> Result<u32, Errno> {
read_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, cq_tail) as u64)
}
fn write_cq_tail(&self, value: u32) -> Result<(), Errno> {
// TODO(https://fxbug.dev/297431387): Writing the tail field should be atomic with ordering
// release once we map these buffers into kernel memory.
write_object(&self.ring_buffer, std::mem::offset_of!(ControlHeader, cq_tail) as u64, &value)
}
fn read_array_entry(&self, index: u32) -> Result<u32, Errno> {
read_object(&self.ring_buffer, self.metadata.array_entry_offset(index))
}
fn read_sq_entry(&self, index: u32) -> Result<SqEntry, Errno> {
let sqe_index = self.read_array_entry(index)?;
read_object(&self.sq_entries, self.metadata.sq_entry_offset(sqe_index))
}
fn write_cq_entry(&self, index: u32, entry: &CqEntry) -> Result<(), Errno> {
write_object(&self.ring_buffer, self.metadata.cq_entry_offset(index), entry)
}
fn increment_overflow(&self) -> Result<(), Errno> {
// TODO(https://fxbug.dev/297431387): Incrementing the overflow count should be an atomic
// operation.
let offset = std::mem::offset_of!(ControlHeader, cq_overflow) as u64;
let mut overflow: u32 = read_object(&self.ring_buffer, offset)?;
overflow = overflow.saturating_add(1);
write_object(&self.ring_buffer, offset, &overflow)
}
/// Pop an entry off the submission queue and update the head to let userspace queue more
/// entries.
///
/// Returns `None` if the submission queue is empty.
fn pop_sq_entry(&self) -> Result<Option<SqEntry>, Errno> {
let tail = self.read_sq_tail()?;
let head = self.read_sq_head()?;
if head != tail {
let sq_entry = self.read_sq_entry(head)?;
self.write_sq_head(head.wrapping_add(1))?;
Ok(Some(sq_entry))
} else {
Ok(None)
}
}
/// Push an entry onto the completion queue and update the tail to let userspace know a new
/// entry is available.
///
/// If there is no room in the completion queue, this function will increment the overflow
/// counter.
fn push_cq_entry(&self, entry: &CqEntry) -> Result<(), Errno> {
let head = self.read_cq_head()?;
let tail = self.read_cq_tail()?;
// Check that the offset for the tail location doesn't collide with the head of the queue.
// This can happen because the entries are stored in a ring buffer.
if head != tail
&& self.metadata.cq_entry_offset(tail) == self.metadata.cq_entry_offset(head)
{
self.increment_overflow()?;
} else {
self.write_cq_entry(tail, entry)?;
self.write_cq_tail(tail.wrapping_add(1))?;
}
Ok(())
}
}
pub struct IoUringFileObject {
queue: IoUringQueue,
state: LockDepMutex<IoUringFileMutableState, IoUringStateLock>,
_flags: IoRingSetupFlags,
}
#[derive(Default, Debug)]
struct IoUringFileMutableState {
registered_buffers: UserBuffers,
registered_iobuffers: Vec<IoUringProviderRingBuffer>,
}
impl IoUringFileObject {
pub fn new_file(
current_task: &CurrentTask,
entries: u32,
params: &mut io_uring_params,
) -> Result<FileHandle, Errno> {
let flags = IoRingSetupFlags::build_and_validate_from(params.flags)?;
let sq_entries = entries.next_power_of_two();
let cq_entries = if flags.contains(IoRingSetupFlags::CqSize) {
UserValue::from_raw(params.cq_entries)
.validate(sq_entries..IORING_MAX_CQ_ENTRIES)
.ok_or_else(|| errno!(EINVAL))?
.next_power_of_two()
} else {
// This operation cannot overflow because sq_entries is capped at IORING_MAX_ENTRIES,
// which is only 15 bits.
sq_entries * 2
};
let queue =
IoUringQueue::new(IoUringMetadata { sq_entries: sq_entries, cq_entries: cq_entries })?;
queue.write_header(ControlHeader {
sq_ring_mask: sq_entries - 1,
cq_ring_mask: cq_entries - 1,
sq_ring_entries: sq_entries,
cq_ring_entries: cq_entries,
..Default::default()
})?;
params.sq_entries = sq_entries;
params.cq_entries = cq_entries;
params.features = IORING_FEAT_SINGLE_MMAP;
params.sq_off = io_sqring_offsets {
head: std::mem::offset_of!(ControlHeader, sq_head) as u32,
tail: std::mem::offset_of!(ControlHeader, sq_tail) as u32,
ring_mask: std::mem::offset_of!(ControlHeader, sq_ring_mask) as u32,
ring_entries: std::mem::offset_of!(ControlHeader, sq_ring_entries) as u32,
flags: std::mem::offset_of!(ControlHeader, sq_flags) as u32,
dropped: std::mem::offset_of!(ControlHeader, sq_dropped) as u32,
array: queue.metadata.array_offset() as u32,
..Default::default()
};
params.cq_off = io_cqring_offsets {
head: std::mem::offset_of!(ControlHeader, cq_head) as u32,
tail: std::mem::offset_of!(ControlHeader, cq_tail) as u32,
ring_mask: std::mem::offset_of!(ControlHeader, cq_ring_mask) as u32,
ring_entries: std::mem::offset_of!(ControlHeader, cq_ring_entries) as u32,
overflow: std::mem::offset_of!(ControlHeader, cq_overflow) as u32,
cqes: queue.metadata.cqes_offset() as u32,
flags: std::mem::offset_of!(ControlHeader, cq_flags) as u32,
..Default::default()
};
let object =
Box::new(IoUringFileObject { queue, state: Default::default(), _flags: flags });
Anon::new_file(current_task, object, OpenFlags::RDWR, "[io_uring]")
}
pub fn register_buffers(&self, buffers: UserBuffers) {
// The docs for io_uring_register imply that the kernel should actually map this memory
// into its own address space when these buffers are registered. That's probably observable
// if the client changes the mappings for these addresses between the time they are
// registered and they are used. For now, we just store the addresses.
self.state.lock().registered_buffers = buffers;
}
pub fn unregister_buffers(&self) {
self.state.lock().registered_buffers.clear();
}
pub fn register_ring_buffers(
&self,
buffer_definition: uapi::io_uring_buf_reg,
) -> Result<(), Errno> {
track_stub!(
TODO("https://fxbug.dev/297431387"),
"IoUringFileObject::register_ring_buffers"
);
if !buffer_definition.ring_addr.is_multiple_of(*PAGE_SIZE) {
return error!(EINVAL);
}
if !buffer_definition.ring_entries.is_power_of_two() {
return error!(EINVAL);
}
if buffer_definition.ring_entries > IORING_MAX_ENTRIES {
return error!(EINVAL);
}
self.state
.lock()
.registered_iobuffers
.push(IoUringProviderRingBuffer::new(buffer_definition)?);
Ok(())
}
pub fn unregister_ring_buffers(
&self,
buffer_definition: uapi::io_uring_buf_reg,
) -> Result<(), Errno> {
if self
.state
.lock()
.registered_iobuffers
.extract_if(.., |buffer| buffer.config.bgid == buffer_definition.bgid)
.next()
.is_none()
{
return error!(EINVAL);
}
Ok(())
}
pub fn ring_buffer_status(
&self,
buffer_status: &mut uapi::io_uring_buf_status,
) -> Result<(), Errno> {
let state = self.state.lock();
let Some(buffer) = state
.registered_iobuffers
.iter()
.find(|buffer| buffer.config.bgid as u32 == buffer_status.buf_group)
else {
return error!(EINVAL);
};
buffer_status.head = buffer.head as u32;
Ok(())
}
pub fn enter(
&self,
current_task: &CurrentTask,
to_submit: u32,
_min_complete: u32,
_flags: u32,
) -> Result<u32, Errno> {
let mut submitted = 0;
while let Some(sq_entry) = self.queue.pop_sq_entry()? {
submitted += 1;
// We currently act as if every SqEntry has IOSQE_IO_DRAIN.
let mut complete_flags: u32 = 0;
let result = self.execute(current_task, &sq_entry, &mut complete_flags);
let cq_entry = sq_entry.complete(result, complete_flags);
self.queue.push_cq_entry(&cq_entry)?;
if submitted >= to_submit {
break;
}
}
Ok(submitted)
}
fn has_registered_buffers(&self) -> bool {
!self.state.lock().registered_buffers.is_empty()
}
fn check_buffer(&self, entry: &SqEntry) -> Result<(), Errno> {
let index = entry.buf_index();
let state = self.state.lock();
let buffers = &state.registered_buffers;
if buffers.is_empty() {
return error!(EFAULT);
}
let buffer = buffers.get(index).ok_or_else(|| errno!(EINVAL))?;
if !buffer.contains(entry.address(), entry.length()) { error!(EFAULT) } else { Ok(()) }
}
fn execute(
&self,
current_task: &CurrentTask,
entry: &SqEntry,
complete_flags: &mut u32,
) -> Result<SyscallResult, Errno> {
assert_eq!(*complete_flags, 0);
let flags = SqEntryFlags::from_bits(entry.flags).ok_or_else(|| errno!(EINVAL))?;
match Op::from_code(entry.opcode as io_uring_op)? {
Op::NOP => Ok(SUCCESS),
Op::ReadV => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 || entry.buf_index() != 0 {
return error!(EINVAL);
}
sys_preadv2(
current_task,
entry.fd(),
entry.iovec_addr(current_task),
entry.iovec_count(),
entry.offset(),
SyscallArg::default(),
entry.op_flags,
)
.map(Into::into)
}
Op::WriteV => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 || entry.buf_index() != 0 {
return error!(EINVAL);
}
sys_pwritev2(
current_task,
entry.fd(),
entry.iovec_addr(current_task),
entry.iovec_count(),
entry.offset(),
SyscallArg::default(),
entry.op_flags,
)
.map(Into::into)
}
Op::ReadFixed => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 {
return error!(EINVAL);
}
// TODO(https://fxbug.dev/297431387): We're supposed to make a kernel mapping
// when the buffers are registered and we should be performing this operation using
// those kernel mappings rather than using the userspace mappings.
self.check_buffer(entry)?;
do_read(current_task, entry)
}
Op::WriteFixed => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 {
return error!(EINVAL);
}
// TODO(https://fxbug.dev/297431387): We're supposed to make a kernel mapping
// when the buffers are registered and we should be performing this operation using
// those kernel mappings rather than using the userspace mappings.
self.check_buffer(entry)?;
do_write(current_task, entry)
}
Op::Read => {
if !flags.is_empty() {
return error!(EINVAL);
}
if self.has_registered_buffers() {
return error!(EINVAL);
}
do_read(current_task, entry)
}
Op::Write => {
if !flags.is_empty() {
return error!(EINVAL);
}
if self.has_registered_buffers() {
return error!(EINVAL);
}
do_write(current_task, entry)
}
Op::SendMsg => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 {
return error!(EINVAL);
}
sys_sendmsg(
current_task,
entry.fd(),
MsgHdrPtr::new(current_task, entry.address()),
entry.op_flags,
)
.map(Into::into)
}
Op::RecvMsg => {
// A struct to hold the information about the provided buffer.
// This is needed because the buffer is claimed before the call to `recvmsg_impl`
// but the result is adjusted after.
struct RecvMsgBufferInfo {
buffer: UserBuffer,
header: uapi::io_uring_recvmsg_out,
buffer_adjustment: usize,
}
let mut flags = flags;
let mut ioprio = entry.ioprio as u32;
let msg_hdr_ptr = MsgHdrPtr::new(current_task, entry.address());
let (mut msg_hdr_ref, recv_msg_buffer_info): (
MsgHdrRef,
Option<RecvMsgBufferInfo>,
) = if flags.contains(SqEntryFlags::BUFFER_SELECT) {
flags -= SqEntryFlags::BUFFER_SELECT;
// If BUFFER_SELECT is set, the application is providing a buffer for the
// recvmsg operation.
let buffer =
self.claim_next_buffer(current_task, entry.group(), complete_flags)?;
let mut msg_hdr = current_task.read_multi_arch_object(msg_hdr_ptr)?;
// The buffer is laid out as follows:
// - io_uring_recvmsg_out
// - sockaddr (name)
// - msghdr.msg_control
// - payload
let headerlen: u32 = std::mem::size_of::<uapi::io_uring_recvmsg_out>() as u32;
let namelen: u32 = msg_hdr.name_len.try_into().map_err(|_| errno!(EINVAL))?;
let controllen: u32 =
msg_hdr.control_len.try_into().map_err(|_| errno!(EINVAL))?;
let buffer_adjustment: u32 = headerlen
.checked_add(namelen)
.and_then(|v| v.checked_add(controllen))
.ok_or_else(|| errno!(EINVAL))?;
let payloadlen: u32 = (buffer.length as u32)
.checked_sub(buffer_adjustment)
.ok_or_else(|| errno!(EINVAL))?;
let io_uring_hdr = uapi::io_uring_recvmsg_out {
namelen,
controllen,
payloadlen,
flags: msg_hdr.flags,
};
let name_addr = (buffer.address + headerlen as usize)?;
let control_addr = (name_addr + namelen as usize)?;
let payload_addr = (control_addr + controllen as usize)?;
msg_hdr.name = name_addr;
msg_hdr.control = control_addr;
// Zero out the prefix of the buffer that will contain the header, name and
// control bytes.
current_task.zero(buffer.address, buffer_adjustment as usize)?;
let msg_hdr = WithAlternateBuffer::WithAux(
msg_hdr,
UserBuffer { address: payload_addr, length: payloadlen as usize },
);
(
msg_hdr.into(),
Some(RecvMsgBufferInfo {
buffer,
header: io_uring_hdr,
buffer_adjustment: buffer_adjustment as usize,
}),
)
} else {
(msg_hdr_ptr.into(), None)
};
if ioprio & uapi::IORING_RECV_MULTISHOT > 0 {
// Ignoring IORING_RECV_MULTISHOT
// Because the IORING_CQE_F_BUFFER flags will never be set, the client will
// always have to call the syscall again.
ioprio &= !uapi::IORING_RECV_MULTISHOT;
}
if !flags.is_empty() {
return error!(EINVAL);
}
if ioprio != 0 {
return error!(EINVAL);
}
let mut count =
recvmsg_impl(current_task, entry.fd(), &mut msg_hdr_ref, entry.op_flags)?;
if let Some(recv_msg_buffer_info) = recv_msg_buffer_info {
// The result from `recvmsg_impl` is the number of bytes written to the
// payload. The result of the io_uring operation is the number of bytes
// written to the provided buffer.
// 1. Write the io_uring buffer header.
current_task.write_object(
recv_msg_buffer_info.buffer.address.into(),
&recv_msg_buffer_info.header,
)?;
// 2. Adjust the written count.
count += recv_msg_buffer_info.buffer_adjustment;
}
Ok(count.into())
}
Op::Send => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 {
return error!(EINVAL);
}
sys_sendto(
current_task,
entry.fd(),
entry.address(),
entry.length(),
entry.op_flags,
UserAddress::default(),
socklen_t::default(),
)
.map(Into::into)
}
Op::Recv => {
if !flags.is_empty() {
return error!(EINVAL);
}
if entry.ioprio != 0 {
return error!(EINVAL);
}
sys_recvfrom(
current_task,
entry.fd(),
entry.address(),
entry.length(),
entry.op_flags,
UserAddress::default(),
UserRef::default(),
)
.map(Into::into)
}
Op::FSync
| Op::PollAdd
| Op::PollRemove
| Op::SyncFileRange
| Op::Timeout
| Op::TimeoutRemove
| Op::Accept
| Op::AsyncCancel
| Op::LinkTimeout
| Op::Connect
| Op::FAllocate
| Op::OpenAt
| Op::Close
| Op::FilesUpdate
| Op::StatX
| Op::FAdvise
| Op::MAdvise
| Op::OpenAt2
| Op::EpollCtl => error!(EOPNOTSUPP),
}
}
fn claim_next_buffer(
&self,
current_task: &CurrentTask,
bgid: u16,
complete_flags: &mut u32,
) -> Result<UserBuffer, Errno> {
let mut state = self.state.lock();
let Some(buffer) =
state.registered_iobuffers.iter_mut().find(|buffer| buffer.config.bgid == bgid)
else {
return error!(EINVAL);
};
buffer.claim_next(current_task, complete_flags)
}
}
#[derive(Debug)]
struct IoUringProviderRingBuffer {
config: uapi::io_uring_buf_reg,
tail_ptr: UserRef<u16>,
entries_ptr: UserRef<UserRingBufferEntry>,
head: u16,
}
impl IoUringProviderRingBuffer {
fn new(config: uapi::io_uring_buf_reg) -> Result<Self, Errno> {
let ring_addr = UserAddress::from(config.ring_addr);
let tail_ptr =
UserRef::<u16>::from((ring_addr + std::mem::offset_of!(UserRingBufferHeader, tail))?);
let entries_ptr = UserRef::<UserRingBufferEntry>::from(ring_addr);
Ok(Self { config, tail_ptr, entries_ptr, head: 0 })
}
fn claim_next(
&mut self,
current_task: &CurrentTask,
complete_flags: &mut u32,
) -> Result<UserBuffer, Errno> {
// TODO(https://fxbug.dev/297431387): Reading the tail field should be atomic with ordering
// acquire.
let tail = current_task.read_object(self.tail_ptr)?;
if self.head == tail {
return error!(ENOBUFS);
}
let buffer_info = current_task.read_object(
self.entries_ptr.at((self.head as usize) % (self.config.ring_entries as usize))?,
)?;
self.head += 1;
*complete_flags |=
uapi::IORING_CQE_F_BUFFER | ((buffer_info.bid as u32) << uapi::IORING_CQE_BUFFER_SHIFT);
Ok(UserBuffer { address: buffer_info.addr.into(), length: buffer_info.len as usize })
}
}
fn do_read(current_task: &CurrentTask, entry: &SqEntry) -> Result<SyscallResult, Errno> {
let offset = entry.offset();
if offset == -1 {
sys_read(current_task, entry.fd(), entry.address(), entry.length()).map(Into::into)
} else {
sys_pread64(current_task, entry.fd(), entry.address(), entry.length(), offset)
.map(Into::into)
}
}
fn do_write(current_task: &CurrentTask, entry: &SqEntry) -> Result<SyscallResult, Errno> {
let offset = entry.offset();
if offset == -1 {
sys_write(current_task, entry.fd(), entry.address(), entry.length()).map(Into::into)
} else {
sys_pwrite64(current_task, entry.fd(), entry.address(), entry.length(), entry.offset())
.map(Into::into)
}
}
impl FileOps for IoUringFileObject {
fileops_impl_nonseekable!();
fileops_impl_noop_sync!();
fileops_impl_dataless!();
fn mmap(
&self,
_file: &FileObject,
current_task: &CurrentTask,
addr: DesiredAddress,
memory_offset: u64,
length: usize,
prot_flags: ProtectionFlags,
options: MappingOptions,
filename: NamespaceNode,
) -> Result<UserAddress, Errno> {
if !options.contains(MappingOptions::SHARED) {
return error!(EINVAL);
}
let magic_offset: u32 = memory_offset.try_into().map_err(|_| errno!(EINVAL))?;
let memory = match magic_offset {
IORING_OFF_SQ_RING | IORING_OFF_CQ_RING => self.queue.ring_buffer.clone(),
IORING_OFF_SQES => self.queue.sq_entries.clone(),
_ => return error!(EINVAL),
};
current_task.mm()?.map_memory(
addr,
memory,
0,
length,
prot_flags,
Access::rwx(),
options,
MappingName::File(filename.into_mapping(None)?),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[::fuchsia::test]
fn test_uring_cmd_not_supported() {
// TODO(https://fxbug.dev/505326826): If the uring_cmd operation is supported,
// add the necessary security checks.
assert!(Op::from_code(starnix_uapi::io_uring_op_IORING_OP_URING_CMD).is_err());
}
}