use crate::{
FrameAllocator, PageTableEntry, PagingError, PagingResult, PhysAddr, PteConfigOf, TableMeta,
VirtAddr, frame::Frame,
};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MapConfig<C> {
pub vaddr: VirtAddr,
pub paddr: PhysAddr,
pub size: usize,
pub pte: C,
pub allow_huge: bool,
pub flush: bool,
}
#[derive(Clone, Copy)]
pub struct MapRecursiveConfig<C> {
pub start_vaddr: VirtAddr,
pub start_paddr: PhysAddr,
pub end_vaddr: VirtAddr,
pub level: usize,
pub allow_huge: bool,
pub flush: bool,
pub pte_template: C,
}
#[derive(Clone, Copy)]
pub struct UnmapConfig {
pub start_vaddr: VirtAddr,
pub size: usize,
pub flush: bool,
}
#[derive(Clone, Copy)]
pub struct UnmapRecursiveConfig {
pub start_vaddr: VirtAddr,
pub end_vaddr: VirtAddr,
pub level: usize,
pub flush: bool,
}
impl<C> core::fmt::Debug for MapConfig<C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MapConfig")
.field("vaddr", &format_args!("{:#x}", self.vaddr.as_usize()))
.field("paddr", &format_args!("{:#x}", self.paddr.as_usize()))
.field("size", &format_args!("{:#x}", self.size))
.field("allow_huge", &self.allow_huge)
.field("flush", &self.flush)
.finish()
}
}
impl<T, A> Frame<T, A>
where
T: TableMeta,
A: FrameAllocator,
{
pub fn map_range_recursive(
&mut self,
config: MapRecursiveConfig<PteConfigOf<T>>,
) -> PagingResult<()> {
let mut vaddr = config.start_vaddr;
let mut paddr = config.start_paddr;
while vaddr < config.end_vaddr {
let index = Self::virt_to_index(vaddr, config.level);
let level_size = Self::level_size(config.level);
let remaining_size = config.end_vaddr - vaddr;
if config.allow_huge
&& config.level > 1
&& config.level <= T::MAX_BLOCK_LEVEL
&& level_size <= remaining_size
&& vaddr.as_usize().is_multiple_of(level_size)
&& paddr.as_usize().is_multiple_of(level_size)
{
let entries = self.as_slice_mut();
let pte_ref = &mut entries[index];
if !pte_ref.unused() {
return Err(PagingError::mapping_conflict(vaddr, paddr));
}
*pte_ref = T::P::new_page(paddr, config.pte_template, true);
if config.flush {
T::flush(Some(vaddr));
}
vaddr += level_size;
paddr += level_size;
continue;
}
if config.level == 1 {
let entries = self.as_slice_mut();
let pte_ref = &mut entries[index];
if !pte_ref.unused() {
return Err(PagingError::mapping_conflict(vaddr, paddr));
}
*pte_ref = T::P::new_page(paddr, config.pte_template, false);
if config.flush {
T::flush(Some(vaddr));
}
vaddr += T::PAGE_SIZE;
paddr += T::PAGE_SIZE;
continue;
}
let allocator = self.allocator.clone();
let current_pte = self.as_slice()[index];
let child_frame = if !current_pte.unused() {
if current_pte.huge(true) {
return Err(PagingError::mapping_conflict(
vaddr,
current_pte.paddr(true),
));
}
if !current_pte.present() {
return Err(PagingError::hierarchy_error(
"Non-present intermediate entry is not a leaf",
));
}
Frame::from_paddr(current_pte.paddr(true), allocator)
} else {
let new_frame = Frame::<T, A>::new(allocator)?;
let new_frame_paddr = new_frame.paddr;
let entries = self.as_slice_mut();
let pte_ref = &mut entries[index];
*pte_ref = T::P::new_table(new_frame_paddr);
new_frame
};
let current_entry_end = (vaddr.as_usize() / level_size)
.saturating_add(1)
.saturating_mul(level_size);
let next_level_vaddr =
VirtAddr::from_usize(current_entry_end.min(config.end_vaddr.as_usize()));
let mut child_frame = child_frame;
let child_config = MapRecursiveConfig {
start_vaddr: vaddr,
start_paddr: paddr,
end_vaddr: next_level_vaddr,
level: config.level - 1,
allow_huge: config.allow_huge,
flush: config.flush,
pte_template: config.pte_template,
};
child_frame.map_range_recursive(child_config)?;
let mapped_size = next_level_vaddr - vaddr;
vaddr = next_level_vaddr;
paddr += mapped_size;
}
Ok(())
}
pub fn unmap_range_recursive(&mut self, config: UnmapRecursiveConfig) -> PagingResult<bool> {
let mut vaddr = config.start_vaddr;
let mut can_reclaim = true;
let allocator = self.allocator.clone();
while vaddr < config.end_vaddr {
let index = Self::virt_to_index(vaddr, config.level);
let level_size = Self::level_size(config.level);
let remaining_size = config.end_vaddr - vaddr;
let entries = self.as_slice_mut();
let pte_ref = &mut entries[index];
if pte_ref.unused() {
vaddr += level_size.min(remaining_size);
continue;
}
if !pte_ref.present() {
pte_ref.clear();
if config.flush {
T::flush(Some(vaddr));
}
vaddr += level_size.min(remaining_size);
continue;
}
let is_huge = pte_ref.huge(config.level > 1);
if config.level == 1 || is_huge {
pte_ref.clear();
if config.flush {
T::flush(Some(vaddr));
}
vaddr += if is_huge { level_size } else { T::PAGE_SIZE };
continue;
}
let child_paddr = pte_ref.paddr(true);
let current_entry_end = ((vaddr.as_usize() / level_size) + 1) * level_size;
let next_level_vaddr =
VirtAddr::from_usize(current_entry_end.min(config.end_vaddr.as_usize()));
{
let mut child_frame: Frame<T, A> =
Frame::from_paddr(child_paddr, allocator.clone());
let child_config = UnmapRecursiveConfig {
start_vaddr: vaddr,
end_vaddr: next_level_vaddr,
level: config.level - 1,
flush: config.flush,
};
let child_can_reclaim = child_frame.unmap_range_recursive(child_config)?;
if child_can_reclaim {
pte_ref.clear();
allocator.dealloc_frame(child_paddr);
} else {
can_reclaim = false;
}
}
vaddr = next_level_vaddr;
}
if can_reclaim {
can_reclaim = self.is_frame_empty();
}
Ok(can_reclaim)
}
fn is_frame_empty(&self) -> bool {
let entries = self.as_slice();
for pte in entries {
if !pte.unused() {
return false;
}
}
true
}
}