Merge pull request #301 from hexagonal-sun/libkernel-yet-more-fixes

Libkernel yet more fixes
This commit is contained in:
Ashwin Naren
2026-05-13 13:01:17 -07:00
committed by GitHub
7 changed files with 239 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

@@ -41,6 +41,8 @@ pub struct RegionList {
regions: *mut PhysMemoryRegion,
}
const NULL_PAGE_RGN: PhysMemoryRegion = PhysMemoryRegion::new(PA::null(), PAGE_SIZE);
impl RegionList {
/// Returns `true` if the list contains no regions.
pub fn is_empty(&self) -> bool {
@@ -248,6 +250,14 @@ impl<T: AddressTranslator<()>> Smalloc<T> {
.find_allocation_location(size, align)
.ok_or(KernelError::NoMemory)?;
if address.is_null() {
// Reserve the zero page and try again. We never want to return
// an allocation of NULL since it trips up UB checks.
self.res.insert_region(NULL_PAGE_RGN);
return self.alloc(size, align);
}
// Allocation fits and doesn't overlap any reservation
self.res.insert_region(PhysMemoryRegion::new(address, size));

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)
}
}
}

View File

@@ -59,6 +59,11 @@ impl<T: MemKind> MemoryRegion<T> {
}
}
/// Returns `true` if this memory region is empty.
pub fn is_empty(self) -> bool {
self.size == 0
}
/// Create a memory region from a start and end address.
///
/// The size is calculated as `end - start`. No alignment is enforced.
@@ -71,6 +76,27 @@ impl<T: MemKind> MemoryRegion<T> {
}
}
/// Cap the size of the region. If `max_size < self.size`, the region is
/// shrunk.
pub fn cap_size(self, max_size: usize) -> Self {
if max_size < self.size {
Self::new(self.start_address(), max_size)
} else {
self
}
}
/// Shrink this region by moving the start address 'forward' by `x` bytes.
///
/// If `x` moves the start address beyond the end of the region, it
/// saturates on the boundary and becomes empty.
pub fn shrink_start(self, x: usize) -> Self {
Self {
address: self.start_address().add_bytes(x),
size: self.size.saturating_sub(x),
}
}
/// Return a new region with the same size but a different start address.
pub fn with_start_address(mut self, new_start: Address<T, ()>) -> Self {
self.address = new_start;
@@ -676,6 +702,83 @@ mod tests {
assert_eq!(main.punch_hole(hole), (Some(main), None));
}
#[test]
fn is_empty_zero_size() {
let r = region(0x1000, 0);
assert!(r.is_empty());
}
#[test]
fn is_empty_nonzero_size() {
let r = region(0x1000, 0x10);
assert!(!r.is_empty());
}
#[test]
fn is_empty_empty_constructor() {
assert!(PhysMemoryRegion::empty().is_empty());
}
#[test]
fn cap_size_below_current() {
let r = region(0x1000, 0x100);
let capped = r.cap_size(0x50);
assert_eq!(capped.start_address().value(), 0x1000);
assert_eq!(capped.size(), 0x50);
}
#[test]
fn cap_size_equal_to_current() {
let r = region(0x1000, 0x100);
let capped = r.cap_size(0x100);
assert_eq!(capped, r);
}
#[test]
fn cap_size_above_current() {
let r = region(0x1000, 0x100);
let capped = r.cap_size(0x200);
assert_eq!(capped, r);
}
#[test]
fn cap_size_to_zero() {
let r = region(0x1000, 0x100);
let capped = r.cap_size(0);
assert_eq!(capped.start_address().value(), 0x1000);
assert!(capped.is_empty());
}
#[test]
fn shrink_start_within_bounds() {
let r = region(0x1000, 0x100);
let shrunk = r.shrink_start(0x40);
assert_eq!(shrunk.start_address().value(), 0x1040);
assert_eq!(shrunk.size(), 0xC0);
}
#[test]
fn shrink_start_exact_size() {
let r = region(0x1000, 0x100);
let shrunk = r.shrink_start(0x100);
assert_eq!(shrunk.start_address().value(), 0x1100);
assert!(shrunk.is_empty());
}
#[test]
fn shrink_start_beyond_end_saturates() {
let r = region(0x1000, 0x100);
let shrunk = r.shrink_start(0x200);
assert_eq!(shrunk.start_address().value(), 0x1200);
assert!(shrunk.is_empty());
}
#[test]
fn shrink_start_zero() {
let r = region(0x1000, 0x100);
assert_eq!(r.shrink_start(0), r);
}
#[test]
fn test_punch_hole_disjoint() {
// Hole is completely far away (after)