libkernel: paging: add translate

Add a `translate` function which is much more powerful that `get_pte`.
It permits block mappings to be returned for a given VA.
This commit is contained in:
Matthew Leach
2026-05-13 16:19:52 +01:00
parent 3c0253e26a
commit 0791b01f38
5 changed files with 126 additions and 8 deletions

View File

@@ -219,6 +219,10 @@ macro_rules! define_descriptor {
let addr = reg.read(BlockPageFields::OUTPUT_ADDR);
Some(PA::from_value((addr << Self::MAP_SHIFT) as usize))
}
fn permissions(self) -> Option<PtePermissions> {
self.permissions()
}
}
}
)?

View File

@@ -195,6 +195,14 @@ macro_rules! impl_pa_mapper {
Some(self.address())
}
fn permissions(self) -> Option<PtePermissions> {
if (self.0 & $marker) != $marker {
return None;
}
Some(self.permissions())
}
}
}
)+

View File

@@ -4,18 +4,20 @@ use crate::{
error::{MapError, Result},
memory::{
PAGE_SIZE,
address::{TPA, VA},
address::{PA, TPA, VA},
paging::{
NullTlbInvalidator, PageTableEntry, PageTableMapper, PgTable, PgTableArray,
walk::{RecursiveWalker, WalkContext},
NullTlbInvalidator, PaMapper, PageTableEntry, PageTableMapper, PgTable, PgTableArray,
TableMapper,
permissions::PtePermissions,
walk::{RecursiveWalker, Translator, WalkContext},
},
region::VirtMemoryRegion,
region::{PhysMemoryRegion, VirtMemoryRegion},
},
};
use super::{
pg_descriptors::PTE,
pg_tables::{PML4Table, PTable},
pg_tables::{PDPTable, PML4Table, PTable},
};
impl RecursiveWalker<PTE> for PTable {
@@ -112,6 +114,70 @@ pub fn get_pte<PM: PageTableMapper>(
Ok(descriptor)
}
impl Translator for PML4Table {
fn translate<PM: PageTableMapper>(
table_pa: TPA<PgTableArray<Self>>,
va: VA,
ctx: &mut WalkContext<PM>,
) -> Result<Option<(PA, usize, PtePermissions)>> {
let desc = unsafe {
ctx.mapper
.with_page_table(table_pa, |pgtable| Self::from_ptr(pgtable).get_desc(va))?
};
match desc.next_table_address() {
Some(next_pa) => PDPTable::translate(next_pa, va, ctx),
None if desc.is_valid() => Err(MapError::InvalidDescriptor.into()),
None => Ok(None),
}
}
}
impl Translator for PTable {
fn translate<PM: PageTableMapper>(
table_pa: TPA<PgTableArray<Self>>,
va: VA,
ctx: &mut WalkContext<PM>,
) -> Result<Option<(PA, usize, PtePermissions)>> {
let desc = unsafe {
ctx.mapper
.with_page_table(table_pa, |pgtable| Self::from_ptr(pgtable).get_desc(va))?
};
match desc.mapped_address() {
Some(pa) => Ok(Some((
pa,
1 << Self::Descriptor::MAP_SHIFT,
desc.permissions(),
))),
None if desc.is_valid() => Err(MapError::InvalidDescriptor.into()),
None => Ok(None),
}
}
}
/// Translates the VA into a physical region plus an offset and permissions.
pub fn translate<PM: PageTableMapper>(
pml4_table: TPA<PgTableArray<PML4Table>>,
va: VA,
mapper: &mut PM,
) -> Result<Option<(PhysMemoryRegion, usize, PtePermissions)>> {
let mut walk_ctx = WalkContext {
mapper,
// Safe to not invalidate the TLB, as we are not modifying any PTEs.
invalidator: &NullTlbInvalidator {},
};
if let Some((pa, blk_sz, perms)) = PML4Table::translate(pml4_table, va, &mut walk_ctx)? {
debug_assert!(blk_sz.is_power_of_two());
let offset = va.value() & (blk_sz - 1);
Ok(Some((PhysMemoryRegion::new(pa, blk_sz), offset, perms)))
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -70,6 +70,9 @@ pub trait PaMapper: PageTableEntry {
/// Return the mapped physical address.
fn mapped_address(self) -> Option<PA>;
/// Return the permissions set on the PTE.
fn permissions(self) -> Option<PtePermissions>;
}
/// Trait representing a single level of the page table hierarchy.

View File

@@ -3,14 +3,14 @@
use crate::{
error::MapError,
memory::{
address::{TPA, VA},
address::{PA, TPA, VA},
region::VirtMemoryRegion,
},
};
use super::{
PageTableEntry, PageTableMapper, PgTable, PgTableArray, TLBInvalidator, TableMapper,
TableMapperTable,
PaMapper, PageTableEntry, PageTableMapper, PgTable, PgTableArray, TLBInvalidator, TableMapper,
TableMapperTable, permissions::PtePermissions,
};
/// A collection of context required to modify page tables.
@@ -96,3 +96,40 @@ where
Ok(())
}
}
pub(crate) trait Translator: PgTable + Sized {
fn translate<PM: PageTableMapper>(
table_pa: TPA<PgTableArray<Self>>,
va: VA,
ctx: &mut WalkContext<PM>,
) -> crate::error::Result<Option<(PA, usize, PtePermissions)>>;
}
impl<T> Translator for T
where
T: TableMapperTable,
T::Descriptor: PaMapper,
<T::Descriptor as TableMapper>::NextLevel: Translator,
{
fn translate<PM: PageTableMapper>(
table_pa: TPA<PgTableArray<Self>>,
va: VA,
ctx: &mut WalkContext<PM>,
) -> crate::error::Result<Option<(PA, usize, PtePermissions)>> {
let desc = unsafe {
ctx.mapper
.with_page_table(table_pa, |pgtable| T::from_ptr(pgtable).get_desc(va))?
};
if let Some(next_pa) = desc.next_table_address() {
<T::Descriptor as TableMapper>::NextLevel::translate(next_pa, va, ctx)
} else if let Some(block_pa) = desc.mapped_address() {
let block_size = 1usize << T::Descriptor::MAP_SHIFT;
Ok(Some((block_pa, block_size, desc.permissions().unwrap())))
} else if desc.is_valid() {
Err(MapError::InvalidDescriptor)?
} else {
Ok(None)
}
}
}