blob: ee2eafdbd1815dadcd357ae3b326dab4f505bae2 [file] [log] [blame]
// Copyright 2025 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.
//! Type-safe bindings for Zircon iobuffer objects.
use crate::{ok, sys, AsHandleRef, Handle, HandleBased, HandleRef, Status};
use bitflags::bitflags;
mod io_slice;
pub use self::io_slice::*;
// TODO(https://fxbug.dev/389788832): Point this at the right place when the documentation is fixed.
/// An object representing a Zircon
/// [IOBuffer](https://fuchsia.dev/reference/syscalls/iob_create).
///
/// As essentially a subtype of `Handle`, it can be freely interconverted.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Iob(Handle);
impl_handle_based!(Iob);
#[derive(Default)]
pub struct IobOptions;
#[derive(Clone, Copy)]
pub enum IobRegionType<'a> {
Private { size: u64, options: IobRegionPrivateOptions },
Shared { options: vdso_next::IobRegionSharedOptions, region: &'a vdso_next::IobSharedRegion },
}
impl IobRegionType<'_> {
fn to_raw(&self) -> (sys::zx_iob_region_type_t, sys::zx_iob_region_extension_t) {
match self {
IobRegionType::Private { .. } => (
sys::ZX_IOB_REGION_TYPE_PRIVATE,
sys::zx_iob_region_extension_t { private_region: Default::default() },
),
IobRegionType::Shared { region, .. } => (
sys::ZX_IOB_REGION_TYPE_SHARED,
sys::zx_iob_region_extension_t {
shared_region: sys::zx_iob_region_shared_t {
options: 0,
shared_region: region.raw_handle(),
padding: Default::default(),
},
},
),
}
}
}
#[derive(Clone, Copy, Default)]
pub struct IobRegionPrivateOptions;
pub struct IobRegion<'a> {
pub region_type: IobRegionType<'a>,
pub access: IobAccess,
pub discipline: IobDiscipline,
}
bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IobAccess: u32 {
const EP0_CAN_MAP_READ = sys::ZX_IOB_ACCESS_EP0_CAN_MAP_READ;
const EP0_CAN_MAP_WRITE = sys::ZX_IOB_ACCESS_EP0_CAN_MAP_WRITE;
const EP0_CAN_MEDIATED_READ = sys::ZX_IOB_ACCESS_EP0_CAN_MEDIATED_READ;
const EP0_CAN_MEDIATED_WRITE = sys::ZX_IOB_ACCESS_EP0_CAN_MEDIATED_WRITE;
const EP1_CAN_MAP_READ = sys::ZX_IOB_ACCESS_EP1_CAN_MAP_READ;
const EP1_CAN_MAP_WRITE = sys::ZX_IOB_ACCESS_EP1_CAN_MAP_WRITE;
const EP1_CAN_MEDIATED_READ = sys::ZX_IOB_ACCESS_EP1_CAN_MEDIATED_READ;
const EP1_CAN_MEDIATED_WRITE = sys::ZX_IOB_ACCESS_EP1_CAN_MEDIATED_WRITE;
}
}
#[derive(Clone, Copy)]
pub enum IobDiscipline {
None,
MediatedWriteRingBuffer { tag: u64 },
}
impl IobDiscipline {
fn to_raw(&self) -> sys::zx_iob_discipline_t {
match self {
IobDiscipline::None => sys::zx_iob_discipline_t {
r#type: sys::ZX_IOB_DISCIPLINE_TYPE_NONE,
extension: sys::zx_iob_discipline_extension_t {
reserved: [sys::PadByte::default(); 64],
},
},
IobDiscipline::MediatedWriteRingBuffer { tag } => sys::zx_iob_discipline_t {
r#type: sys::ZX_IOB_DISCIPLINE_TYPE_MEDIATED_WRITE_RING_BUFFER,
extension: sys::zx_iob_discipline_extension_t {
ring_buffer: sys::zx_iob_discipline_mediated_write_ring_buffer_t {
tag: *tag,
padding: [sys::PadByte::default(); 56],
},
},
},
}
}
}
#[derive(Default)]
pub struct IobWriteOptions;
impl Iob {
/// Creates an IOBuffer.
///
/// Wraps the [zx_iob_create](https://fuchsia.dev/reference/syscalls/iob_create) syscall.
pub fn create(_options: IobOptions, regions: &[IobRegion<'_>]) -> Result<(Iob, Iob), Status> {
let raw_regions: Vec<_> = regions
.iter()
.map(|r| {
let (r#type, extension) = r.region_type.to_raw();
sys::zx_iob_region_t {
r#type,
access: r.access.bits(),
size: match &r.region_type {
IobRegionType::Private { size, .. } => *size,
IobRegionType::Shared { .. } => 0,
},
discipline: r.discipline.to_raw(),
extension,
}
})
.collect();
let mut handle1 = 0;
let mut handle2 = 0;
let status = unsafe {
sys::zx_iob_create(
0,
raw_regions.as_ptr() as *const u8,
raw_regions.len(),
&mut handle1,
&mut handle2,
)
};
ok(status)?;
unsafe { Ok((Iob::from(Handle::from_raw(handle1)), Iob::from(Handle::from_raw(handle2)))) }
}
/// Performs a mediated write to an IOBuffer region.
///
/// Wraps the [zx_iob_writev](https://fuchsia.dev/reference/syscalls/iob_writev) syscall.
pub fn write(
&self,
options: IobWriteOptions,
region_index: u32,
data: &[u8],
) -> Result<(), Status> {
self.writev(options, region_index, &[IobIoSlice::new(data)])
}
/// Performs a mediated write (with vectors) to an IOBuffer region.
///
/// Wraps the
/// [`zx_stream_writev`](https://fuchsia.dev/fuchsia-src/reference/syscalls/stream_writev)
/// syscall.
pub fn writev(
&self,
_options: IobWriteOptions,
region_index: u32,
iovecs: &[IobIoSlice<'_>],
) -> Result<(), Status> {
let status = unsafe {
sys::zx_iob_writev(
self.raw_handle(),
0,
region_index,
iovecs.as_ptr().cast::<sys::zx_iovec_t>(),
iovecs.len(),
)
};
ok(status)?;
Ok(())
}
}
pub(crate) mod vdso_next {
use super::*;
use std::sync::OnceLock;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct IobSharedRegion(Handle);
impl_handle_based!(IobSharedRegion);
/// Options for `IobSharedRegion::create`.
#[derive(Default)]
pub struct IobSharedRegionOptions;
/// Options for `IobRegionType` which is used by `Iob::create`.
#[derive(Clone, Copy, Default)]
pub struct IobRegionSharedOptions;
impl IobSharedRegion {
/// Creates a shared region that can be used with multiple IOBuffer objects.
///
/// Wraps the `zx_iob_create_shared_region` syscall.
pub fn create(_options: IobSharedRegionOptions, size: u64) -> Result<Self, Status> {
// We have to go through this dance because we now have shared libraries (e.g. the VFS
// library) which means we encounter a relocation failure if used with the stable vdso.
// Weak link attributes are experimental, so we search for the symbol dynamically.
static ZX_IOB_CREATE_SHARED_REGION_FN: OnceLock<
unsafe extern "C" fn(u64, u64, *mut sys::zx_handle_t) -> sys::zx_status_t,
> = OnceLock::new();
let zx_iob_create_shared_region = ZX_IOB_CREATE_SHARED_REGION_FN.get_or_init(|| {
// SAFETY: These arguments should be safe to pass to dlsym.
let symbol = unsafe {
libc::dlsym(libc::RTLD_DEFAULT, c"zx_iob_create_shared_region".as_ptr())
};
assert!(!symbol.is_null(), "zx_iob_create_shared_region requires vdso next");
// SAFETY: The above signature should be correct for the symbol we found.
unsafe { std::mem::transmute(symbol) }
});
let mut handle = 0;
let status = unsafe { zx_iob_create_shared_region(0, size, &mut handle) };
ok(status)?;
Ok(Self::from(unsafe { Handle::from_raw(handle) }))
}
}
#[cfg(all(test, vdso_next))]
mod tests {
use crate::handle::AsHandleRef;
use crate::{
sys, system_get_page_size, Iob, IobDiscipline, IobRegion, IobRegionType,
IobSharedRegion, Unowned, Vmar, VmarFlags,
};
use std::sync::atomic::{AtomicU64, Ordering};
#[test]
fn test() {
let region_size = 2 * system_get_page_size() as usize;
let shared_region =
IobSharedRegion::create(region_size as u64, Default::default()).unwrap();
let (ep0, ep1) = Iob::create(
Default::default(),
&[IobRegion {
region_type: IobRegionType::Shared(Default::default(), &shared_region),
access: sys::ZX_IOB_ACCESS_EP0_CAN_MAP_READ
| sys::ZX_IOB_ACCESS_EP0_CAN_MAP_WRITE
| sys::ZX_IOB_ACCESS_EP1_CAN_MEDIATED_WRITE,
discipline: IobDiscipline::MediatedWriteRingBuffer,
}],
)
.unwrap();
ep1.write(Default::default(), 0, b"hello").unwrap();
let vmar_handle = unsafe { fuchsia_runtime::zx_vmar_root_self() };
let vmar = unsafe { Unowned::<Vmar>::from_raw_handle(vmar_handle) };
let addr = vmar
.map_iob(VmarFlags::PERM_READ | VmarFlags::PERM_WRITE, 0, &ep0, 0, 0, region_size)
.unwrap();
#[repr(C)]
struct Header {
head: AtomicU64,
tail: AtomicU64,
}
let header = unsafe { &*(addr as *const Header) };
let head = header.head.load(Ordering::Acquire);
assert_eq!(head, 24);
let tail = header.tail.load(Ordering::Relaxed);
assert_eq!(tail, 0);
struct Message {
tag: u64,
length: u64,
data: [u8; 8],
}
let message =
unsafe { &(*((addr + system_get_page_size() as usize) as *const Message)) };
assert_eq!(message.tag, ep1.get_koid().unwrap().raw_koid());
assert_eq!(message.length, 5);
assert_eq!(&message.data[..5], b"hello");
}
}
}
#[cfg(test)]
mod tests {
use super::{Iob, IobAccess, IobDiscipline, IobRegion, IobRegionType};
use crate::{Unowned, Vmar, VmarFlags};
use std::sync::atomic::{AtomicU64, Ordering};
#[test]
fn test_create_iob() {
let region_size = zx::system_get_page_size() as usize * 8;
let (ep0, ep1) = Iob::create(
Default::default(),
&[IobRegion {
region_type: IobRegionType::Private {
size: region_size as u64,
options: Default::default(),
},
access: IobAccess::EP0_CAN_MAP_READ
| IobAccess::EP0_CAN_MAP_WRITE
| IobAccess::EP1_CAN_MAP_READ,
discipline: IobDiscipline::None,
}],
)
.expect("create failed");
// We can't use fuchsia_runtime other than to get the handle, because its Vmar type is
// considered distinct from crate::Vmar.
let root_vmar =
unsafe { Unowned::<Vmar>::from_raw_handle(fuchsia_runtime::zx_vmar_root_self()) };
let write_addr = root_vmar
.map_iob(VmarFlags::PERM_READ | VmarFlags::PERM_WRITE, 0, &ep0, 0, 0, region_size)
.expect("map_iob failed");
let read_addr = root_vmar
.map_iob(VmarFlags::PERM_READ, 0, &ep1, 0, 0, region_size)
.expect("map_iob failed");
const VALUE: u64 = 0x123456789abcdef;
unsafe { &*(write_addr as *const AtomicU64) }.store(VALUE, Ordering::Relaxed);
assert_eq!(unsafe { &*(read_addr as *const AtomicU64) }.load(Ordering::Relaxed), VALUE);
unsafe {
root_vmar.unmap(write_addr, region_size).expect("unmap failed");
root_vmar.unmap(read_addr, region_size).expect("unmap failed");
}
}
}