diff --git a/Cargo.lock b/Cargo.lock index 67142832d..69d95b967 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,6 +398,12 @@ dependencies = [ "toml", ] +[[package]] +name = "const-assert" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8298db53081b3a951cadb6e0f4ebbe36def7bcb591a34676708d0d7ac87dd86" + [[package]] name = "controlled" version = "0.1.0" @@ -1070,6 +1076,7 @@ dependencies = [ "bitvec", "buddy_system_allocator", "cfg-if", + "const-assert", "gimli 0.28.0", "iced-x86", "id-alloc", diff --git a/ostd/Cargo.toml b/ostd/Cargo.toml index b788274f1..b5e623f85 100644 --- a/ostd/Cargo.toml +++ b/ostd/Cargo.toml @@ -22,6 +22,7 @@ buddy_system_allocator = "0.9.0" bitflags = "1.3" bitvec = { version = "1.0", default-features = false, features = ["alloc"] } cfg-if = "1.0" +const-assert = "1.0" gimli = { version = "0.28", default-features = false, features = ["read-core"] } id-alloc = { path = "libs/id-alloc", version = "0.1.0" } inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-macro", rev = "98f7e3e", version = "0.1.0" } diff --git a/ostd/src/mm/io.rs b/ostd/src/mm/io.rs index 78fd0382b..d70fad5a6 100644 --- a/ostd/src/mm/io.rs +++ b/ostd/src/mm/io.rs @@ -5,6 +5,7 @@ use core::marker::PhantomData; use align_ext::AlignExt; +use const_assert::{Assert, IsTrue}; use inherit_methods_macro::inherit_methods; use crate::{ @@ -155,7 +156,28 @@ pub trait VmIo: Send + Sync { } } -macro_rules! impl_vmio_pointer { +/// A trait that enables reading/writing data from/to a VM object using one non-tearing memory +/// load/store. +/// +/// See also [`VmIo`], which enables reading/writing data from/to a VM object without the guarantee +/// of using one non-tearing memory load/store. +pub trait VmIoOnce { + /// Reads a value of the `PodOnce` type at the specified offset using one non-tearing memory + /// load. + /// + /// Except that the offset is specified explicitly, the semantics of this method is the same as + /// [`VmReader::read_once`]. + fn read_once(&self, offset: usize) -> Result; + + /// Writes a value of the `PodOnce` type at the specified offset using one non-tearing memory + /// store. + /// + /// Except that the offset is specified explicitly, the semantics of this method is the same as + /// [`VmWriter::write_once`]. + fn write_once(&self, offset: usize, new_val: &T) -> Result<()>; +} + +macro_rules! impl_vm_io_pointer { ($typ:ty,$from:tt) => { #[inherit_methods(from = $from)] impl VmIo for $typ { @@ -169,10 +191,25 @@ macro_rules! impl_vmio_pointer { }; } -impl_vmio_pointer!(&T, "(**self)"); -impl_vmio_pointer!(&mut T, "(**self)"); -impl_vmio_pointer!(Box, "(**self)"); -impl_vmio_pointer!(Arc, "(**self)"); +impl_vm_io_pointer!(&T, "(**self)"); +impl_vm_io_pointer!(&mut T, "(**self)"); +impl_vm_io_pointer!(Box, "(**self)"); +impl_vm_io_pointer!(Arc, "(**self)"); + +macro_rules! impl_vm_io_once_pointer { + ($typ:ty,$from:tt) => { + #[inherit_methods(from = $from)] + impl VmIoOnce for $typ { + fn read_once(&self, offset: usize) -> Result; + fn write_once(&self, offset: usize, new_val: &F) -> Result<()>; + } + }; +} + +impl_vm_io_once_pointer!(&T, "(**self)"); +impl_vm_io_once_pointer!(&mut T, "(**self)"); +impl_vm_io_once_pointer!(Box, "(**self)"); +impl_vm_io_once_pointer!(Arc, "(**self)"); /// A marker structure used for [`VmReader`] and [`VmWriter`], /// representing their operated memory scope is in user space. @@ -391,6 +428,34 @@ impl<'a> VmReader<'a, KernelSpace> { self.read(&mut writer); Ok(val) } + + /// Reads a value of the `PodOnce` type using one non-tearing memory load. + /// + /// If the length of the `PodOnce` type exceeds `self.remain()`, this method will return `Err`. + /// + /// This method will not compile if the `Pod` type is too large for the current architecture + /// and the operation must be tear into multiple memory loads. + /// + /// # Panics + /// + /// This method will panic if the current position of the reader does not meet the alignment + /// requirements of type `T`. + pub fn read_once(&mut self) -> Result { + if self.remain() < core::mem::size_of::() { + return Err(Error::InvalidArgs); + } + + let cursor = self.cursor.cast::(); + assert!(cursor.is_aligned()); + + // SAFETY: We have checked that the number of bytes remaining is at least the size of `T` + // and that the cursor is properly aligned with respect to the type `T`. All other safety + // requirements are the same as for `Self::read`. + let val = unsafe { cursor.read_volatile() }; + self.cursor = unsafe { self.cursor.add(core::mem::size_of::()) }; + + Ok(val) + } } impl<'a> VmReader<'a, UserSpace> { @@ -553,6 +618,31 @@ impl<'a> VmWriter<'a, KernelSpace> { Ok(()) } + /// Writes a value of the `PodOnce` type using one non-tearing memory store. + /// + /// If the length of the `PodOnce` type exceeds `self.remain()`, this method will return `Err`. + /// + /// # Panics + /// + /// This method will panic if the current position of the writer does not meet the alignment + /// requirements of type `T`. + pub fn write_once(&mut self, new_val: &T) -> Result<()> { + if self.avail() < core::mem::size_of::() { + return Err(Error::InvalidArgs); + } + + let cursor = self.cursor.cast::(); + assert!(cursor.is_aligned()); + + // SAFETY: We have checked that the number of bytes remaining is at least the size of `T` + // and that the cursor is properly aligned with respect to the type `T`. All other safety + // requirements are the same as for `Self::writer`. + unsafe { cursor.cast::().write_volatile(*new_val) }; + self.cursor = unsafe { self.cursor.add(core::mem::size_of::()) }; + + Ok(()) + } + /// Fills the available space by repeating `value`. /// /// Returns the number of values written. @@ -672,3 +762,23 @@ impl<'a> From<&'a mut [u8]> for VmWriter<'a> { unsafe { Self::from_kernel_space(slice.as_mut_ptr(), slice.len()) } } } + +/// A marker trait for POD types that can be read or written with one instruction. +/// +/// We currently rely on this trait to ensure that the memory operation created by +/// `ptr::read_volatile` and `ptr::write_volatile` doesn't tear. However, the Rust documentation +/// makes no such guarantee, and even the wording in the LLVM LangRef is ambiguous. +/// +/// At this point, we can only _hope_ that this doesn't break in future versions of the Rust or +/// LLVM compilers. However, this is unlikely to happen in practice, since the Linux kernel also +/// uses "volatile" semantics to implement `READ_ONCE`/`WRITE_ONCE`. +pub trait PodOnce: Pod {} + +impl PodOnce for T where Assert<{ is_pod_once::() }>: IsTrue {} + +#[cfg(target_arch = "x86_64")] +const fn is_pod_once() -> bool { + let size = size_of::(); + + size == 1 || size == 2 || size == 4 || size == 8 +} diff --git a/ostd/src/mm/mod.rs b/ostd/src/mm/mod.rs index 3496a49db..d3aa68326 100644 --- a/ostd/src/mm/mod.rs +++ b/ostd/src/mm/mod.rs @@ -28,7 +28,7 @@ use spin::Once; pub use self::{ dma::{Daddr, DmaCoherent, DmaDirection, DmaStream, DmaStreamSlice, HasDaddr}, frame::{options::FrameAllocOptions, Frame, Segment}, - io::{KernelSpace, UserSpace, VmIo, VmReader, VmWriter}, + io::{KernelSpace, PodOnce, UserSpace, VmIo, VmIoOnce, VmReader, VmWriter}, page_prop::{CachePolicy, PageFlags, PageProperty}, vm_space::VmSpace, };