use core::{fmt::Debug, marker::PhantomData, ops::Range};
use super::{
nr_subpage_per_huge, page::meta::MapTrackingStatus, page_prop::PageProperty, page_size, Paddr,
PagingConstsTrait, PagingLevel, Vaddr,
};
use crate::{
arch::mm::{PageTableEntry, PagingConsts},
Pod,
};
mod node;
use node::*;
pub mod cursor;
pub use cursor::{Cursor, CursorMut, PageTableItem};
#[cfg(ktest)]
mod test;
pub(crate) mod boot_pt;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PageTableError {
InvalidVaddrRange(Vaddr, Vaddr),
InvalidVaddr(Vaddr),
UnalignedVaddr,
}
pub trait PageTableMode: Clone + Debug + 'static {
const VADDR_RANGE: Range<Vaddr>;
fn covers(r: &Range<Vaddr>) -> bool {
Self::VADDR_RANGE.start <= r.start && r.end <= Self::VADDR_RANGE.end
}
}
#[derive(Clone, Debug)]
pub struct UserMode {}
impl PageTableMode for UserMode {
const VADDR_RANGE: Range<Vaddr> = 0..super::MAX_USERSPACE_VADDR;
}
#[derive(Clone, Debug)]
pub struct KernelMode {}
impl PageTableMode for KernelMode {
const VADDR_RANGE: Range<Vaddr> = super::KERNEL_VADDR_RANGE;
}
const fn nr_pte_index_bits<C: PagingConstsTrait>() -> usize {
nr_subpage_per_huge::<C>().ilog2() as usize
}
const fn pte_index<C: PagingConstsTrait>(va: Vaddr, level: PagingLevel) -> usize {
va >> (C::BASE_PAGE_SIZE.ilog2() as usize + nr_pte_index_bits::<C>() * (level as usize - 1))
& (nr_subpage_per_huge::<C>() - 1)
}
#[derive(Debug)]
pub struct PageTable<
M: PageTableMode,
E: PageTableEntryTrait = PageTableEntry,
C: PagingConstsTrait = PagingConsts,
> where
[(); C::NR_LEVELS as usize]:,
{
root: RawPageTableNode<E, C>,
_phantom: PhantomData<M>,
}
impl PageTable<UserMode> {
pub fn activate(&self) {
unsafe {
self.root.activate();
}
}
}
impl PageTable<KernelMode> {
pub fn create_user_page_table(&self) -> PageTable<UserMode> {
let mut root_node = self.root.clone_shallow().lock();
let mut new_node =
PageTableNode::alloc(PagingConsts::NR_LEVELS, MapTrackingStatus::NotApplicable);
const NR_PTES_PER_NODE: usize = nr_subpage_per_huge::<PagingConsts>();
for i in NR_PTES_PER_NODE / 2..NR_PTES_PER_NODE {
let root_entry = root_node.entry(i);
if !root_entry.is_none() {
let _ = new_node.entry(i).replace(root_entry.to_owned());
}
}
PageTable::<UserMode> {
root: new_node.into_raw(),
_phantom: PhantomData,
}
}
pub fn make_shared_tables(&self, root_index: Range<usize>) {
const NR_PTES_PER_NODE: usize = nr_subpage_per_huge::<PagingConsts>();
let start = root_index.start;
debug_assert!(start >= NR_PTES_PER_NODE / 2);
debug_assert!(start < NR_PTES_PER_NODE);
let end = root_index.end;
debug_assert!(end <= NR_PTES_PER_NODE);
let mut root_node = self.root.clone_shallow().lock();
for i in start..end {
let root_entry = root_node.entry(i);
if root_entry.is_none() {
let nxt_level = PagingConsts::NR_LEVELS - 1;
let is_tracked = if super::kspace::should_map_as_tracked(
i * page_size::<PagingConsts>(nxt_level),
) {
MapTrackingStatus::Tracked
} else {
MapTrackingStatus::Untracked
};
let node = PageTableNode::alloc(nxt_level, is_tracked);
let _ = root_entry.replace(Child::PageTable(node.into_raw()));
}
}
}
pub unsafe fn protect_flush_tlb(
&self,
vaddr: &Range<Vaddr>,
mut op: impl FnMut(&mut PageProperty),
) -> Result<(), PageTableError> {
let mut cursor = CursorMut::new(self, vaddr)?;
while let Some(range) = cursor.protect_next(vaddr.end - cursor.virt_addr(), &mut op) {
crate::arch::mm::tlb_flush_addr(range.start);
}
Ok(())
}
}
impl<'a, M: PageTableMode, E: PageTableEntryTrait, C: PagingConstsTrait> PageTable<M, E, C>
where
[(); C::NR_LEVELS as usize]:,
{
pub fn empty() -> Self {
PageTable {
root: PageTableNode::<E, C>::alloc(C::NR_LEVELS, MapTrackingStatus::NotApplicable)
.into_raw(),
_phantom: PhantomData,
}
}
pub(in crate::mm) unsafe fn first_activate_unchecked(&self) {
self.root.first_activate();
}
pub unsafe fn root_paddr(&self) -> Paddr {
self.root.paddr()
}
pub unsafe fn map(
&self,
vaddr: &Range<Vaddr>,
paddr: &Range<Paddr>,
prop: PageProperty,
) -> Result<(), PageTableError> {
self.cursor_mut(vaddr)?.map_pa(paddr, prop);
Ok(())
}
#[cfg(ktest)]
pub fn query(&self, vaddr: Vaddr) -> Option<(Paddr, PageProperty)> {
unsafe { page_walk::<E, C>(self.root_paddr(), vaddr) }
}
pub fn cursor_mut(
&'a self,
va: &Range<Vaddr>,
) -> Result<CursorMut<'a, M, E, C>, PageTableError> {
CursorMut::new(self, va)
}
pub fn cursor(&'a self, va: &Range<Vaddr>) -> Result<Cursor<'a, M, E, C>, PageTableError> {
Cursor::new(self, va)
}
pub unsafe fn shallow_copy(&self) -> Self {
PageTable {
root: self.root.clone_shallow(),
_phantom: PhantomData,
}
}
}
#[cfg(ktest)]
pub(super) unsafe fn page_walk<E: PageTableEntryTrait, C: PagingConstsTrait>(
root_paddr: Paddr,
vaddr: Vaddr,
) -> Option<(Paddr, PageProperty)> {
use super::paddr_to_vaddr;
let _guard = crate::trap::disable_local();
let mut cur_level = C::NR_LEVELS;
let mut cur_pte = {
let node_addr = paddr_to_vaddr(root_paddr);
let offset = pte_index::<C>(vaddr, cur_level);
unsafe { (node_addr as *const E).add(offset).read() }
};
while cur_level > 1 {
if !cur_pte.is_present() {
return None;
}
if cur_pte.is_last(cur_level) {
debug_assert!(cur_level <= C::HIGHEST_TRANSLATION_LEVEL);
break;
}
cur_level -= 1;
cur_pte = {
let node_addr = paddr_to_vaddr(cur_pte.paddr());
let offset = pte_index::<C>(vaddr, cur_level);
unsafe { (node_addr as *const E).add(offset).read() }
};
}
if cur_pte.is_present() {
Some((
cur_pte.paddr() + (vaddr & (page_size::<C>(cur_level) - 1)),
cur_pte.prop(),
))
} else {
None
}
}
pub trait PageTableEntryTrait: Clone + Copy + Debug + Default + Pod + Sized + Sync {
fn new_absent() -> Self {
Self::default()
}
fn is_present(&self) -> bool;
fn new_page(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self;
fn new_pt(paddr: Paddr) -> Self;
fn paddr(&self) -> Paddr;
fn prop(&self) -> PageProperty;
fn set_prop(&mut self, prop: PageProperty);
fn is_last(&self, level: PagingLevel) -> bool;
}