diff --git a/src/kxos-std/src/rights.rs b/src/kxos-std/src/rights.rs new file mode 100644 index 000000000..4882211cf --- /dev/null +++ b/src/kxos-std/src/rights.rs @@ -0,0 +1,55 @@ +use typeflags::typeflags; +use bitflags::bitflags; + +bitflags! { + /// Value-based access rights. + /// + /// These access rights are provided to cover a wide range of use cases. + /// The access rights' semantics and how they would restrict the behaviors + /// of a capability are decided by the capability's designer. + /// Here, we give some sensible semantics for each access right. + pub struct Rights: u32 { + /// Allows duplicating a capability. + const DUP: u32 = 1 << 0; + /// Allows reading data from a data source (files, VM objects, etc.) or + /// creating readable memory mappings. + const READ: u32 = 1 << 1; + /// Allows writing data to a data sink (files, VM objects, etc.) or + /// creating writable memory mappings. + const WRITE: u32 = 1 << 2; + /// Allows creating executable memory mappings. + const EXEC: u32 = 1 << 3; + /// Allows sending notifications or signals. + const SIGNAL: u32 = 1 << 7; + } +} + +typeflags! { + /// Type-based access rights. + /// + /// Similar to value-based access rights (`Rights`), but represented in + /// types. + pub trait TRights: u32 { + /// Allows duplicating a capability. + struct Dup: u32 = Rights::DUP; + /// Allows reading data from a data source (files, VM objects, etc.) or + /// creating readable memory mappings. + struct Read: u32 = Rights::READ; + /// Allows writing data to a data sink (files, VM objects, etc.) or + /// creating writable memory mappings. + struct Write: u32 = Rights::WRITE; + /// Allows creating executable memory mappings. + struct Exec: u32 = Rights::EXEC; + /// Allows sending notifications or signals. + struct Signal: u32 = Rights::SIGNAL; + } +} + +/// The full set of access rights. +pub type Full = TRights![ + Dup, + Read, + Write, + Exec, + Signal, +]; diff --git a/src/kxos-std/src/vm/mod.rs b/src/kxos-std/src/vm/mod.rs new file mode 100644 index 000000000..e89392f4e --- /dev/null +++ b/src/kxos-std/src/vm/mod.rs @@ -0,0 +1,18 @@ +//! Virtual memory (VM). +//! +//! There are two primary VM abstractions: +//! * Virtual Memory Address Regions (VMARs) a type of capability that manages +/// user address spaces. +//! * Virtual Memory Objects (VMOs) are are a type of capability that +//! represents a set of memory pages. +//! +//! The concepts of VMARs and VMOs are originally introduced by +//! [Zircon](https://fuchsia.dev/fuchsia-src/reference/kernel_objects/vm_object). +//! As capabilities, the two abstractions are aligned with our goal +//! of everything-is-a-capability, although their specifications and +//! implementations in C/C++ cannot apply directly to KxOS. +//! In KxOS, VMARs and VMOs, as well as other capabilities, are implemented +//! as zero-cost capabilities. + +mod vmar; +mod vmo; \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmar/dyn_cap.rs b/src/kxos-std/src/vm/vmar/dyn_cap.rs new file mode 100644 index 000000000..4c31f91ce --- /dev/null +++ b/src/kxos-std/src/vm/vmar/dyn_cap.rs @@ -0,0 +1,130 @@ +impl Vmar { + /// Creates a root VMAR. + pub fn new() -> Result { + let inner = Arc::new(Vmar_::new()); + let rights = Rights::all(); + let new_self = Self(inner, rights); + Ok(new_self) + } + + /// Maps the given VMO into the VMAR through a set of VMAR mapping options. + /// + /// # Example + /// + /// ``` + /// use kxos_std::prelude::*; + /// use kxos_std::vm::{PAGE_SIZE, Vmar, VmoOptions}; + /// + /// let vmar = Vmar::new().unwrap(); + /// let vmo = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let target_vaddr = 0x1234000; + /// let real_vaddr = vmar + /// // Map the VMO to create a read-only mapping + /// .new_map(vmo, VmPerms::READ) + /// // Provide an optional offset for the mapping inside the VMAR + /// .offset(target_vaddr) + /// .build() + /// .unwrap(); + /// assert!(real_vaddr == target_vaddr); + /// ``` + /// + /// For more details on the available options, see `VmarMapOptions`. + /// + /// # Access rights + /// + /// This method requires the following access rights: + /// 1. The VMAR contains the rights corresponding to the memory permissions of + /// the mapping. For example, if `perms` contains `VmPerm::WRITE`, + /// then the VMAR must have the Write right. + /// 2. Similarly, the VMO contains the rights corresponding to the memory + /// permissions of the mapping. + /// + /// Memory permissions may be changed through the `protect` method, + /// which ensures that any updated memory permissions do not go beyond + /// the access rights of the underlying VMOs. + pub fn new_map(&self, vmo: Vmo, perms: VmPerms) -> VmarMapOptions<'_, Rights> { + let dup_self = self.dup()?; + VmarMapOptions::new(dup_self, vmo, perms) + } + + /// Creates a new child VMAR through a set of VMAR child options. + /// + /// # Example + /// + /// ``` + /// let parent = Vmar::new().unwrap(); + /// let child_size = 10 * PAGE_SIZE; + /// let child = parent.new_child(child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmarChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMAR child will be of the same capability class and + /// access rights as the parent. + pub fn new_child(&self, size: usize) -> VmarChildOptions<'a, Rights> { + let dup_self = self.dup()?; + VmarChildOptions::new(dup_self, size) + } + + /// Change the permissions of the memory mappings in the specified range. + /// + /// The range's start and end addresses must be page-aligned. + /// Also, the range must be completely mapped. + /// + /// # Access rights + /// + /// The VMAR must have the rights corresponding to the specified memory + /// permissions. + /// + /// The mappings overlapped with the specified range must be backed by + /// VMOs whose rights contain the rights corresponding to the specified + /// memory permissions. + pub fn protect(&self, perms: VmPerms, range: Range) -> Result<()> { + self.check_rights(perms.into())?; + self.0.protect(perms, range) + } + + /// Destroy a VMAR, including all its mappings and children VMARs. + /// + /// After being destroyed, the VMAR becomes useless and returns errors + /// for most of its methods. + pub fn destroy_all(&self) -> Result<()> { + self.0.destroy_all() + } + + /// Destroy all mappings and children VMARs that fall within the specified + /// range in bytes. + /// + /// The range's start and end addresses must be page-aligned. + /// + /// Mappings may fall partially within the range; only the overlapped + /// portions of the mappings are unmapped. + /// As for children VMARs, they must be fully within the range. + /// All children VMARs that fall within the range get their `destroy` methods + /// called. + pub fn destroy(&self, range: &Range) -> Result<()> { + self.0.destroy(range) + } + + /// Returns the access rights. + pub fn rights(&self) -> Rights { + self.1 + } +} + +impl VmIo for Vmar { + fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + self.check_rights!(Rights::READ)?; + self.0.read(offset, buf) + } + + fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { + self.check_rights!(Rights::WRITE)?; + self.0.write(offset, buf) + } +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmar/mod.rs b/src/kxos-std/src/vm/vmar/mod.rs new file mode 100644 index 000000000..13d9b3c8a --- /dev/null +++ b/src/kxos-std/src/vm/vmar/mod.rs @@ -0,0 +1,110 @@ +//! Virtual Memory Address Regions (VMARs). + +mod static_cap; +mod dyn_cap; +mod options; + +/// Virtual Memory Address Regions (VMARs) are a type of capability that manages +/// user address spaces. +/// +/// # Capabilities +/// +/// As a capability, each VMAR is associated with a set of access rights, +/// whose semantics are explained below. +/// +/// The semantics of each access rights for VMARs are described below: +/// * The Dup right allows duplicating a VMAR and creating children out of +/// a VMAR. +/// * The Read, Write, Exec rights allow creating memory mappings with +/// readable, writable, and executable access permissions, respectively. +/// * The Read and Write rights allow the VMAR to be read from and written to +/// directly. +/// +/// VMARs are implemented with two flavors of capabilities: +/// the dynamic one (`Vmar`) and the static one (`Vmar). +/// +/// # Implementation +/// +/// `Vmar` provides high-level APIs for address space management by wrapping +/// around its low-level counterpart `kx_frame::vm::VmFrames`. +/// Compared with `VmFrames`, +/// `Vmar` is easier to use (by offering more powerful APIs) and +/// harder to misuse (thanks to its nature of being capability). +/// +pub struct Vmar(Arc, R); + +// TODO: how page faults can be delivered to and handled by the current VMAR. + +struct Vmar_ { + inner: Mutex, + // The offset relative to the root VMAR + base: Vaddr, + parent: Option>, +} + +struct Inner { + is_destroyed: bool, + vm_space: VmSpace, + //... +} + +impl Vmar_ { + pub fn new() -> Result { + todo!() + } + + pub fn protect(&self, perms: VmPerms, range: Range) -> Result<()> { + todo!() + } + + pub fn destroy_all(&self) -> Result<()> { + todo!() + } + + pub fn destroy(&self, range: Range) -> Result<()> { + todo!() + } + + pub fn read(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + todo!() + } + + pub fn write(&self, offset: usize, buf: &[u8]) -> Result<()> { + todo!() + } +} + +impl Vmar { + /// The base address, i.e., the offset relative to the root VMAR. + /// + /// The base address of a root VMAR is zero. + pub fn base(&self) -> Vaddr { + self.base + } + + fn check_rights(&self, rights: Rights) -> Result<()> { + if self.rights.contains(rights) { + Ok(()) + } else { + Err(EACCESS) + } + } +} + +bitflags! { + /// The memory access permissions of memory mappings. + pub struct VmPerms: u32 { + /// Readable. + const READ: u32 = 1 << 0; + /// Writable. + const WRITE: u32 = 1 << 1; + /// Executable. + const EXEC: u32 = 1 << 2; + } +} + +impl From for VmPerms { + fn from(perms: VmPerms) -> Rights { + todo!() + } +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmar/options.rs b/src/kxos-std/src/vm/vmar/options.rs new file mode 100644 index 000000000..4c52e886c --- /dev/null +++ b/src/kxos-std/src/vm/vmar/options.rs @@ -0,0 +1,202 @@ +//! Options for allocating child VMARs and creating mappings. + +/// Options for allocating a child VMAR, which must not overlap with any +/// existing mappings or child VMARs. +/// +/// # Examples +/// +/// A child VMAR created from a parent VMAR of _dynamic_ capability is also a +/// _dynamic_ capability. +/// ``` +/// use kxo_std::vm::{PAGE_SIZE, Vmar}; +/// +/// let parent_vmar = Vmar::new(); +/// let child_size = 10 * PAGE_SIZE; +/// let child_vmar = parent_vmar +/// .new_child(child_size) +/// .alloc() +/// .unwrap(); +/// assert!(child_vmar.rights() == parent_vmo.rights()); +/// assert!(child_vmar.size() == child_size); +/// ``` +/// +/// A child VMO created from a parent VMO of _static_ capability is also a +/// _static_ capability. +/// ``` +/// use kxos_std::prelude::*; +/// use kxos_std::vm::{PAGE_SIZE, Vmar}; +/// +/// let parent_vmar: Vmar = Vmar::new(); +/// let child_size = 10 * PAGE_SIZE; +/// let child_vmar = parent_vmar +/// .new_child(child_size) +/// .alloc() +/// .unwrap(); +/// assert!(child_vmar.rights() == parent_vmo.rights()); +/// assert!(child_vmar.size() == child_size); +/// ``` +pub struct VmarChildOptions { + parent: Vmar, + size: usize, + offset: usize, +} + +impl VmarChildOptions { + /// Creates a default set of options with the specified size of the VMAR + /// (in bytes). + /// + /// The size of the VMAR will be rounded up to align with the page size. + pub fn new(parent: Vmar, size: usize) -> Self { + Self { + parent, + size, + offset: 0, + align: PAGE_SIZE, + } + } + + /// Set the alignment of the child VMAR. + /// + /// By default, the alignment is the page size. + /// + /// The alignment must be a power of two and a multiple of the page size. + pub fn align(mut self, align: usize) -> Self { + todo!() + } + + /// Sets the offset of the child VMAR. + /// + /// If not set, the system will choose an offset automatically. + /// + /// The offset must satisfy the alignment requirement. + /// Also, the child VMAR's range `[offset, offset + size)` must be within + /// the VMAR. + /// + /// If not specified, + /// + /// The offset must be page-aligned. + pub fn offset(mut self, offset: usize) -> Self { + todo!() + } + + /// Allocates the child VMAR according to the specified options. + /// + /// The new child VMAR + /// + /// # Access rights + /// + /// The child VMAR is initially assigned all the parent's access rights. + pub fn alloc(mut self) -> Result> { + todo!() + } +} + +/// Options for creating a new mapping. The mapping is not allowed to overlap +/// with any child VMARs. And unless specified otherwise, it is not allowed +/// to overlap with any existing mapping, either. +pub struct VmarMapOptions { + parent: Vmar, + vmo: Vmo, + perms: VmPerms, + vmo_offset: usize, + size: usize, + offset: Option, + align: usize, + can_overwrite: bool, +} + +impl VmarMapOptions<'a, R> { + /// Creates a default set of options with the VMO and the memory access + /// permissions. + /// + /// The VMO must have access rights that correspond to the memory + /// access permissions. For example, if `perms` contains `VmPerm::Write`, + /// then `vmo.rights()` should contain `Rights::WRITE`. + pub fn new(parent: Vmar, vmo: Vmo, perms: VmPerms) -> Self { + Self { + parent, + vmo, + perms, + vmo_offset: 0, + size: vmo.size(), + offset: None, + align: PAGE_SIZE, + can_overwrite: false, + } + } + + /// Sets the offset of the first memory page in the VMO that is to be + /// mapped into the VMAR. + /// + /// The offset must be page-aligned and within the VMO. + /// + /// The default value is zero. + pub fn vmo_offset(mut self, offset: usize) -> Self { + self.vmo_offset = offset; + self + } + + /// Sets the size of the mapping. + /// + /// The size of a mapping may not be equal to that of the VMO. + /// For example, it is ok to create a mapping whose size is larger than + /// that of the VMO, although one cannot read from or write to the + /// part of the mapping that is not backed by the VMO. + /// So you may wonder: what is the point of supporting such _oversized_ + /// mappings? The reason is two-fold. + /// 1. VMOs are resizable. So even if a mapping is backed by a VMO whose + /// size is equal to that of the mapping initially, we cannot prevent + /// the VMO from shrinking. + /// 2. Mappings are not allowed to overlap by default. As a result, + /// oversized mappings can serve as a placeholder to prevent future + /// mappings from occupying some particular address ranges accidentally. + /// + /// The default value is the size of the VMO. + pub fn size(mut self, size: usize) -> Self { + self.size = size; + self + } + + /// Sets the mapping's alignment. + /// + /// The default value is the page size. + /// + /// The provided alignment must be a power of two and a multiple of the + /// page size. + pub fn align(mut self, align: usize) -> Self { + self.align = align; + self + } + + /// Sets the mapping's offset inside the VMAR. + /// + /// The offset must satisfy the alignment requirement. + /// Also, the mapping's range `[offset, offset + size)` must be within + /// the VMAR. + /// + /// If not set, the system will choose an offset automatically. + pub fn offset(mut self, offset: usize) -> Self { + self.offset = offset; + self + } + + /// Sets whether the mapping can overwrite existing mappings. + /// + /// The default value is false. + /// + /// If this option is set to true, then the `offset` option must be + /// set. + pub fn can_overwrite(mut self, can_overwrite: bool) -> Self { + self.can_overwrite = can_overwrite; + self + } + + /// Creates the mapping. + /// + /// All options will be checked at this point. + /// + /// On success, the virtual address of the new mapping is returned. + pub fn build(mut self) -> Result { + todo!() + } +} diff --git a/src/kxos-std/src/vm/vmar/static_cap.rs b/src/kxos-std/src/vm/vmar/static_cap.rs new file mode 100644 index 000000000..a14b1ae6e --- /dev/null +++ b/src/kxos-std/src/vm/vmar/static_cap.rs @@ -0,0 +1,135 @@ +impl Vmar { + /// Creates a root VMAR. + /// + /// # Access rights + /// + /// A root VMAR is initially given full access rights. + pub fn new() -> Result { + let inner = Arc::new(Vmar_::new()); + let rights = R::new(); + let new_self = Self(inner, rights); + Ok(new_self) + } + + /// Maps the given VMO into the VMAR through a set of VMAR mapping options. + /// + /// # Example + /// + /// ``` + /// use kxos_std::prelude::*; + /// use kxos_std::vm::{PAGE_SIZE, Vmar, VmoOptions}; + /// + /// let vmar = Vmar::::new().unwrap(); + /// let vmo = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let target_vaddr = 0x1234000; + /// let real_vaddr = vmar + /// // Map the VMO to create a read-only mapping + /// .new_map(vmo, VmPerms::READ) + /// // Provide an optional offset for the mapping inside the VMAR + /// .offset(target_vaddr) + /// .build() + /// .unwrap(); + /// assert!(real_vaddr == target_vaddr); + /// ``` + /// + /// For more details on the available options, see `VmarMapOptions`. + /// + /// # Access rights + /// + /// This method requires the following access rights: + /// 1. The VMAR contains the rights corresponding to the memory permissions of + /// the mapping. For example, if `perms` contains `VmPerm::WRITE`, + /// then the VMAR must have the Write right. + /// 2. Similarly, the VMO contains the rights corresponding to the memory + /// permissions of the mapping. + /// + /// Memory permissions may be changed through the `protect` method, + /// which ensures that any updated memory permissions do not go beyond + /// the access rights of the underlying VMOs. + pub fn new_map(&self, vmo: Vmo, perms: VmPerms) -> VmarMapOptions<'_, Rights> { + let dup_self = self.dup(); + VmarMapOptions::new(dup_self, vmo_ref) + } + + /// Creates a new child VMAR through a set of VMAR child options. + /// + /// # Example + /// + /// ``` + /// let parent = Vmar::new().unwrap(); + /// let child_size = 10 * PAGE_SIZE; + /// let child = parent.new_child(child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmarChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMAR child will be of the same capability class and + /// access rights as the parent. + #[require(R > Dup)] + pub fn new_child(&self, size: usize) -> VmarChildOptions<'a, R> { + let dup_self = self.dup(); + VmarChildOptions::new(dup_self, size) + } + + /// Change the permissions of the memory mappings in the specified range. + /// + /// The range's start and end addresses must be page-aligned. + /// Also, the range must be completely mapped. + /// + /// # Access rights + /// + /// The VMAR must have the rights corresponding to the specified memory + /// permissions. + /// + /// The mappings overlapped with the specified range must be backed by + /// VMOs whose rights contain the rights corresponding to the specified + /// memory permissions. + pub fn protect(&self, perms: VmPerms, range: Range) -> Result<()> { + self.check_rights(perms.into())?; + self.0.protect(perms, range) + } + + /// Destroy a VMAR, including all its mappings and children VMARs. + /// + /// After being destroyed, the VMAR becomes useless and returns errors + /// for most of its methods. + pub fn destroy_all(&self) -> Result<()> { + self.0.destroy_all() + } + + /// Destroy all mappings and children VMARs that fall within the specified + /// range in bytes. + /// + /// The range's start and end addresses must be page-aligned. + /// + /// Mappings may fall partially within the range; only the overlapped + /// portions of the mappings are unmapped. + /// As for children VMARs, they must be fully within the range. + /// All children VMARs that fall within the range get their `destroy` methods + /// called. + pub fn destroy(&self, range: &Range) -> Result<()> { + self.0.destroy(range) + } + + /// Returns the access rights. + pub const fn rights(&self) -> Rights { + R::BITS + } +} + +impl VmIo for Vmar { + fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + self.check_rights!(Rights::READ)?; + self.0.read(offset, buf) + } + + fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { + self.check_rights!(Rights::WRITE)?; + self.0.write(offset, buf) + } +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmo/dyn_cap.rs b/src/kxos-std/src/vm/vmo/dyn_cap.rs new file mode 100644 index 000000000..15d8b974f --- /dev/null +++ b/src/kxos-std/src/vm/vmo/dyn_cap.rs @@ -0,0 +1,140 @@ +impl Vmo { + /// Creates a new slice VMO through a set of VMO child options. + /// + /// # Example + /// + /// ``` + /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let child_size = parent.size(); + /// let child = parent.new_slice_child(0..child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmoChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMO child will be of the same capability flavor as the parent; + /// so are the access rights. + pub fn new_slice_child(&self, range: Range) -> VmoChildOptions<'_, Rights, VmoSliceChild> { + let dup_self = self.dup()?; + VmoChildOptions::new_slice(dup_self, range) + } + + /// Creates a new COW VMO through a set of VMO child options. + /// + /// # Example + /// + /// ``` + /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let child_size = 2 * parent.size(); + /// let child = parent.new_cow_child(0..child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmoChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMO child will be of the same capability flavor as the parent. + /// The child will be given the access rights of the parent + /// plus the Write right. + pub fn new_cow_child(&self, range: Range) -> VmoChildOptions<'_, Rights, VmoCowChild> { + let dup_self = self.dup()?; + VmoChildOptions::new_cow(dup_self, range) + } + + /// Commits the pages specified in the range (in bytes). + /// + /// The range must be within the size of the VMO. + /// + /// The start and end addresses will be rounded down and up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + pub fn commit(&self, range: Range) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.commit(range) + } + + /// Decommits the pages specified in the range (in bytes). + /// + /// The range must be within the size of the VMO. + /// + /// The start and end addresses will be rounded down and up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + pub fn decommit(&self, range: Range) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.decommit(range) + } + + /// Resizes the VMO by giving a new size. + /// + /// The VMO must be resizable. + /// + /// The new size will be rounded up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + pub fn resize(&self, new_size: usize) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.resize(new_size) + } + + /// Clears the specified range by writing zeros. + /// + /// # Access rights + /// + /// The method requires the Write right. + pub fn clear(&self, range: Range) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.clear(range) + } + + /// Duplicates the capability. + /// + /// # Access rights + /// + /// The method requires the Dup right. + pub fn dup(&self) -> Result { + self.check_rights(Rights::DUP)?; + todo!() + } + + /// Restricts the access rights given the mask. + pub fn restrict(mut self, mask: Rights) -> Self { + todo!() + } + + /// Converts to a static capability. + pub fn to_static(self) -> Result> { + self.check_rights(R1::BITS)?; + todo!() + } + + /// Returns the access rights. + pub fn rights(&self) -> Rights { + self.1 + } +} + +impl VmIo for Vmo { + fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + self.check_rights(Rights::READ)?; + self.0.read(offset, buf) + } + + fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.write(offset, buf) + } +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmo/mod.rs b/src/kxos-std/src/vm/vmo/mod.rs new file mode 100644 index 000000000..cf50d7491 --- /dev/null +++ b/src/kxos-std/src/vm/vmo/mod.rs @@ -0,0 +1,170 @@ +//! Virtual Memory Objects (VMOs). + +use kx_frame::vm::VmIo; + +use crate::rights::{Rights, TRights}; + +mod static_cap; +mod dyn_cap; +mod options; +mod pager; + +pub use options::{VmoOptions, VmoChildOptions}; +pub use pager::Pager; + +/// Virtual Memory Objects (VMOs) are a type of capability that represents a +/// range of memory pages. +/// +/// # Features +/// +/// * **I/O interface.** A VMO provides read and write methods to access the +/// memory pages that it contain. +/// * **On-demand paging.** The memory pages of a VMO (except for _contiguous_ +/// VMOs) are allocated lazily when the page is first accessed. +/// * **Tree structure.** Given a VMO, one can create a child VMO from it. +/// The child VMO can only access a subset of the parent's memory, +/// which is a good thing for the perspective of access control. +/// * **Copy-on-write (COW).** A child VMO may be created with COW semantics, +/// which prevents any writes on the child from affecting the parent +/// by duplicating memory pages only upon the first writes. +/// * **Access control.** As capabilities, VMOs restrict the +/// accessible range of memory and the allowed I/O operations. +/// * **Device driver support.** If specified upon creation, VMOs will be +/// backed by physically contiguous memory pages starting at a target address. +/// * **File system support.** By default, a VMO's memory pages are initially +/// all zeros. But if a VMO is attached to a pager (`Pager`) upon creation, +/// then its memory pages will be populated by the pager. +/// With this pager mechanism, file systems can easily implement page caches +/// with VMOs by attaching the VMOs to pagers backed by inodes. +/// +/// # Capabilities +/// +/// As a capability, each VMO is associated with a set of access rights, +/// whose semantics are explained below. +/// +/// * The Dup right allows duplicating a VMO and creating children out of +/// a VMO. +/// * The Read, Write, Exec rights allow creating memory mappings with +/// readable, writable, and executable access permissions, respectively. +/// * The Read and Write rights allow the VMO to be read from and written to +/// directly. +/// * The Write right allows resizing a resizable VMO. +/// +/// VMOs are implemented with two flavors of capabilities: +/// the dynamic one (`Vmo`) and the static one (`Vmo). +/// +/// # Examples +/// +/// For creating root VMOs, see `VmoOptions`.` +/// +/// For creating child VMOs, see `VmoChildOptions`. +/// +/// # Implementation +/// +/// `Vmo` provides high-level APIs for address space management by wrapping +/// around its low-level counterpart `kx_frame::vm::VmFrames`. +/// Compared with `VmFrames`, +/// `Vmo` is easier to use (by offering more powerful APIs) and +/// harder to misuse (thanks to its nature of being capability). +/// +pub struct Vmo(Arc, R); + +bitflags! { + /// VMO flags. + pub struct VmoFlags: u32 { + /// Set this flag if a VMO is resizable. + const RESIZABLE: u32 = 1 << 0; + /// Set this flags if a VMO is backed by physically contiguous memory + /// pages. + /// + /// To ensure the memory pages to be contiguous, these pages + /// are allocated upon the creation of the VMO, rather than on demands. + const CONTIGUOUS: u32 = 1 << 1; + /// Set this flag if a VMO is backed by memory pages that supports + /// Direct Memory Access (DMA) by devices. + const DMA: u32 = 1 << 2; + } +} + +struct Vmo_ { + flags: VmoFlags, + inner: Mutex, + parent: Option>, +} + +struct VmoInner { + //... +} + +impl Vmo_ { + pub fn commit_page(&self, offset: usize) -> Result<()> { + todo!() + } + + pub fn decommit_page(&self, offset: usize) -> Result<()> { + todo!() + } + + pub fn commit(&self, range: Range) -> Result<()> { + todo!() + } + + pub fn decommit(&self, range: Range) -> Result<()> { + todo!() + } + + pub fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + todo!() + } + + pub fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { + todo!() + } + + pub fn clear(&self, range: Range) -> Result<()> { + todo!() + } + + pub fn size(&self) -> usize { + self.0.size() + } + + pub fn resize(&self, new_size: usize) -> Result<()> { + todo!() + } + + pub fn paddr(&self) -> Option { + todo!() + } + + pub fn flags(&self) -> VmoFlags { + todo!() + } +} + + +impl Vmo { + /// Returns the size (in bytes) of a VMO. + pub fn size(&self) -> usize { + self.0.size() + } + + /// Returns the starting physical address of a VMO, if it is contiguous. + /// Otherwise, returns none. + pub fn paddr(&self) -> Option { + self.0.paddr() + } + + /// Returns the flags of a VMO. + pub fn flags(&self) -> VmoFlags { + self.0.flags() + } + + fn check_rights(&self, rights: Rights) -> Result<()> { + if self.rights().contains(rights) { + Ok(()) + } else { + Err(Error::AccessDenied) + } + } +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmo/options.rs b/src/kxos-std/src/vm/vmo/options.rs new file mode 100644 index 000000000..78cd7ccb6 --- /dev/null +++ b/src/kxos-std/src/vm/vmo/options.rs @@ -0,0 +1,303 @@ +//! Options for allocating root and child VMOs. + +/// Options for allocating a root VMO. +/// +/// # Examples +/// +/// Creating a VMO as a _dynamic_ capability with full access rights: +/// ``` +/// use kxo_std::vm::{PAGE_SIZE, VmoOptions}; +/// +/// let vmo = VmoOptions::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// ``` +/// +/// Creating a VMO as a _static_ capability with all access rights: +/// ``` +/// use kxos_std::prelude::*; +/// use kxo_std::vm::{PAGE_SIZE, VmoOptions}; +/// +/// let vmo = VmoOptions::::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// ``` +/// +/// Creating a resizable VMO backed by 10 memory pages that may not be +/// physically contiguous: +/// +/// ``` +/// use kxos_std::vm::{PAGE_SIZE, VmoOptions, VmoFlags}; +/// +/// let vmo = VmoOptions::new(10 * PAGE_SIZE) +/// .flags(VmoFlags::RESIZABLE) +/// .alloc() +/// .unwrap(); +/// ``` +pub struct VmoOptions { + size: usize, + paddr: Option, + flags: VmoFlags, + supplier: Option>, +} + +impl VmoOptions { + /// Creates a default set of options with the specified size of the VMO + /// (in bytes). + /// + /// The size of the VMO will be rounded up to align with the page size. + pub fn new(size: usize) -> Self { + todo!() + } + + /// Sets the starting physical address of the VMO. + /// + /// By default, this option is not set. + /// + /// If this option is set, then the underlying pages of VMO must be contiguous. + /// So `VmoFlags::IS_CONTIGUOUS` will be set automatically. + pub fn paddr(mut self, paddr: Paddr) -> Self { + todo!() + } + + /// Sets the VMO flags. + /// + /// The default value is `VmoFlags::empty()`. + /// + /// For more information about the flags, see `VmoFlags`. + pub fn flags(mut self, flags: VmoFlags) -> Self { + todo!() + } + + /// Sets the pager of the VMO. + pub fn pager(mut self, pager: Arc) -> Self { + todo!() + } +} + +impl VmoOptions { + /// Allocates the VMO according to the specified options. + /// + /// # Access rights + /// + /// The VMO is initially assigned full access rights. + pub fn alloc(mut self) -> Result> { + todo!() + } +} + +impl VmoOptions { + /// Allocates the VMO according to the specified options. + /// + /// # Access rights + /// + /// The VMO is initially assigned the access rights represented + /// by `R: TRights`. + pub fn alloc(mut self) -> Result> { + todo!() + } +} + +/// Options for allocating a child VMO out of a parent VMO. +/// +/// # Examples +/// +/// A child VMO created from a parent VMO of _dynamic_ capability is also a +/// _dynamic_ capability. +/// ``` +/// use kxo_std::vm::{PAGE_SIZE, VmoOptions}; +/// +/// let parent_vmo = VmoOptions::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// let child_vmo = parent_vmo.new_slice_child(0..PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// assert!(parent_vmo.rights() == child_vmo.rights()); +/// ``` +/// +/// A child VMO created from a parent VMO of _static_ capability is also a +/// _static_ capability. +/// ``` +/// use kxos_std::prelude::*; +/// use kxos_std::vm::{PAGE_SIZE, VmoOptions, VmoChildOptions}; +/// +/// let parent_vmo: Vmo = VmoOptions::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// let child_vmo: Vmo = parent_vmo.new_slice_child(0..PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// assert!(parent_vmo.rights() == child_vmo.rights()); +/// ``` +/// +/// Normally, a child VMO is initially given the same set of access rights +/// as its parent (as shown above). But there is one exception: +/// if the child VMO is created as a COW child, then it is granted the Write +/// right regardless of whether the parent is writable or not. +/// +/// ``` +/// use kxo_std::vm::{PAGE_SIZE, VmoOptions, VmoChildOptions}; +/// +/// let parent_vmo = VmoOptions::new(PAGE_SIZE) +/// .alloc() +/// .unwrap() +/// .restrict(Rights::DUP | Rights::READ); +/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// assert!(child_vmo.rights().contains(Rights::WRITE)); +/// ``` +/// +/// The above rule for COW VMO children also applies to static capabilities. +/// +/// ``` +/// use kxos_std::vm::{PAGE_SIZE, VmoOptions, VmoChildOptions}; +/// +/// let parent_vmo = VmoOptions::::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// assert!(child_vmo.rights().contains(Rights::WRITE)); +/// ``` +/// +/// One can set VMO flags for a child VMO. Currently, the only flag that is +/// valid when creating VMO children is `VmoFlags::RESIZABLE`. +/// Note that a slice VMO child and its parent cannot not be resizable. +/// +/// ```rust +/// use kxo_std::vm::{PAGE_SIZE, VmoOptions}; +/// +/// let parent_vmo = VmoOptions::new(PAGE_SIZE) +/// .alloc() +/// .unwrap(); +/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) +/// // Make the child resizable! +/// .flags(VmoFlags::RESIZABLE) +/// .alloc() +/// .unwrap(); +/// assert!(parent_vmo.rights() == child_vmo.rights()); +/// ``` +pub struct VmoChildOptions { + parent: Vmo, + range: Range, + flags: VmoFlags, + // Specifies whether the child is a slice or a COW + child_marker: PhantomData, +} + +impl VmoChildOptions { + /// Creates a default set of options for creating a slice VMO child. + /// + /// A slice child of a VMO, which has direct access to a range of memory + /// pages in the parent VMO. In other words, any updates of the parent will + /// reflect on the child, and vice versa. + /// + /// The range of a child must be within that of the parent. + #[require(R > Dup)] + pub fn new_slice(parent: Vmo, range: Range) -> Self { + Self { + parent, + range, + flags: parent.flags & Self::PARENT_FLAGS_MASK, + marker: PhantomData, + } + } +} + +impl VmoChildOptions { + /// Creates a default set of options for creating a copy-on-write (COW) + /// VMO child. + /// + /// A COW VMO child behaves as if all its + /// memory pages are copied from the parent VMO upon creation, although + /// the copying is done lazily when the parent's memory pages are updated. + /// + /// The range of a child may go beyond that of the parent. + /// Any pages that are beyond the parent's range are initially all zeros. + pub fn new_cow(parent: Vmo, range: Range) -> Self { + Self { + parent, + range, + flags: parent.flags & Self::PARENT_FLAGS_MASK, + marker: PhantomData, + } + } +} + +impl VmoChildOptions { + /// Flags that a VMO child inherits from its parent. + pub const PARENT_FLAGS_MASK: VmoFlags = VmoFlags::CONTIGUOUS.bits | + VmoFlags::DMA.bits; + /// Flags that a VMO child may differ from its parent. + pub const CHILD_FLAGS_MASK: VmoFlags = VmoFlags::RESIZABLE.bits; + + /// Sets the VMO flags. + /// + /// Only the flags among `Self::CHILD_FLAGS_MASK` may be set through this + /// method. + /// + /// To set `VmoFlags::RESIZABLE`, the child must be COW. + /// + /// The default value is `VmoFlags::empty()`. + pub fn flags(mut self, flags: VmoFlags) -> Self { + self.flags = flags & Self::CHILD_FLAGS_MASK; + self + } +} + +impl<'a, C> VmoChildOptions<'a, Rights, C> { + /// Allocates the child VMO. + /// + /// # Access rights + /// + /// The child VMO is initially assigned all the parent's access rights. + pub fn alloc(mut self) -> Result> { + todo!() + } +} + +impl<'a, R: TRights> VmoChildOptions<'a, R, VmoSliceChild> { + /// Allocates the child VMO. + /// + /// # Access rights + /// + /// The child VMO is initially assigned all the parent's access rights. + pub fn alloc(mut self) -> Result> { + todo!() + } +} + +impl<'a, R: TRights> VmoChildOptions<'a, R, VmoCowChild> { + /// Allocates the child VMO. + /// + /// # Access rights + /// + /// The child VMO is initially assigned all the parent's access rights + /// plus the Write right. + pub fn alloc(mut self) -> Result> + where + // TODO: R1 must contain the Write right. To do so at the type level, + // we need to implement a type-level operator + // (say, `TRightsExtend(L, F)`) + // that may extend a list (`L`) of type-level flags with an extra flag `F`. + R1: R // TRightsExtend + { + todo!() + } +} + +/// A type to specify the "type" of a child, which is either a slice or a COW. +pub trait VmoChildType {} + +/// A type to mark a child is slice. +#[derive(Copy, Clone, Debug)] +pub struct VmoSliceChild; +impl VmoChildType for VmoSliceChild {}; + +/// A type to mark a child is COW. +#[derive(Copy, Clone, Debug)] +pub struct VmoCowChild; +impl VmoChildType for VmoCowChild {}; diff --git a/src/kxos-std/src/vm/vmo/pager.rs b/src/kxos-std/src/vm/vmo/pager.rs new file mode 100644 index 000000000..f40b3b6c9 --- /dev/null +++ b/src/kxos-std/src/vm/vmo/pager.rs @@ -0,0 +1,55 @@ +/// Pagers provide frame to a VMO. +/// +/// A `Pager` object can be attached to a VMO. Whenever the +/// VMO needs more frames (i.e., on commits), it will turn to the pager, +/// which should then provide frames whose data have been initialized properly. +/// Any time a frame is updated through the VMO, the VMO will +/// notify the attached pager that the frame has been updated. +/// Finally, when a frame is no longer needed (i.e., on decommits), +/// the frame pager will also be notified. +pub trait Pager { + /// Ask the pager to provide a frame at a specified offset (in bytes). + /// + /// After a page of a VMO is committed, the VMO shall not call this method + /// again until the page is decommitted. But a robust implementation of + /// `Pager` should not rely on this behavior for its correctness; + /// instead, it should returns the _same_ frame. + /// + /// If a VMO page has been previously committed and decommited, + /// and is to be committed again, then the pager is free to return + /// whatever frame that may or may not be the same as the last time. + /// + /// It is up to the pager to decide the range of valid offsets. + /// + /// The offset will be rounded down to page boundary. + fn commit_page(&self, offset: usize) -> Result; + + /// Notify the pager that the frame at a specified offset (in bytes) + /// has been updated. + /// + /// Being aware of the updates allow the pager (e.g., an inode) to + /// know which pages are dirty and only write back the _dirty_ pages back + /// to disk. + /// + /// The VMO will not call this method for an uncommitted page. + /// But a robust implementation of `Pager` should not make + /// such an assumption for its correctness; instead, it should simply ignore the + /// call or return an error. + /// + /// The offset will be rounded down to page boundary. + fn update_page(&self, offset: usize) -> Result<()>; + + /// Notify the pager that the frame at the specified offset (in bytes) + /// has been decommitted. + /// + /// Knowing that a frame is no longer needed, the pager (e.g., an inode) + /// can free the frame after writing back its data to the disk. + /// + /// The VMO will not call this method for an uncommitted page. + /// But a robust implementation of `Pager` should not make + /// such an assumption for its correctness; instead, it should simply ignore the + /// call or return an error. + /// + /// The offset will be rounded down to page boundary. + fn decommit_page(&self, offset: usize) -> Result<()>; +} \ No newline at end of file diff --git a/src/kxos-std/src/vm/vmo/static_cap.rs b/src/kxos-std/src/vm/vmo/static_cap.rs new file mode 100644 index 000000000..9276487a3 --- /dev/null +++ b/src/kxos-std/src/vm/vmo/static_cap.rs @@ -0,0 +1,147 @@ +impl Vmo { + /// Creates a new slice VMO through a set of VMO child options. + /// + /// # Example + /// + /// ``` + /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let child_size = parent.size(); + /// let child = parent.new_slice_child(0..child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmoChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMO child will be of the same capability flavor as the parent; + /// so are the access rights. + #[require(R > Dup)] + pub fn new_slice_child(&self, range: Range) -> VmoChildOptions<'_, R, VmoSliceChild> { + let dup_self = self.dup(); + VmoChildOptions::new_slice(dup_self, range) + } + + /// Creates a new COW VMO through a set of VMO child options. + /// + /// # Example + /// + /// ``` + /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); + /// let child_size = 2 * parent.size(); + /// let child = parent.new_cow_child(0..child_size).alloc().unwrap(); + /// assert!(child.size() == child_size); + /// ``` + /// + /// For more details on the available options, see `VmoChildOptions`. + /// + /// # Access rights + /// + /// This method requires the Dup right. + /// + /// The new VMO child will be of the same capability flavor as the parent. + /// The child will be given the access rights of the parent + /// plus the Write right. + #[require(R > Dup)] + pub fn new_cow_child(&self, range: Range) -> VmoChildOptions<'_, R, VmoCowChild> { + let dup_self = self.dup(); + VmoChildOptions::new_cow(dup_self, range) + } + + /// Commit the pages specified in the range (in bytes). + /// + /// The range must be within the size of the VMO. + /// + /// The start and end addresses will be rounded down and up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + #[require(R > Write)] + pub fn commit(&self, range: Range) -> Result<()> { + self.0.commit(range) + } + + /// Decommit the pages specified in the range (in bytes). + /// + /// The range must be within the size of the VMO. + /// + /// The start and end addresses will be rounded down and up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + #[require(R > Write)] + pub fn decommit(&self, range: Range) -> Result<()> { + self.0.decommit(range) + } + + /// Resize the VMO by giving a new size. + /// + /// The VMO must be resizable. + /// + /// The new size will be rounded up to page boundaries. + /// + /// # Access rights + /// + /// The method requires the Write right. + #[require(R > Write)] + pub fn resize(&self, new_size: usize) -> Result<()> { + self.0.resize(new_size) + } + + /// Clear the specified range by writing zeros. + /// + /// # Access rights + /// + /// The method requires the Write right. + #[require(R > Write)] + pub fn clear(&self, range: Range) -> Result<()> { + self.0.clear(range) + } + + /// Returns the size of the VMO in bytes. + pub fn size(&self) -> usize { + self.0.size() + } + + /// Duplicate the capability. + /// + /// # Access rights + /// + /// The method requires the Dup right. + #[require(R > Dup)] + pub fn dup(&self) -> Result { + todo!() + } + + /// Strict the access rights. + #[require(R > R1)] + pub fn restrict(mut self) -> Vmo { + todo!() + } + + /// Converts to a dynamic capability. + pub fn to_dyn(self) -> Vmo { + todo!() + } + + /// Returns the access rights. + pub const fn rights(&self) -> Rights { + R::BITS + } +} + +impl VmIo for Vmo { + fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { + self.check_rights(Rights::READ)?; + self.0.read_bytes(offset, buf) + } + + fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.write_bytes(offset, buf) + } +} \ No newline at end of file