use core::ops::{Deref, DerefMut, Range};
use ax_memory_addr::MemoryAddr;
use crate::{
FrameAllocator, PageTableEntry, PagingError, PagingResult, PhysAddr, PteConfigOf, TableMeta,
VirtAddr,
frame::Frame,
map::{MapConfig, MapRecursiveConfig, UnmapConfig, UnmapRecursiveConfig},
walk::{PageTableWalker, WalkConfig},
};
const TARGETED_FLUSH_LIMIT: usize = 32;
pub struct PageTable<T: TableMeta, A: FrameAllocator> {
inner: PageTableRef<T, A>,
#[cfg(feature = "copy-from")]
borrowed_root_entries: Option<Range<usize>>,
}
impl<T: TableMeta, A: FrameAllocator> PageTable<T, A> {
pub const VALID_BITS: usize = Frame::<T, A>::PT_VALID_BITS;
pub fn new(allocator: A) -> PagingResult<Self> {
let inner = unsafe { PageTableRef::new(allocator) }?;
Ok(Self {
inner,
#[cfg(feature = "copy-from")]
borrowed_root_entries: None,
})
}
pub const fn root_paddr(&self) -> PhysAddr {
self.inner.root.paddr
}
pub fn clone_missing_root_entries_from(
&mut self,
other: &PageTableRef<T, A>,
start_vaddr: VirtAddr,
size: usize,
) -> PagingResult {
let Some(entries) = Self::root_entry_range(start_vaddr, size)? else {
return Ok(());
};
let root_level = Frame::<T, A>::PT_LEVEL;
let mut changed = false;
for index in entries {
changed |= self
.inner
.root
.clone_entry_from(&other.root, index, root_level)?;
}
if changed {
T::flush(None);
}
Ok(())
}
#[cfg(feature = "copy-from")]
pub unsafe fn share_root_entries_from(
&mut self,
other: &Self,
start_vaddr: VirtAddr,
size: usize,
) -> PagingResult {
if size == 0 {
return Ok(());
}
if self.borrowed_root_entries.is_some() {
return Err(PagingError::hierarchy_error(
"Page table already contains shared root entries",
));
}
let Some(entries) = Self::root_entry_range(start_vaddr, size)? else {
return Ok(());
};
let root_level = Frame::<T, A>::PT_LEVEL;
for index in entries.clone() {
self.inner.root.dealloc_entry_recursive(index, root_level);
self.inner.root.as_slice_mut()[index] = other.inner.root.as_slice()[index];
}
self.borrowed_root_entries = Some(entries);
T::flush(None);
Ok(())
}
fn root_entry_range(start_vaddr: VirtAddr, size: usize) -> PagingResult<Option<Range<usize>>> {
if size == 0 {
return Ok(None);
}
let end_vaddr = start_vaddr
.as_usize()
.checked_add(size)
.ok_or_else(|| PagingError::address_overflow("root_entry_range"))?;
let root_level = Frame::<T, A>::PT_LEVEL;
let start_index = Frame::<T, A>::virt_to_index(start_vaddr, root_level);
let end_index =
Frame::<T, A>::virt_to_index(VirtAddr::from_usize(end_vaddr - 1), root_level) + 1;
if start_index >= end_index {
return Err(PagingError::invalid_range(
"Range must be contiguous in the root page table",
));
}
Ok(Some(start_index..end_index))
}
#[cfg(feature = "copy-from")]
fn detach_borrowed_root_entries(&mut self) {
let Some(entries) = self.borrowed_root_entries.take() else {
return;
};
for index in entries {
self.inner.root.as_slice_mut()[index].clear();
}
}
}
impl<T: TableMeta, A: FrameAllocator> Drop for PageTable<T, A> {
fn drop(&mut self) {
#[cfg(feature = "copy-from")]
self.detach_borrowed_root_entries();
unsafe {
self.deallocate();
}
}
}
impl<T: TableMeta, A: FrameAllocator> Deref for PageTable<T, A> {
type Target = PageTableRef<T, A>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T: TableMeta, A: FrameAllocator> DerefMut for PageTable<T, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Clone, Copy)]
pub struct PageTableRef<T: TableMeta, A: FrameAllocator> {
pub root: Frame<T, A>,
}
impl<T: TableMeta, A: FrameAllocator> core::fmt::Debug for PageTableRef<T, A>
where
T::P: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PageTable")
.field(
"root_paddr",
&format_args!("{:#x}", self.root.paddr.as_usize()),
)
.field("table_levels", &T::LEVEL_BITS.len())
.field("max_block_level", &T::MAX_BLOCK_LEVEL)
.field("page_size", &format_args!("{:#x}", T::PAGE_SIZE))
.finish()
}
}
impl<T: TableMeta, A: FrameAllocator> PageTableRef<T, A> {
pub unsafe fn new(allocator: A) -> PagingResult<Self> {
let root = Frame::new_root(allocator)?;
Ok(Self { root })
}
pub fn from_paddr(paddr: PhysAddr, allocator: A) -> Self {
let root = Frame::from_root_paddr(paddr, allocator);
Self { root }
}
pub fn map_page(
&mut self,
vaddr: VirtAddr,
paddr: PhysAddr,
page_size: usize,
config: PteConfigOf<T>,
) -> PagingResult {
let Some(level) = Frame::<T, A>::level_for_page_size(page_size) else {
return Err(PagingError::invalid_size(
"Page size is not represented by the page-table levels",
));
};
if level > 1 && level > T::MAX_BLOCK_LEVEL {
return Err(PagingError::invalid_size(
"Page size exceeds the architecture's block-mapping level",
));
}
self.map(&MapConfig {
vaddr: vaddr.align_down(page_size),
paddr: paddr.align_down(page_size),
size: page_size,
pte: config,
allow_huge: level > 1,
flush: true,
})
}
pub fn map_region(
&mut self,
start_vaddr: VirtAddr,
get_paddr: impl Fn(VirtAddr) -> PhysAddr,
size: usize,
config: PteConfigOf<T>,
allow_huge: bool,
) -> PagingResult {
if size == 0 {
return Err(PagingError::invalid_size("Region size cannot be zero"));
}
if !start_vaddr.as_usize().is_multiple_of(T::PAGE_SIZE)
|| !size.is_multiple_of(T::PAGE_SIZE)
{
return Err(PagingError::alignment_error(
"Region start and size must be base-page aligned",
));
}
start_vaddr.as_usize().checked_add(size).ok_or_else(|| {
PagingError::address_overflow("Virtual address overflow in map_region")
})?;
self.validate_address_width(start_vaddr, size, "map_region")?;
let mut offset = 0;
let mut flush_addrs = heapless::Vec::<VirtAddr, TARGETED_FLUSH_LIMIT>::new();
let mut full_flush = false;
let result = loop {
if offset >= size {
break Ok(());
}
let vaddr = start_vaddr + offset;
let paddr = get_paddr(vaddr);
let remaining = size - offset;
let page_size = largest_page_size::<T, A>(vaddr, paddr, remaining, allow_huge);
if let Err(err) = self.map(&MapConfig {
vaddr: vaddr.align_down(page_size),
paddr: paddr.align_down(page_size),
size: page_size,
pte: config,
allow_huge: page_size > T::PAGE_SIZE,
flush: false,
}) {
let rollback_result = if offset == 0 {
Ok(())
} else {
self.unmap_with_config(&UnmapConfig {
start_vaddr,
size: offset,
flush: false,
})
};
break match rollback_result {
Ok(()) => Err(err),
Err(rollback_err) => Err(rollback_err),
};
}
if !full_flush && flush_addrs.push(vaddr).is_err() {
full_flush = true;
flush_addrs.clear();
}
offset += page_size;
};
if full_flush {
T::flush(None);
} else {
for vaddr in flush_addrs {
T::flush(Some(vaddr));
}
}
result
}
pub fn unmap_page(
&mut self,
vaddr: VirtAddr,
) -> PagingResult<(PhysAddr, PteConfigOf<T>, usize)> {
let (pte, level) = self
.root
.find_occupied_leaf(vaddr, Frame::<T, A>::PT_LEVEL)?;
let page_size = Frame::<T, A>::level_size(level);
let is_dir = level > 1;
let paddr = pte.paddr(is_dir);
let config = pte.config(is_dir);
self.unmap_with_config(&UnmapConfig {
start_vaddr: vaddr.align_down(page_size),
size: page_size,
flush: true,
})?;
Ok((paddr, config, page_size))
}
pub fn protect_page(&mut self, vaddr: VirtAddr, config: PteConfigOf<T>) -> PagingResult<usize> {
let page_size = self
.root
.protect_recursive(vaddr, config, Frame::<T, A>::PT_LEVEL)?;
T::flush(Some(vaddr));
Ok(page_size)
}
pub fn protect_region(
&mut self,
start_vaddr: VirtAddr,
size: usize,
config: PteConfigOf<T>,
) -> PagingResult {
let end = start_vaddr
.as_usize()
.checked_add(size)
.ok_or_else(|| PagingError::address_overflow("protect_region"))?;
let mut vaddr = start_vaddr;
while vaddr.as_usize() < end {
match self.protect_page(vaddr, config) {
Ok(page_size) => vaddr += page_size,
Err(PagingError::NotMapped) => vaddr += T::PAGE_SIZE,
Err(err) => return Err(err),
}
}
Ok(())
}
pub fn remap_page(
&mut self,
vaddr: VirtAddr,
paddr: PhysAddr,
config: PteConfigOf<T>,
) -> PagingResult<usize> {
let page_size = self
.root
.remap_recursive(vaddr, paddr, config, Frame::<T, A>::PT_LEVEL)?;
T::flush(Some(vaddr));
Ok(page_size)
}
pub fn query(&self, vaddr: VirtAddr) -> PagingResult<(PhysAddr, PteConfigOf<T>, usize)> {
let (paddr, pte, level) = self.translate_with_level(vaddr)?;
Ok((
paddr,
pte.config(level > 1),
Frame::<T, A>::level_size(level),
))
}
pub fn map(&mut self, config: &MapConfig<PteConfigOf<T>>) -> PagingResult {
self.validate_map_config(config)?;
if config.vaddr.as_usize().checked_add(config.size).is_none()
|| config.paddr.as_usize().checked_add(config.size).is_none()
{
return Err(PagingError::address_overflow(
"Virtual or physical address overflow",
));
}
self.validate_address_width(config.vaddr, config.size, "map")?;
self.root.map_range_recursive(MapRecursiveConfig {
start_vaddr: config.vaddr,
start_paddr: config.paddr,
end_vaddr: config.vaddr + config.size,
level: Frame::<T, A>::PT_LEVEL,
allow_huge: config.allow_huge,
flush: config.flush,
pte_template: config.pte,
})?;
Ok(())
}
pub fn unmap(&mut self, start_vaddr: VirtAddr, size: usize) -> PagingResult<()> {
self.validate_unmap_params(start_vaddr, size)?;
let end_vaddr: VirtAddr = match start_vaddr.as_usize().checked_add(size) {
Some(end) => VirtAddr::from_usize(end),
None => {
return Err(PagingError::address_overflow(
"Virtual address overflow in unmap",
));
}
};
self.validate_address_width(start_vaddr, size, "unmap")?;
self.root.unmap_range_recursive(UnmapRecursiveConfig {
start_vaddr,
end_vaddr,
level: Frame::<T, A>::PT_LEVEL,
flush: true, })?;
Ok(())
}
pub fn unmap_with_config(&mut self, config: &UnmapConfig) -> PagingResult<()> {
self.validate_unmap_params(config.start_vaddr, config.size)?;
let end_vaddr = match config.start_vaddr.as_usize().checked_add(config.size) {
Some(end) => VirtAddr::from_usize(end),
None => {
return Err(PagingError::address_overflow(
"Virtual address overflow in unmap_with_config",
));
}
};
self.validate_address_width(config.start_vaddr, config.size, "unmap_with_config")?;
self.root.unmap_range_recursive(UnmapRecursiveConfig {
start_vaddr: config.start_vaddr,
end_vaddr,
level: Frame::<T, A>::PT_LEVEL,
flush: config.flush,
})?;
Ok(())
}
fn validate_unmap_params(&self, start_vaddr: VirtAddr, size: usize) -> PagingResult<()> {
if size == 0 {
return Err(PagingError::invalid_size("Size cannot be zero in unmap"));
}
if !start_vaddr.as_usize().is_multiple_of(T::PAGE_SIZE) {
return Err(PagingError::alignment_error(
"Start virtual address not page aligned in unmap",
));
}
if !size.is_multiple_of(T::PAGE_SIZE) {
return Err(PagingError::alignment_error(
"Size not page aligned in unmap",
));
}
Ok(())
}
pub fn walk_all(&self, config: WalkConfig) -> PageTableWalker<'_, T, A> {
PageTableWalker::new(self, config)
}
pub fn walk(
&self,
start_vaddr: VirtAddr,
end_vaddr: VirtAddr,
) -> impl Iterator<Item = crate::walk::PteInfo<T::P>> + '_ {
let config = WalkConfig {
start_vaddr,
end_vaddr,
};
PageTableWalker::new(self, config).filter(|p| p.pte.present())
}
pub fn walk_valid(&self) -> impl Iterator<Item = crate::walk::PteInfo<T::P>> + '_ {
self.walk(0.into(), usize::MAX.into())
.filter(|p| p.pte.present() && p.is_final_mapping)
}
fn validate_map_config(&self, config: &MapConfig<PteConfigOf<T>>) -> PagingResult {
if config.size == 0 {
return Err(PagingError::invalid_size("Size cannot be zero"));
}
if !config.vaddr.as_usize().is_multiple_of(T::PAGE_SIZE) {
return Err(PagingError::alignment_error(
"Virtual address not page aligned",
));
}
if !config.paddr.as_usize().is_multiple_of(T::PAGE_SIZE) {
return Err(PagingError::alignment_error(
"Physical address not page aligned",
));
}
Ok(())
}
fn validate_address_width(
&self,
start_vaddr: VirtAddr,
size: usize,
operation: &'static str,
) -> PagingResult<()> {
if !T::STRICT_ADDRESS_WIDTH {
return Ok(());
}
let Some(end) = start_vaddr.as_usize().checked_add(size) else {
return Err(PagingError::address_overflow(
"Virtual address range overflow",
));
};
let last = end.saturating_sub(1);
if !Self::is_addr_in_width(start_vaddr.as_usize()) || !Self::is_addr_in_width(last) {
return Err(PagingError::address_overflow(operation));
}
Ok(())
}
pub const fn page_size() -> usize {
T::PAGE_SIZE
}
pub const fn table_levels() -> usize {
T::LEVEL_BITS.len()
}
pub const fn valid_bits() -> usize {
Frame::<T, A>::PT_VALID_BITS
}
fn is_addr_in_width(addr: usize) -> bool {
let valid_bits = Self::valid_bits();
if valid_bits >= usize::BITS as usize {
return true;
}
addr < (1usize << valid_bits)
}
pub unsafe fn destroy(mut self) {
self.root.deallocate_recursive(Frame::<T, A>::PT_LEVEL);
}
pub unsafe fn deallocate(&mut self) {
self.root.deallocate_recursive(Frame::<T, A>::PT_LEVEL);
}
pub fn deallocate_range(&mut self, start_vaddr: VirtAddr, end_vaddr: VirtAddr) -> PagingResult {
if start_vaddr >= end_vaddr {
return Err(PagingError::invalid_range(
"Start address must be less than end address",
));
}
Ok(())
}
pub fn translate(&self, vaddr: VirtAddr) -> PagingResult<(PhysAddr, T::P)> {
self.translate_with_level(vaddr)
.map(|(phys_addr, pte, _)| (phys_addr, pte))
}
pub fn translate_with_level(&self, vaddr: VirtAddr) -> PagingResult<(PhysAddr, T::P, usize)> {
if T::STRICT_ADDRESS_WIDTH && !Self::is_addr_in_width(vaddr.as_usize()) {
return Err(PagingError::address_overflow("translate"));
}
let (pte, level) = self
.root
.translate_recursive_with_level(vaddr, Frame::<T, A>::PT_LEVEL)?;
let is_huge = pte.huge(level > 1);
let pte_paddr = pte.paddr(level > 1);
let (phys_addr, _) = if is_huge {
let level_size = Frame::<T, A>::level_size(level);
let offset_in_page = vaddr.as_usize() % level_size;
(
PhysAddr::from_usize(pte_paddr.as_usize() + offset_in_page),
level_size,
)
} else {
let offset_in_page = vaddr.as_usize() % T::PAGE_SIZE;
(
PhysAddr::from_usize(pte_paddr.as_usize() + offset_in_page),
T::PAGE_SIZE,
)
};
Ok((phys_addr, pte, level))
}
pub fn translate_phys(&self, vaddr: VirtAddr) -> PagingResult<PhysAddr> {
let (p, _) = self.translate(vaddr)?;
Ok(p)
}
pub fn is_mapped(&self, vaddr: VirtAddr) -> bool {
self.translate(vaddr).is_ok()
}
pub fn root_paddr(&self) -> crate::PhysAddr {
self.root.paddr
}
}
fn largest_page_size<T: TableMeta, A: FrameAllocator>(
vaddr: VirtAddr,
paddr: PhysAddr,
remaining: usize,
allow_huge: bool,
) -> usize {
if allow_huge {
let max_level = Frame::<T, A>::PT_LEVEL.min(T::MAX_BLOCK_LEVEL);
for level in (2..=max_level).rev() {
let page_size = Frame::<T, A>::level_size(level);
if vaddr.is_aligned(page_size) && paddr.is_aligned(page_size) && remaining >= page_size
{
return page_size;
}
}
}
T::PAGE_SIZE
}