diff --git a/Cargo.lock b/Cargo.lock index 3382080c..870d0cd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,12 +163,53 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "font8x8" version = "0.2.7" @@ -207,6 +248,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.9.2" @@ -217,6 +264,17 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "inherit-methods-macro" +version = "0.1.0" +source = "git+https://github.com/occlum/occlum?branch=1.0.0-preview#927512bc20b1221060bcfc505e71adae5f42b1ff" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "int-to-c-enum" version = "0.1.0" @@ -306,6 +364,7 @@ dependencies = [ "bitflags", "buddy_system_allocator", "cfg-if", + "inherit-methods-macro", "intrusive-collections", "lazy_static", "limine", @@ -359,6 +418,16 @@ dependencies = [ "spin 0.9.4", ] +[[package]] +name = "jinux-rights" +version = "0.1.0" +dependencies = [ + "bitflags", + "jinux-rights-proc", + "typeflags", + "typeflags-util", +] + [[package]] name = "jinux-rights-proc" version = "0.1.0" @@ -382,6 +451,7 @@ dependencies = [ "jinux-block", "jinux-frame", "jinux-input", + "jinux-rights", "jinux-rights-proc", "jinux-time", "jinux-util", @@ -415,7 +485,10 @@ name = "jinux-util" version = "0.1.0" dependencies = [ "jinux-frame", + "jinux-rights", + "jinux-rights-proc", "pod", + "typeflags-util", ] [[package]] @@ -646,6 +719,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" diff --git a/services/libs/jinux-util/Cargo.toml b/services/libs/jinux-util/Cargo.toml index 605ebecf..3365d29c 100644 --- a/services/libs/jinux-util/Cargo.toml +++ b/services/libs/jinux-util/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -jinux-frame = {path = "../../../framework/jinux-frame"} +jinux-frame = { path = "../../../framework/jinux-frame" } pod = { git = "https://github.com/jinzhao-dev/pod", rev = "7fa2ed2" } +typeflags-util = { path = "../typeflags-util" } +jinux-rights-proc = { path = "../jinux-rights-proc" } +jinux-rights = { path = "../jinux-rights" } [features] - diff --git a/services/libs/jinux-util/src/dup.rs b/services/libs/jinux-util/src/dup.rs new file mode 100644 index 00000000..e4ece7d3 --- /dev/null +++ b/services/libs/jinux-util/src/dup.rs @@ -0,0 +1,13 @@ +/// This trait is a _fallible_ version of `Clone`. +/// +/// If any object of a type `T` is duplicable, then `T` should implement +/// `Clone`. However, if whether an object is duplicable must be determined +/// on a per-object basis at runtime, then `T` should implement `Dup` as +/// the `dup` method is allowed to return an error. +/// +/// As a best practice, the `Clone` and `Dup` traits should be implemented +/// _exclusively_ to one another. In other words, a type should not implement +/// both traits. +pub trait Dup: Sized { + fn dup(&self) -> jinux_frame::Result; +} diff --git a/services/libs/jinux-util/src/lib.rs b/services/libs/jinux-util/src/lib.rs index 7e5eac35..91498426 100644 --- a/services/libs/jinux-util/src/lib.rs +++ b/services/libs/jinux-util/src/lib.rs @@ -4,6 +4,8 @@ extern crate alloc; +pub mod dup; pub mod frame_ptr; +pub mod safe_ptr; pub mod slot_vec; pub mod union_read_ptr; diff --git a/services/libs/jinux-util/src/safe_ptr.rs b/services/libs/jinux-util/src/safe_ptr.rs new file mode 100644 index 00000000..d77e50a7 --- /dev/null +++ b/services/libs/jinux-util/src/safe_ptr.rs @@ -0,0 +1,372 @@ +use core::marker::PhantomData; +use jinux_frame::vm::VmIo; +use jinux_frame::Result; +use jinux_rights::{Dup, Exec, Full, Read, Signal, TRightSet, TRights, Write}; +use jinux_rights_proc::require; +use pod::Pod; + +/// Safe pointers. +/// +/// # Overview +/// +/// Safe pointers allows using pointers to access memory without +/// unsafe code, which is a key enabler for writing device drivers in safe Rust. +/// +/// To ensure its soundness, safe pointers (`SafePtr`) have to be +/// more restricted than raw pointers (`*const T` or `*mut T`). +/// More specifically, there are three major restrictions. +/// +/// 1. A safe pointer can only refer to a value of a POD type `T: Pod`, +/// while raw pointers can do to a value of any type `T`. +/// 2. A safe pointer can only refer to an address within a virtual memory object +/// of type `M: VmIo` (e.g., VMAR and VMO), while raw pointers can do to +/// an address within any virtual memory space. +/// 3. A safe pointer only allows one to copy values to/from the target address, +/// while a raw pointer allows one to borrow an immutable or mutable reference +/// to the target address. +/// +/// The expressiveness of safe pointers, although being less than that of +/// raw pointers, is sufficient for our purpose of writing an OS kernel in safe +/// Rust. +/// +/// In addition, safe pointers `SafePtr` are associated with access +/// rights, which are encoded statically with type `R: TRights`. +/// +/// # Examples +/// +/// ## Constructing a safe pointer +/// +/// An instance of `SafePtr` can be created with a VM object and an address +/// within the VM object. +/// +/// ``` +/// let u32_ptr: SafePtr, _> = { +/// let vm_obj = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); +/// let addr = 16; +/// SafePtr::new(vm_obj, addr) +/// }; +/// ``` +/// +/// The generic parameter `M` of `SafePtr<_, M, _>` must implement the `VmIo` +/// trait. The most important `VmIo` types are `Vmar`, `Vmo`, `Mmio`, and +/// `VmFrame`. The blanket implementations of `VmIo` also include pointer-like +/// types that refer to a `VmIo` type. Some examples are `&Vmo`, `Box`, +/// and `Arc`. +/// +/// The safe pointer itself does not and cannot guarantee that its address is valid. +/// This is because different VM objects may interpret addresses differently +/// and each VM object can have different restrictions for valid addresses. +/// So the detection of invalid addresses is delayed to the time when the +/// pointers are actually read from or written to. +/// +/// Initially, a newly-created safe pointer has all access rights. +/// +/// ## Reading and writing a safe pointer +/// +/// The value pointed to by a safe pointer can be read or written with the +/// `read` or `write` method. Both methods may return errors. The possible reasons +/// of error are determined by the underlying VM objects. +/// +/// ``` +/// u32_ptr.write(1234).unwrap(); +/// assert!(u32_ptr.read().unwrap() == 1234); +/// ``` +/// +/// ## Manipulating a safe pointer +/// +/// The address of a safe pointer can be obtained by the `addr` method. +/// The address can be updated by assigning a new value with the `set_addr` method +/// or updated incrementally through methods like `add`, `offset`, `byte_addr`, +/// `byte_offset`. +/// +/// The VM object of a safe pointer can also be obtained or updated through the +/// `vm` and `set_vm` methods. A new safe pointer that is backed by the same +/// VM object of an existing safe pointer can be obtained through the `borrow_vm` +/// method. +/// +/// As an example, the code below shows how the `add` and `borrow_vm` methods +/// can be used together to to iterate all values pointed to by an array pointer. +/// +/// ``` +/// fn collect_values(array_ptr: &SafePtr, array_len: usize) -> Vec { +/// let mut curr_ptr: SafePtr = array_ptr.borrow_vm(); +/// (0..array_len) +/// .iter() +/// .map(|_| { +/// let val = curr_ptr.read().unwrap(); +/// curr_ptr.add(1); +/// val +/// }) +/// .collect() +/// } +/// ``` +/// +/// The data type of a safe pointer can be converted with the `cast` method. +/// +/// ```rust +/// let u8_ptr: SafePtr = u32_ptr.cast(); +/// ``` +/// +/// ## Reading and writing the fields of a struct +/// +/// Given a safe pointer that points to a struct (say, `Foo`), one can read +/// the value of its field as follows. +/// +/// ``` +/// pub struct Foo { +/// first: u64, +/// second: u32, +/// } +/// +/// fn read_second_field(ptr: &SafePtr) -> u32 { +/// let field_ptr = ptr +/// .borrow_vm() +/// .byte_add(offset_of!(Foo, second) as usize) +/// .cast::(); +/// field_ptr.read().unwrap() +/// } +/// ``` +/// +/// But this coding pattern is too tedius for such a common task. +/// To make the life of users easier, we provide a convinient macro named +/// `field_ptr`, which can be used to obtain the safe pointer of a field from +/// that of its containing struct. +/// +/// ``` +/// fn read_second_field(ptr: &SafePtr) -> u32 { +/// let field_ptr = field_ptr!(ptr, Foo, second); +/// field_ptr.read().unwrap() +/// } +/// ``` +/// +/// # Access rights +/// +/// A safe pointer may have a combination of three access rights: +/// Read, Write, and Dup. +pub struct SafePtr { + addr: usize, + vm_obj: M, + rights: R, + phantom: PhantomData, +} + +impl SafePtr { + /// Create a new instance. + /// + /// # Access rights + /// + /// The default access rights of a new instance are `Read`, `Write`, and + /// `Dup`. + pub fn new(vm_obj: M, addr: usize) -> Self { + Self { + vm_obj, + addr, + rights: TRightSet(::new()), + phantom: PhantomData, + } + } +} + +impl SafePtr { + // =============== Read and write methods ============== + + /// Read the value from the pointer. + /// + /// # Access rights + /// + /// This method requires the Read right. + #[require(R > Read)] + pub fn read(&self) -> Result { + self.vm_obj.read_val(self.addr) + } + + /// Read a slice of values from the pointer. + /// + /// # Access rights + /// + /// This method requires the Read right. + #[require(R > Read)] + pub fn read_slice(&self, slice: &mut [T]) -> Result<()> { + self.vm_obj.read_slice(0, slice) + } + + /// Overwrite the value at the pointer. + /// + /// # Access rights + /// + /// This method requires the Write right. + #[require(R > Write)] + pub fn write(&self, val: &T) -> Result<()> { + self.vm_obj.write_val(self.addr, val) + } + + /// Overwrite a slice of values at the pointer. + /// + /// # Access rights + /// + /// This method requires the Write right. + #[require(R > Write)] + pub fn write_slice(&self, slice: &[T]) -> Result<()> { + self.vm_obj.write_slice(0, slice) + } + + // =============== Address-related methods ============== + + pub const fn addr(&self) -> usize { + self.addr + } + + pub fn set_addr(&mut self, addr: usize) { + self.addr = addr; + } + + pub const fn is_aligned(&self) -> bool { + self.addr % core::mem::align_of::() == 0 + } + + /// Increase the address in units of bytes occupied by the generic T. + pub fn add(&mut self, count: usize) { + let offset = count * core::mem::size_of::(); + self.addr += offset; + } + + /// Increase or decrease the address in units of bytes occupied by the generic T. + pub fn offset(&mut self, count: isize) { + let offset = count * core::mem::size_of::() as isize; + if count >= 0 { + self.addr += offset as usize; + } else { + self.addr -= (-offset) as usize; + } + } + + /// Increase the address in units of bytes. + pub fn byte_add(&mut self, bytes: usize) { + self.addr += bytes; + } + + /// Increase or decrease the address in units of bytes. + pub fn byte_offset(&mut self, bytes: isize) { + if bytes >= 0 { + self.addr += bytes as usize; + } else { + self.addr -= (-bytes) as usize; + } + } + + // =============== VM object-related methods ============== + + pub const fn vm(&self) -> &M { + &self.vm_obj + } + + pub fn set_vm(&mut self, vm_obj: M) { + self.vm_obj = vm_obj; + } + + /// Construct a new SafePtr which will point to the same address + pub const fn borrow_vm(&self) -> SafePtr { + let SafePtr { + addr, + vm_obj, + rights, + .. + } = self; + SafePtr { + addr: *addr, + vm_obj, + rights: *rights, + phantom: PhantomData, + } + } + + // =============== Type conversion methods ============== + + /// Cast the accessed structure into a new one, which is usually used when accessing a field in a structure. + pub fn cast(self) -> SafePtr { + let SafePtr { + addr, + vm_obj, + rights, + .. + } = self; + SafePtr { + addr, + vm_obj, + rights, + phantom: PhantomData, + } + } + + /// Construct a new SafePtr and restrict the rights of it. + /// + /// # Access rights + /// + /// This method requires the target rights to be a subset of the current rights. + #[require(R > R1)] + pub fn restrict(self) -> SafePtr { + let SafePtr { addr, vm_obj, .. } = self; + SafePtr { + addr, + vm_obj, + rights: R1::new(), + phantom: PhantomData, + } + } +} + +#[require(R > Dup)] +impl Clone for SafePtr { + fn clone(&self) -> Self { + Self { + addr: self.addr, + vm_obj: self.vm_obj.clone(), + rights: self.rights, + phantom: PhantomData, + } + } +} + +#[require(R > Dup)] +impl crate::dup::Dup for SafePtr { + fn dup(&self) -> Result { + let duplicated = Self { + addr: self.addr, + vm_obj: self.vm_obj.dup()?, + rights: self.rights, + phantom: PhantomData, + }; + Ok(duplicated) + } +} + +/// Create a safe pointer for the field of a struct. +#[macro_export] +macro_rules! field_ptr { + ($ptr:expr, $type:ty, $($field:tt)+) => {{ + use jinux_frame::offset_of; + use jinux_frame::vm::VmIo; + // import more... + + #[inline] + fn new_field_ptr( + container_ptr: &SafePtr, + field_offset: *const U + ) -> SafePtr + where + T: Pod, + M: VmIo, + R: TRights, + U: Pod, + { + container_ptr + .borrow_vm() + .byte_add(offset as usize) + .cast() + } + + let ptr = $ptr; + let field_offset = offset_of!(ty, $($field)*); + new_field_ptr(ptr, field_offset) + }} +}