// SPDX-License-Identifier: MPL-2.0 //! Metadata management of pages. //! //! You can picture a globally shared, static, gigantic array of metadata initialized for each page. //! An entry in the array is called a `MetaSlot`, which contains the metadata of a page. There would //! be a dedicated small "heap" space in each slot for dynamic metadata. You can store anything as the //! metadata of a page as long as it's [`Sync`]. //! //! In the implementation level, the slots are placed in the metadata pages mapped to a certain virtual //! address. It is faster, simpler, safer and more versatile compared with an actual static array //! implementation. pub mod mapping { //! The metadata of each physical page is linear mapped to fixed virtual addresses //! in [`FRAME_METADATA_RANGE`]. use core::mem::size_of; use super::MetaSlot; use crate::mm::{kspace::FRAME_METADATA_RANGE, Paddr, PagingConstsTrait, Vaddr, PAGE_SIZE}; /// Converts a physical address of a base page to the virtual address of the metadata slot. pub const fn page_to_meta(paddr: Paddr) -> Vaddr { let base = FRAME_METADATA_RANGE.start; let offset = paddr / PAGE_SIZE; base + offset * size_of::() } /// Converts a virtual address of the metadata slot to the physical address of the page. pub const fn meta_to_page(vaddr: Vaddr) -> Paddr { let base = FRAME_METADATA_RANGE.start; let offset = (vaddr - base) / size_of::(); offset * PAGE_SIZE } } use alloc::vec::Vec; use core::{ cell::UnsafeCell, marker::PhantomData, mem::{size_of, ManuallyDrop}, panic, sync::atomic::{AtomicU32, AtomicU8, Ordering}, }; use log::info; use num_derive::FromPrimitive; use static_assertions::const_assert_eq; use super::{allocator, Page}; use crate::{ arch::mm::{PageTableEntry, PagingConsts}, mm::{ paddr_to_vaddr, page_size, page_table::{boot_pt, PageTableEntryTrait}, CachePolicy, Paddr, PageFlags, PageProperty, PagingConstsTrait, PagingLevel, PrivilegedPageFlags, Vaddr, PAGE_SIZE, }, }; /// Represents the usage of a page. #[repr(u8)] #[derive(Debug, FromPrimitive, PartialEq)] pub enum PageUsage { // The zero variant is reserved for the unused type. Only an unused page // can be designated for one of the other purposes. #[allow(dead_code)] Unused = 0, /// The page is reserved or unusable. The kernel should not touch it. #[allow(dead_code)] Reserved = 1, /// The page is used as a frame, i.e., a page of untyped memory. Frame = 32, /// The page is used by a page table. PageTable = 64, /// The page stores metadata of other pages. Meta = 65, /// The page stores the kernel such as kernel code, data, etc. Kernel = 66, /// The page stores data for kernel stack. KernelStack = 67, } #[repr(C)] pub(in crate::mm) struct MetaSlot { /// The metadata of the page. /// /// It is placed at the beginning of a slot because: /// - the implementation can simply cast a `*const MetaSlot` /// to a `*const PageMeta` for manipulation; /// - the subsequent fields can utilize the end padding of the /// the inner union to save space. _inner: MetaSlotInner, /// To store [`PageUsage`]. pub(super) usage: AtomicU8, pub(super) ref_count: AtomicU32, } pub(super) union MetaSlotInner { _frame: ManuallyDrop, _pt: ManuallyDrop, } // Currently the sizes of the `MetaSlotInner` union variants are no larger // than 8 bytes and aligned to 8 bytes. So the size of `MetaSlot` is 16 bytes. // // Note that the size of `MetaSlot` should be a multiple of 8 bytes to prevent // cross-page accesses. const_assert_eq!(size_of::(), 16); /// All page metadata types must implemented this sealed trait, /// which ensures that each fields of `PageUsage` has one and only /// one metadata type corresponding to the usage purpose. Any user /// outside this module won't be able to add more metadata types /// and break assumptions made by this module. /// /// If a page type needs specific drop behavior, it should specify /// when implementing this trait. When we drop the last handle to /// this page, the `on_drop` method will be called. pub trait PageMeta: Sync + private::Sealed + Sized { const USAGE: PageUsage; fn on_drop(page: &mut Page); } /// An internal routine in dropping implementations. /// /// # Safety /// /// The caller should ensure that the pointer points to a page's metadata slot. The /// page should have a last handle to the page, and the page is about to be dropped, /// as the metadata slot after this operation becomes uninitialized. pub(super) unsafe fn drop_as_last(ptr: *const MetaSlot) { // This would be guaranteed as a safety requirement. debug_assert_eq!((*ptr).ref_count.load(Ordering::Relaxed), 0); // Let the custom dropper handle the drop. let mut page = Page:: { ptr, _marker: PhantomData, }; M::on_drop(&mut page); let _ = ManuallyDrop::new(page); // Drop the metadata. core::ptr::drop_in_place(ptr as *mut M); // No handles means no usage. This also releases the page as unused for further // calls to `Page::from_unused`. (*ptr).usage.store(0, Ordering::Release); // Deallocate the page. // It would return the page to the allocator for further use. This would be done // after the release of the metadata to avoid re-allocation before the metadata // is reset. allocator::PAGE_ALLOCATOR.get().unwrap().lock().dealloc( mapping::meta_to_page::(ptr as Vaddr) / PAGE_SIZE, 1, ); } mod private { pub trait Sealed {} } // ======= Start of all the specific metadata structures definitions ========== use private::Sealed; #[derive(Debug, Default)] #[repr(C)] pub struct FrameMeta { // If not doing so, the page table metadata would fit // in the front padding of meta slot and make it 12 bytes. // We make it 16 bytes. Further usage of frame metadata // is welcome to exploit this space. _unused_for_layout_padding: [u8; 8], } impl Sealed for FrameMeta {} /// The metadata of any kinds of page table pages. /// Make sure the the generic parameters don't effect the memory layout. #[derive(Debug)] #[repr(C)] pub(in crate::mm) struct PageTablePageMeta< E: PageTableEntryTrait = PageTableEntry, C: PagingConstsTrait = PagingConsts, > where [(); C::NR_LEVELS as usize]:, { /// The number of valid PTEs. It is mutable if the lock is held. pub nr_children: UnsafeCell, /// The level of the page table page. A page table page cannot be /// referenced by page tables of different levels. pub level: PagingLevel, /// Whether the pages mapped by the node is tracked. pub is_tracked: MapTrackingStatus, /// The lock for the page table page. pub lock: AtomicU8, _phantom: core::marker::PhantomData<(E, C)>, } /// Describe if the physical address recorded in this page table refers to a /// page tracked by metadata. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub(in crate::mm) enum MapTrackingStatus { /// The page table node cannot contain references to any pages. It can only /// contain references to child page table nodes. NotApplicable, /// The mapped pages are not tracked by metadata. If any child page table /// nodes exist, they should also be tracked. Untracked, /// The mapped pages are tracked by metadata. If any child page table nodes /// exist, they should also be tracked. Tracked, } impl PageTablePageMeta where [(); C::NR_LEVELS as usize]:, { pub fn new_locked(level: PagingLevel, is_tracked: MapTrackingStatus) -> Self { Self { nr_children: UnsafeCell::new(0), level, is_tracked, lock: AtomicU8::new(1), _phantom: PhantomData, } } } impl Sealed for PageTablePageMeta where [(); C::NR_LEVELS as usize]: { } unsafe impl Send for PageTablePageMeta where [(); C::NR_LEVELS as usize]: { } unsafe impl Sync for PageTablePageMeta where [(); C::NR_LEVELS as usize]: { } #[derive(Debug, Default)] #[repr(C)] pub struct MetaPageMeta {} impl Sealed for MetaPageMeta {} impl PageMeta for MetaPageMeta { const USAGE: PageUsage = PageUsage::Meta; fn on_drop(_page: &mut Page) { panic!("Meta pages are currently not allowed to be dropped"); } } #[derive(Debug, Default)] #[repr(C)] pub struct KernelMeta {} impl Sealed for KernelMeta {} impl PageMeta for KernelMeta { const USAGE: PageUsage = PageUsage::Kernel; fn on_drop(_page: &mut Page) { panic!("Kernel pages are not allowed to be dropped"); } } #[derive(Debug, Default)] #[repr(C)] pub struct KernelStackMeta {} impl Sealed for KernelStackMeta {} impl PageMeta for KernelStackMeta { const USAGE: PageUsage = PageUsage::KernelStack; fn on_drop(_page: &mut Page) {} } // ======== End of all the specific metadata structures definitions =========== /// Initializes the metadata of all physical pages. /// /// The function returns a list of `Page`s containing the metadata. pub(crate) fn init() -> Vec> { let max_paddr = { let regions = crate::boot::memory_regions(); regions.iter().map(|r| r.base() + r.len()).max().unwrap() }; info!( "Initializing page metadata for physical memory up to {:x}", max_paddr ); super::MAX_PADDR.store(max_paddr, Ordering::Relaxed); let num_pages = max_paddr / page_size::(1); let num_meta_pages = (num_pages * size_of::()).div_ceil(PAGE_SIZE); let meta_pages = alloc_meta_pages(num_meta_pages); // Map the metadata pages. boot_pt::with_borrow(|boot_pt| { for (i, frame_paddr) in meta_pages.iter().enumerate() { let vaddr = mapping::page_to_meta::(0) + i * PAGE_SIZE; let prop = PageProperty { flags: PageFlags::RW, cache: CachePolicy::Writeback, priv_flags: PrivilegedPageFlags::GLOBAL, }; // SAFETY: we are doing the metadata mappings for the kernel. unsafe { boot_pt.map_base_page(vaddr, frame_paddr / PAGE_SIZE, prop) }; } }) .unwrap(); // Now the metadata pages are mapped, we can initialize the metadata. meta_pages .into_iter() .map(|paddr| Page::::from_unused(paddr, MetaPageMeta::default())) .collect() } fn alloc_meta_pages(nframes: usize) -> Vec { let mut meta_pages = Vec::new(); let start_frame = allocator::PAGE_ALLOCATOR .get() .unwrap() .lock() .alloc(nframes) .unwrap() * PAGE_SIZE; // Zero them out as initialization. let vaddr = paddr_to_vaddr(start_frame) as *mut u8; unsafe { core::ptr::write_bytes(vaddr, 0, PAGE_SIZE * nframes) }; for i in 0..nframes { let paddr = start_frame + i * PAGE_SIZE; meta_pages.push(paddr); } meta_pages }