// SPDX-License-Identifier: MPL-2.0 //! Virtual memory space management. //! //! The [`VmSpace`] struct is provided to manage the virtual memory space of a //! user. Cursors are used to traverse and modify over the virtual memory space //! concurrently. The VM space cursor [`self::Cursor`] is just a wrapper over //! the page table cursor [`super::page_table::Cursor`], providing efficient, //! powerful concurrent accesses to the page table, and suffers from the same //! validity concerns as described in [`super::page_table::cursor`]. use core::{ops::Range, sync::atomic::Ordering}; use crate::{ arch::mm::{ current_page_table_paddr, tlb_flush_all_excluding_global, PageTableEntry, PagingConsts, }, cpu::{AtomicCpuSet, CpuExceptionInfo, CpuSet, PinCurrentCpu}, cpu_local_cell, mm::{ io::Fallible, kspace::KERNEL_PAGE_TABLE, page_table::{self, PageTable, PageTableItem, UserMode}, tlb::{TlbFlushOp, TlbFlusher, FLUSH_ALL_RANGE_THRESHOLD}, Frame, PageProperty, VmReader, VmWriter, MAX_USERSPACE_VADDR, }, prelude::*, sync::{RwLock, RwLockReadGuard}, task::{disable_preempt, DisabledPreemptGuard}, Error, }; /// Virtual memory space. /// /// A virtual memory space (`VmSpace`) can be created and assigned to a user /// space so that the virtual memory of the user space can be manipulated /// safely. For example, given an arbitrary user-space pointer, one can read /// and write the memory location referred to by the user-space pointer without /// the risk of breaking the memory safety of the kernel space. /// /// A newly-created `VmSpace` is not backed by any physical memory pages. To /// provide memory pages for a `VmSpace`, one can allocate and map physical /// memory ([`Frame`]s) to the `VmSpace` using the cursor. /// /// A `VmSpace` can also attach a page fault handler, which will be invoked to /// handle page faults generated from user space. #[allow(clippy::type_complexity)] #[derive(Debug)] pub struct VmSpace { pt: PageTable, page_fault_handler: Option core::result::Result<(), ()>>, /// A CPU can only activate a `VmSpace` when no mutable cursors are alive. /// Cursors hold read locks and activation require a write lock. activation_lock: RwLock<()>, cpus: AtomicCpuSet, } impl VmSpace { /// Creates a new VM address space. pub fn new() -> Self { Self { pt: KERNEL_PAGE_TABLE.get().unwrap().create_user_page_table(), page_fault_handler: None, activation_lock: RwLock::new(()), cpus: AtomicCpuSet::new(CpuSet::new_empty()), } } /// Clears the user space mappings in the page table. /// /// This method returns error if the page table is activated on any other /// CPUs or there are any cursors alive. pub fn clear(&self) -> core::result::Result<(), VmSpaceClearError> { let preempt_guard = disable_preempt(); let _guard = self .activation_lock .try_write() .ok_or(VmSpaceClearError::CursorsAlive)?; let cpus = self.cpus.load(); let cpu = preempt_guard.current_cpu(); let cpus_set_is_empty = cpus.is_empty(); let cpus_set_is_single_self = cpus.count() == 1 && cpus.contains(cpu); if cpus_set_is_empty || cpus_set_is_single_self { // SAFETY: We have ensured that the page table is not activated on // other CPUs and no cursors are alive. unsafe { self.pt.clear() }; if cpus_set_is_single_self { tlb_flush_all_excluding_global(); } Ok(()) } else { Err(VmSpaceClearError::PageTableActivated(cpus)) } } /// Gets an immutable cursor in the virtual address range. /// /// The cursor behaves like a lock guard, exclusively owning a sub-tree of /// the page table, preventing others from creating a cursor in it. So be /// sure to drop the cursor as soon as possible. /// /// The creation of the cursor may block if another cursor having an /// overlapping range is alive. pub fn cursor(&self, va: &Range) -> Result> { Ok(self.pt.cursor(va).map(Cursor)?) } /// Gets an mutable cursor in the virtual address range. /// /// The same as [`Self::cursor`], the cursor behaves like a lock guard, /// exclusively owning a sub-tree of the page table, preventing others /// from creating a cursor in it. So be sure to drop the cursor as soon as /// possible. /// /// The creation of the cursor may block if another cursor having an /// overlapping range is alive. The modification to the mapping by the /// cursor may also block or be overridden the mapping of another cursor. pub fn cursor_mut(&self, va: &Range) -> Result> { Ok(self.pt.cursor_mut(va).map(|pt_cursor| { let activation_lock = self.activation_lock.read(); CursorMut { pt_cursor, activation_lock, flusher: TlbFlusher::new(self.cpus.load(), disable_preempt()), } })?) } /// Activates the page table on the current CPU. pub(crate) fn activate(self: &Arc) { let preempt_guard = disable_preempt(); let cpu = preempt_guard.current_cpu(); let last_ptr = ACTIVATED_VM_SPACE.load(); if last_ptr == Arc::as_ptr(self) { return; } // Ensure no mutable cursors (which holds read locks) are alive before // we add the CPU to the CPU set. let _activation_lock = self.activation_lock.write(); // Record ourselves in the CPU set and the activated VM space pointer. self.cpus.add(cpu, Ordering::Relaxed); let self_ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace; ACTIVATED_VM_SPACE.store(self_ptr); if !last_ptr.is_null() { // SAFETY: The pointer is cast from an `Arc` when it's activated // the last time, so it can be restored and only restored once. let last = unsafe { Arc::from_raw(last_ptr) }; last.cpus.remove(cpu, Ordering::Relaxed); } self.pt.activate(); } pub(crate) fn handle_page_fault( &self, info: &CpuExceptionInfo, ) -> core::result::Result<(), ()> { if let Some(func) = self.page_fault_handler { return func(self, info); } Err(()) } /// Registers the page fault handler in this `VmSpace`. pub fn register_page_fault_handler( &mut self, func: fn(&VmSpace, &CpuExceptionInfo) -> core::result::Result<(), ()>, ) { self.page_fault_handler = Some(func); } /// Creates a reader to read data from the user space of the current task. /// /// Returns `Err` if this `VmSpace` is not belonged to the user space of the current task /// or the `vaddr` and `len` do not represent a user space memory range. pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result> { if current_page_table_paddr() != unsafe { self.pt.root_paddr() } { return Err(Error::AccessDenied); } if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR { return Err(Error::AccessDenied); } // `VmReader` is neither `Sync` nor `Send`, so it will not live longer than the current // task. This ensures that the correct page table is activated during the usage period of // the `VmReader`. // // SAFETY: The memory range is in user space, as checked above. Ok(unsafe { VmReader::::from_user_space(vaddr as *const u8, len) }) } /// Creates a writer to write data into the user space. /// /// Returns `Err` if this `VmSpace` is not belonged to the user space of the current task /// or the `vaddr` and `len` do not represent a user space memory range. pub fn writer(&self, vaddr: Vaddr, len: usize) -> Result> { if current_page_table_paddr() != unsafe { self.pt.root_paddr() } { return Err(Error::AccessDenied); } if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR { return Err(Error::AccessDenied); } // `VmWriter` is neither `Sync` nor `Send`, so it will not live longer than the current // task. This ensures that the correct page table is activated during the usage period of // the `VmWriter`. // // SAFETY: The memory range is in user space, as checked above. Ok(unsafe { VmWriter::::from_user_space(vaddr as *mut u8, len) }) } } impl Default for VmSpace { fn default() -> Self { Self::new() } } /// An error that may occur when doing [`VmSpace::clear`]. #[derive(Debug)] pub enum VmSpaceClearError { /// The page table is activated on other CPUs. /// /// The activated CPUs detected are contained in the error. PageTableActivated(CpuSet), /// There are still cursors alive. CursorsAlive, } /// The cursor for querying over the VM space without modifying it. /// /// It exclusively owns a sub-tree of the page table, preventing others from /// reading or modifying the same sub-tree. Two read-only cursors can not be /// created from the same virtual address range either. pub struct Cursor<'a>(page_table::Cursor<'a, UserMode, PageTableEntry, PagingConsts>); impl Iterator for Cursor<'_> { type Item = VmItem; fn next(&mut self) -> Option { let result = self.query(); if result.is_ok() { self.0.move_forward(); } result.ok() } } impl Cursor<'_> { /// Query about the current slot. /// /// This function won't bring the cursor to the next slot. pub fn query(&mut self) -> Result { Ok(self.0.query().map(|item| item.try_into().unwrap())?) } /// Jump to the virtual address. pub fn jump(&mut self, va: Vaddr) -> Result<()> { self.0.jump(va)?; Ok(()) } /// Get the virtual address of the current slot. pub fn virt_addr(&self) -> Vaddr { self.0.virt_addr() } } /// The cursor for modifying the mappings in VM space. /// /// It exclusively owns a sub-tree of the page table, preventing others from /// reading or modifying the same sub-tree. pub struct CursorMut<'a, 'b> { pt_cursor: page_table::CursorMut<'a, UserMode, PageTableEntry, PagingConsts>, #[allow(dead_code)] activation_lock: RwLockReadGuard<'b, ()>, // We have a read lock so the CPU set in the flusher is always a superset // of actual activated CPUs. flusher: TlbFlusher, } impl CursorMut<'_, '_> { /// Query about the current slot. /// /// This is the same as [`Cursor::query`]. /// /// This function won't bring the cursor to the next slot. pub fn query(&mut self) -> Result { Ok(self .pt_cursor .query() .map(|item| item.try_into().unwrap())?) } /// Jump to the virtual address. /// /// This is the same as [`Cursor::jump`]. pub fn jump(&mut self, va: Vaddr) -> Result<()> { self.pt_cursor.jump(va)?; Ok(()) } /// Get the virtual address of the current slot. pub fn virt_addr(&self) -> Vaddr { self.pt_cursor.virt_addr() } /// Get the dedicated TLB flusher for this cursor. pub fn flusher(&self) -> &TlbFlusher { &self.flusher } /// Map a frame into the current slot. /// /// This method will bring the cursor to the next slot after the modification. pub fn map(&mut self, frame: Frame, prop: PageProperty) { let start_va = self.virt_addr(); // SAFETY: It is safe to map untyped memory into the userspace. let old = unsafe { self.pt_cursor.map(frame.into(), prop) }; if let Some(old) = old { self.flusher .issue_tlb_flush_with(TlbFlushOp::Address(start_va), old); self.flusher.dispatch_tlb_flush(); } } /// Clear the mapping starting from the current slot. /// /// This method will bring the cursor forward by `len` bytes in the virtual /// address space after the modification. /// /// Already-absent mappings encountered by the cursor will be skipped. It /// is valid to unmap a range that is not mapped. /// /// It must issue and dispatch a TLB flush after the operation. Otherwise, /// the memory safety will be compromised. Please call this function less /// to avoid the overhead of TLB flush. Using a large `len` is wiser than /// splitting the operation into multiple small ones. /// /// # Panics /// /// This method will panic if `len` is not page-aligned. pub fn unmap(&mut self, len: usize) { assert!(len % super::PAGE_SIZE == 0); let end_va = self.virt_addr() + len; let tlb_prefer_flush_all = len > FLUSH_ALL_RANGE_THRESHOLD; loop { // SAFETY: It is safe to un-map memory in the userspace. let result = unsafe { self.pt_cursor.take_next(end_va - self.virt_addr()) }; match result { PageTableItem::Mapped { va, page, .. } => { if !self.flusher.need_remote_flush() && tlb_prefer_flush_all { // Only on single-CPU cases we can drop the page immediately before flushing. drop(page); continue; } self.flusher .issue_tlb_flush_with(TlbFlushOp::Address(va), page); } PageTableItem::NotMapped { .. } => { break; } PageTableItem::MappedUntracked { .. } => { panic!("found untracked memory mapped into `VmSpace`"); } } } if !self.flusher.need_remote_flush() && tlb_prefer_flush_all { self.flusher.issue_tlb_flush(TlbFlushOp::All); } self.flusher.dispatch_tlb_flush(); } /// Applies the operation to the next slot of mapping within the range. /// /// The range to be found in is the current virtual address with the /// provided length. /// /// The function stops and yields the actually protected range if it has /// actually protected a page, no matter if the following pages are also /// required to be protected. /// /// It also makes the cursor moves forward to the next page after the /// protected one. If no mapped pages exist in the following range, the /// cursor will stop at the end of the range and return [`None`]. /// /// Note that it will **NOT** flush the TLB after the operation. Please /// make the decision yourself on when and how to flush the TLB using /// [`Self::flusher`]. /// /// # Panics /// /// This function will panic if: /// - the range to be protected is out of the range where the cursor /// is required to operate; /// - the specified virtual address range only covers a part of a page. pub fn protect_next( &mut self, len: usize, mut op: impl FnMut(&mut PageProperty), ) -> Option> { // SAFETY: It is safe to protect memory in the userspace. unsafe { self.pt_cursor.protect_next(len, &mut op) } } /// Copies the mapping from the given cursor to the current cursor. /// /// All the mappings in the current cursor's range must be empty. The /// function allows the source cursor to operate on the mapping before /// the copy happens. So it is equivalent to protect then duplicate. /// Only the mapping is copied, the mapped pages are not copied. /// /// After the operation, both cursors will advance by the specified length. /// /// Note that it will **NOT** flush the TLB after the operation. Please /// make the decision yourself on when and how to flush the TLB using /// the source's [`CursorMut::flusher`]. /// /// # Panics /// /// This function will panic if: /// - either one of the range to be copied is out of the range where any /// of the cursor is required to operate; /// - either one of the specified virtual address ranges only covers a /// part of a page. /// - the current cursor's range contains mapped pages. pub fn copy_from( &mut self, src: &mut Self, len: usize, op: &mut impl FnMut(&mut PageProperty), ) { // SAFETY: Operations on user memory spaces are safe if it doesn't // involve dropping any pages. unsafe { self.pt_cursor.copy_from(&mut src.pt_cursor, len, op) } } } cpu_local_cell! { /// The `Arc` pointer to the activated VM space on this CPU. If the pointer /// is NULL, it means that the activated page table is merely the kernel /// page table. // TODO: If we are enabling ASID, we need to maintain the TLB state of each // CPU, rather than merely the activated `VmSpace`. When ASID is enabled, // the non-active `VmSpace`s can still have their TLB entries in the CPU! static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null(); } /// The result of a query over the VM space. #[derive(Debug)] pub enum VmItem { /// The current slot is not mapped. NotMapped { /// The virtual address of the slot. va: Vaddr, /// The length of the slot. len: usize, }, /// The current slot is mapped. Mapped { /// The virtual address of the slot. va: Vaddr, /// The mapped frame. frame: Frame, /// The property of the slot. prop: PageProperty, }, } impl TryFrom for VmItem { type Error = &'static str; fn try_from(item: PageTableItem) -> core::result::Result { match item { PageTableItem::NotMapped { va, len } => Ok(VmItem::NotMapped { va, len }), PageTableItem::Mapped { va, page, prop } => Ok(VmItem::Mapped { va, frame: page .try_into() .map_err(|_| "found typed memory mapped into `VmSpace`")?, prop, }), PageTableItem::MappedUntracked { .. } => { Err("found untracked memory mapped into `VmSpace`") } } } }