libkernel: memory: paging: make PageTableEntry contain MAP_SHIFT

We currently define the MAP_SHIFT for a page-table level in two places,
`TableMapper` and `PgTable`. Store only one shift trait constant value
in the `PageTableEntry` trait.
This commit is contained in:
Matthew Leach
2026-04-15 16:34:52 +01:00
parent a211d82477
commit b9dc420aee
6 changed files with 55 additions and 65 deletions

View File

@@ -36,12 +36,12 @@ macro_rules! define_descriptor {
(
$(#[$outer:meta])*
$name:ident,
shift: $shift:literal,
// Optional: Implement TableMapper if this section is present
$( table: $table_bits:literal, )?
// Optional: Implement PaMapper if this section is present
$( map: {
bits: $map_bits:literal,
shift: $tbl_shift:literal,
oa_len: $oa_len:literal,
},
)?
@@ -54,6 +54,7 @@ macro_rules! define_descriptor {
impl PageTableEntry for $name {
type RawDescriptor = u64;
const INVALID: u64 = 0;
const MAP_SHIFT: usize = $shift;
fn is_valid(self) -> bool { (self.0 & 0b11) != 0 }
fn as_raw(self) -> Self::RawDescriptor { self.0 }
@@ -91,7 +92,7 @@ macro_rules! define_descriptor {
XN OFFSET(54) NUMBITS(1) [ NotExecutable = 1, Executable = 0 ],
// Software defined bit
COW OFFSET(55) NUMBITS(1) [ CowShared = 1, NotCowShared = 0 ],
OUTPUT_ADDR OFFSET($tbl_shift) NUMBITS($oa_len) []
OUTPUT_ADDR OFFSET($shift) NUMBITS($oa_len) []
]
];
}
@@ -163,17 +164,16 @@ macro_rules! define_descriptor {
impl PaMapper for $name {
type MemoryType = MemoryType;
const MAP_SHIFT: usize = $tbl_shift;
fn could_map(region: PhysMemoryRegion, va: VA) -> bool {
let is_aligned = |addr: usize| (addr & ((1 << $tbl_shift) - 1)) == 0;
let is_aligned = |addr: usize| (addr & ((1 << Self::MAP_SHIFT) - 1)) == 0;
is_aligned(region.start_address().value())
&& is_aligned(va.value())
&& region.size() >= (1 << $tbl_shift)
&& region.size() >= (1 << Self::MAP_SHIFT)
}
fn new_map_pa(page_address: PA, memory_type: MemoryType, perms: PtePermissions) -> Self {
let is_aligned = |addr: usize| (addr & ((1 << $tbl_shift) - 1)) == 0;
let is_aligned = |addr: usize| (addr & ((1 << Self::MAP_SHIFT) - 1)) == 0;
if !is_aligned(page_address.value()) {
panic!("Cannot map non-aligned physical address");
}
@@ -181,7 +181,7 @@ macro_rules! define_descriptor {
let reg = InMemoryRegister::new(0);
use [<$name Fields>]::BlockPageFields;
reg.modify(BlockPageFields::OUTPUT_ADDR.val((page_address.value() >> $tbl_shift) as u64)
reg.modify(BlockPageFields::OUTPUT_ADDR.val((page_address.value() >> Self::MAP_SHIFT) as u64)
+ BlockPageFields::AF::Accessed);
match memory_type {
@@ -209,7 +209,7 @@ macro_rules! define_descriptor {
let reg = InMemoryRegister::new(self.0);
let addr = reg.read(BlockPageFields::OUTPUT_ADDR);
Some(PA::from_value((addr << $tbl_shift) as usize))
Some(PA::from_value((addr << Self::MAP_SHIFT) as usize))
}
}
}
@@ -220,16 +220,17 @@ macro_rules! define_descriptor {
define_descriptor!(
/// A Level 0 descriptor. Can only be an invalid or table descriptor.
L0Descriptor,
shift: 39,
table: 0b11,
);
define_descriptor!(
/// A Level 1 descriptor. Can be a block, table, or invalid descriptor.
L1Descriptor,
shift: 30,
table: 0b11,
map: {
bits: 0b01, // L1 Block descriptor has bits[1:0] = 01
shift: 30, // Maps a 1GiB block
oa_len: 18, // Output address length for 48-bit PA
},
);
@@ -237,10 +238,10 @@ define_descriptor!(
define_descriptor!(
/// A Level 2 descriptor. Can be a block, table, or invalid descriptor.
L2Descriptor,
shift: 21,
table: 0b11,
map: {
bits: 0b01, // L2 Block descriptor has bits[1:0] = 01
shift: 21, // Maps a 2MiB block
oa_len: 27, // Output address length for 48-bit PA
},
);
@@ -248,10 +249,10 @@ define_descriptor!(
define_descriptor!(
/// A Level 3 descriptor. Can be a page or invalid descriptor.
L3Descriptor,
shift: 12,
// Note: No 'table' capability at L3.
map: {
bits: 0b11, // L3 Page descriptor has bits[1:0] = 11
shift: 12, // Maps a 4KiB page
oa_len: 36, // Output address length for 48-bit PA
},
);

View File

@@ -30,7 +30,7 @@ pub(super) trait TableMapperTable: PgTable<Descriptor: TableMapper> + Clone + Co
}
macro_rules! impl_pgtable {
($(#[$outer:meta])* $table:ident, $shift:expr, $desc_type:ident) => {
($(#[$outer:meta])* $table:ident, $desc_type:ident) => {
#[derive(Clone, Copy)]
$(#[$outer])*
pub struct $table {
@@ -38,7 +38,6 @@ macro_rules! impl_pgtable {
}
impl PgTable for $table {
const SHIFT: usize = $shift;
type Descriptor = $desc_type;
fn from_ptr(ptr: TVA<PgTableArray<Self>>) -> Self {
@@ -73,25 +72,25 @@ macro_rules! impl_pgtable {
}
impl_pgtable!(/// Level 0 page table (512 GiB per entry).
L0Table, 39, L0Descriptor);
L0Table, L0Descriptor);
impl TableMapperTable for L0Table {
type NextLevel = L1Table;
}
impl_pgtable!(/// Level 1 page table (1 GiB per entry).
L1Table, 30, L1Descriptor);
L1Table, L1Descriptor);
impl TableMapperTable for L1Table {
type NextLevel = L2Table;
}
impl_pgtable!(/// Level 2 page table (2 MiB per entry).
L2Table, 21, L2Descriptor);
L2Table, L2Descriptor);
impl TableMapperTable for L2Table {
type NextLevel = L3Table;
}
impl_pgtable!(/// Level 3 page table (4 KiB per entry).
L3Table, 12, L3Descriptor);
L3Table, L3Descriptor);
/// Describes the attributes of a memory range to be mapped.
pub struct MapAttributes {

View File

@@ -55,13 +55,13 @@ where
PM: PageTableMapper,
F: FnMut(VA, L3Descriptor) -> L3Descriptor,
{
let table_coverage = 1 << T::SHIFT;
let table_coverage = 1 << T::Descriptor::MAP_SHIFT;
let start_idx = Self::pg_index(region.start_address());
let end_idx = Self::pg_index(region.end_address_inclusive());
// Calculate the base address of the *entire* table.
let table_base_va = region.start_address().align(1 << (T::SHIFT + 9));
let table_base_va = region.start_address().align(1 << (T::Descriptor::MAP_SHIFT + 9));
for idx in start_idx..=end_idx {
let entry_va = table_base_va.add_bytes(idx * table_coverage);
@@ -269,7 +269,7 @@ mod tests {
// This VA range will cross an L2 entry boundary, forcing a walk over
// two L3 tables. L2 entry covers 2MiB. Let's map a region around a 2MiB
// boundary.
let l2_boundary = 1 << L2Table::SHIFT; // 2MiB
let l2_boundary = 1 << <L2Table as PgTable>::Descriptor::MAP_SHIFT; // 2MiB
let va_start = VA::from_value(l2_boundary - 5 * PAGE_SIZE);
let num_pages = 10;
let region = VirtMemoryRegion::new(va_start, num_pages * PAGE_SIZE);
@@ -302,7 +302,7 @@ mod tests {
fn walk_region_spanning_l2_tables() {
let mut harness = TestHarness::new(6);
// This VA range will cross an L1 entry boundary, forcing a walk over two L2 tables.
let l1_boundary = 1 << L1Table::SHIFT; // 1GiB
let l1_boundary = 1 << <L1Table as PgTable>::Descriptor::MAP_SHIFT; // 1GiB
let va_start = VA::from_value(l1_boundary - 5 * PAGE_SIZE);
let num_pages = 10;
let region = VirtMemoryRegion::new(va_start, num_pages * PAGE_SIZE);

View File

@@ -27,11 +27,7 @@ macro_rules! define_descriptor {
(
$(#[$outer:meta])*
$name:ident,
// Optional: Implement PaMapper if this section is present
$( map: {
shift: $tbl_shift:literal,
},
)?
shift: $shift:literal
) => {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -41,6 +37,7 @@ macro_rules! define_descriptor {
impl PageTableEntry for $name {
type RawDescriptor = u64;
const INVALID: Self::RawDescriptor = 0;
const MAP_SHIFT: usize = $shift;
fn is_valid(self) -> bool { (self.0 & 0b1) != 0 }
fn as_raw(self) -> u64 { self.0 }
fn from_raw(v: u64) -> Self { Self(v) }
@@ -117,12 +114,36 @@ macro_rules! define_descriptor {
}
}
}
}
}
define_descriptor!(
/// A page-map level 4 entry descriptor. Can only be an invalid or table
/// descriptor.
PML4E, shift: 39
);
define_descriptor!(
/// A page directory pointer entry. Can be a block, table, or invalid descriptor.
PDPE, shift: 30
);
define_descriptor!(
/// A page directory entry. Can be a block, table, or invalid descriptor.
PDE, shift: 21
);
define_descriptor!(
/// A page table entry. Can be a page or invalid descriptor.
PTE, shift: 12
);
macro_rules! impl_pa_mapper {
($($name:ident),+ $(,)?) => {
$(
paste! {
impl PaMapper for $name {
type MemoryType = MemoryType;
const MAP_SHIFT: usize = $tbl_shift;
fn could_map(region: PhysMemoryRegion, va: VA) -> bool {
let is_aligned = |addr: usize| (addr & ((1 << Self::MAP_SHIFT) - 1)) == 0;
@@ -175,38 +196,10 @@ macro_rules! define_descriptor {
}
}
)?
};
}
}
define_descriptor!(
/// A page-map level 4 entry descriptor. Can only be an invalid or table
/// descriptor.
PML4E,
);
define_descriptor!(
/// A page directory pointer entry. Can be a block, table, or invalid descriptor.
PDPE,
map: {
shift: 30, // Maps a 1GiB block
},
);
define_descriptor!(
/// A page directory entry. Can be a block, table, or invalid descriptor.
PDE,
map: {
shift: 21, // Maps a 2MiB block
},
);
define_descriptor!(
/// A page table entry. Can be a page or invalid descriptor.
PTE,
map: {
shift: 12, // Maps a 4KiB page
},
);
impl_pa_mapper!(PDPE, PDE, PTE);
macro_rules! impl_table_mapper {
($($name:ident),+ $(,)?) => {

View File

@@ -18,6 +18,9 @@ pub trait PageTableEntry: Sized + Copy + Clone {
/// The raw value for an invalid (not present) descriptor.
const INVALID: Self::RawDescriptor;
/// The VA shift used for indexing at this descriptor level.
const MAP_SHIFT: usize;
/// Returns `true` if the entry is valid (i.e., not an Invalid/Fault entry).
fn is_valid(self) -> bool;
@@ -48,9 +51,6 @@ pub trait PaMapper: PageTableEntry {
/// The memory attribute type for this descriptor's architecture.
type MemoryType: Copy;
/// How many bytes this descriptor type maps.
const MAP_SHIFT: usize;
/// Constructs a new valid page descriptor that maps a physical address.
fn new_map_pa(page_address: PA, memory_type: Self::MemoryType, perms: PtePermissions) -> Self;
@@ -97,9 +97,6 @@ pub trait PgTable: Clone + Copy {
/// Bitmask used to extract the page table index from a shifted virtual address.
const LEVEL_MASK: usize = Self::DESCRIPTORS_PER_PAGE - 1;
/// Bit shift used to extract the index for this page table level.
const SHIFT: usize;
/// The descriptor (page table entry) type for this level.
type Descriptor: PageTableEntry;
@@ -111,7 +108,7 @@ pub trait PgTable: Clone + Copy {
/// Compute the index into this page table from a virtual address.
fn pg_index(va: VA) -> usize {
(va.value() >> Self::SHIFT) & Self::LEVEL_MASK
(va.value() >> Self::Descriptor::MAP_SHIFT) & Self::LEVEL_MASK
}
/// Get the descriptor for a given virtual address.

View File

@@ -102,7 +102,7 @@ impl Fixmap {
);
L2Table::from_ptr(TVA::from_ptr(&mut self.l2 as *mut _)).set_desc(
VA::from_value(FIXMAP_BASE.value() + (1 << L2Table::SHIFT)),
VA::from_value(FIXMAP_BASE.value() + (1 << <L2Table as PgTable>::Descriptor::MAP_SHIFT)),
L2Descriptor::new_next_table(ksym_pa!(self.l3[1])),
&invalidator,
);