| // 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"); |
| } |
| } |
| } |