use crate::vmem::{
BasicMapping, CowMapping, MapRequest, MapResponse, Mapping, MappingKind, SpaceAwareMapping,
SpaceId, SpaceReferenceMapping, TableMovabilityBase, TableOps, TableReadOps, UpdateParent,
UpdateParentNone, modify_ptes, write_entry_updating,
};
pub const PAGE_SIZE: usize = 4096;
pub const PAGE_TABLE_SIZE: usize = 4096;
pub type PageTableEntry = u32;
pub type VirtAddr = u32;
pub type PhysAddr = u32;
pub const PAGE_PRESENT: u64 = 1;
const PAGE_RW: u64 = 1 << 1;
pub const PAGE_USER: u64 = 1 << 2;
const PAGE_ACCESSED: u64 = 1 << 5;
pub const PTE_ADDR_MASK: u64 = 0xFFFFF000;
const PTE_AVL_MASK: u64 = 0x0E00;
const PAGE_AVL_COW: u64 = 1 << 9;
const VA_BITS: usize = 32;
pub trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
fn root_update_parent() -> Self::RootUpdateParent;
}
impl<Op: TableReadOps> TableMovability<Op, crate::vmem::Void> for crate::vmem::MayNotMoveTable {
type RootUpdateParent = UpdateParentNone;
fn root_update_parent() -> Self::RootUpdateParent {
UpdateParentNone {}
}
}
#[inline(always)]
const fn page_rw_flag(writable: bool) -> u64 {
if writable { PAGE_RW } else { 0 }
}
#[inline(always)]
#[allow(clippy::useless_conversion)]
pub(super) unsafe fn read_pte_if_present<Op: TableReadOps>(
op: &Op,
entry_ptr: Op::TableAddr,
) -> Option<u64> {
let pte: u64 = unsafe { op.read_entry(entry_ptr) }.into();
if (pte & PAGE_PRESENT) != 0 {
Some(pte)
} else {
None
}
}
pub(super) unsafe fn require_pte_exist<Op: TableReadOps, P: UpdateParent<Op>>(
op: &Op,
x: MapResponse<Op, P>,
) -> Option<MapRequest<Op, P::ChildType>>
where
P::ChildType: UpdateParent<Op>,
{
unsafe { read_pte_if_present(op, x.entry_ptr) }.map(|pte| MapRequest {
#[allow(clippy::unnecessary_cast)]
table_base: Op::from_phys((pte & PTE_ADDR_MASK) as PhysAddr),
vmin: x.vmin,
len: x.len,
update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
})
}
fn pte_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
#[allow(clippy::unnecessary_cast)]
let phys = Op::to_phys(table_addr) as u64;
phys | PAGE_USER | PAGE_RW | PAGE_ACCESSED | PAGE_PRESENT
}
unsafe fn alloc_pte_if_needed<
Op: TableOps,
P: UpdateParent<
Op,
TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
>,
>(
op: &Op,
x: MapResponse<Op, P>,
) -> MapRequest<Op, P::ChildType>
where
P::ChildType: UpdateParent<Op>,
{
let new_update_parent = x.update_parent.for_child_at_entry(x.entry_ptr);
if let Some(pte) = unsafe { read_pte_if_present(op, x.entry_ptr) } {
#[allow(clippy::unnecessary_cast)]
return MapRequest {
table_base: Op::from_phys((pte & PTE_ADDR_MASK) as super::PhysAddr),
vmin: x.vmin,
len: x.len,
update_parent: new_update_parent,
};
}
let page_addr = unsafe { op.alloc_table() };
let pte = pte_for_table::<Op>(page_addr);
unsafe {
write_entry_updating(op, x.update_parent, x.entry_ptr, pte);
};
MapRequest {
table_base: page_addr,
vmin: x.vmin,
len: x.len,
update_parent: new_update_parent,
}
}
unsafe fn map_page<
Op: TableOps,
P: UpdateParent<
Op,
TableMoveInfo = <Op::TableMovability as TableMovabilityBase<Op>>::TableMoveInfo,
>,
>(
op: &Op,
mapping: &Mapping,
r: MapResponse<Op, P>,
) {
let user_flag = if mapping.user_accessible {
PAGE_USER
} else {
0
};
let pte = match &mapping.kind {
MappingKind::Basic(bm) => {
(mapping.phys_base + (r.vmin - mapping.virt_base))
| user_flag
| PAGE_ACCESSED
| page_rw_flag(bm.writable)
| PAGE_PRESENT
}
MappingKind::Cow(_cm) => {
(mapping.phys_base + (r.vmin - mapping.virt_base))
| user_flag
| PAGE_AVL_COW
| PAGE_ACCESSED
| PAGE_PRESENT
}
MappingKind::Unmapped => 0,
};
unsafe {
write_entry_updating(op, r.update_parent, r.entry_ptr, pte);
}
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
modify_ptes::<31, 22, Op, _>(MapRequest {
table_base: op.root_table(),
vmin: mapping.virt_base,
len: mapping.len,
update_parent: Op::TableMovability::root_update_parent(),
})
.map(|r| unsafe { alloc_pte_if_needed(op, r) })
.flat_map(modify_ptes::<21, 12, Op, _>)
.map(|r| unsafe { map_page(op, &mapping, r) })
.for_each(drop);
}
const SHARED_TABLE_DEPTH: usize = 1;
#[allow(clippy::missing_safety_doc)]
pub unsafe fn walk_va_spaces<Op: TableReadOps>(
op: &Op,
roots: &[Op::TableAddr],
address: u64,
len: u64,
) -> ::alloc::vec::Vec<(SpaceId, ::alloc::vec::Vec<SpaceAwareMapping>)> {
use ::alloc::vec::Vec;
let mut seen_pts: ::alloc::collections::BTreeMap<u64, (SpaceId, u64)> =
::alloc::collections::BTreeMap::new();
let mut results: Vec<(SpaceId, Vec<SpaceAwareMapping>)> = Vec::with_capacity(roots.len());
let vmin = address & !(PAGE_SIZE as u64 - 1);
let vmax = core::cmp::min(address + len, 1u64 << VA_BITS);
for &root in roots {
#[allow(clippy::unnecessary_cast)]
let root_id: SpaceId = Op::to_phys(root) as u64;
let mut mappings: Vec<SpaceAwareMapping> = Vec::new();
let pde_iter = modify_ptes::<31, 22, Op, _>(MapRequest {
table_base: root,
vmin,
len: vmax.saturating_sub(vmin),
update_parent: UpdateParentNone {},
});
for r in pde_iter {
let Some(pde) = (unsafe { read_pte_if_present(op, r.entry_ptr) }) else {
continue;
};
let pt_pa: u64 = pde & PTE_ADDR_MASK;
if let Some(&(owner, their_va)) = seen_pts.get(&pt_pa) {
if owner != root_id {
mappings.push(SpaceAwareMapping::AnotherSpace(SpaceReferenceMapping {
depth: SHARED_TABLE_DEPTH,
space: owner,
our_va: r.vmin,
their_va,
}));
continue;
}
continue;
}
seen_pts.insert(pt_pa, (root_id, r.vmin));
let pt_request = MapRequest {
#[allow(clippy::unnecessary_cast)]
table_base: Op::from_phys(pt_pa as PhysAddr),
vmin: r.vmin,
len: r.len,
update_parent: UpdateParentNone {},
};
for leaf in modify_ptes::<21, 12, Op, _>(pt_request) {
let Some(pte) = (unsafe { read_pte_if_present(op, leaf.entry_ptr) }) else {
continue;
};
let phys_addr = pte & PTE_ADDR_MASK;
let avl = pte & PTE_AVL_MASK;
let kind = if avl == PAGE_AVL_COW {
MappingKind::Cow(CowMapping {
readable: true,
executable: true,
})
} else {
MappingKind::Basic(BasicMapping {
readable: true,
writable: (pte & PAGE_RW) != 0,
executable: true,
})
};
mappings.push(SpaceAwareMapping::ThisSpace(Mapping {
phys_base: phys_addr,
virt_base: leaf.vmin,
len: PAGE_SIZE as u64,
kind,
user_accessible: (pte & PAGE_USER) != 0,
}));
}
}
results.push((root_id, mappings));
}
results
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn space_aware_map<Op: TableOps>(
op: &Op,
ref_map: SpaceReferenceMapping,
built_roots: &::alloc::collections::BTreeMap<SpaceId, Op::TableAddr>,
) {
assert!(
ref_map.depth == SHARED_TABLE_DEPTH,
"i686 only supports depth={} sharing; got depth={}",
SHARED_TABLE_DEPTH,
ref_map.depth
);
let Some(&their_root) = built_roots.get(&ref_map.space) else {
return;
};
let their_pdi = (ref_map.their_va >> 22) & 0x3FF;
let their_pde_ptr = Op::entry_addr(
their_root,
their_pdi * core::mem::size_of::<PageTableEntry>() as u64,
);
let Some(their_pde) = (unsafe { read_pte_if_present(op, their_pde_ptr) }) else {
return;
};
let their_pt_pa: u64 = their_pde & PTE_ADDR_MASK;
let our_pdi = (ref_map.our_va >> 22) & 0x3FF;
let our_root = op.root_table();
let our_pde_ptr = Op::entry_addr(
our_root,
our_pdi * core::mem::size_of::<PageTableEntry>() as u64,
);
let new_pde: u64 = their_pt_pa | (their_pde & !PTE_ADDR_MASK) | PAGE_PRESENT;
unsafe {
write_entry_updating(
op,
Op::TableMovability::root_update_parent(),
our_pde_ptr,
new_pde,
);
}
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn virt_to_phys<'a, Op: TableReadOps + 'a>(
op: impl core::convert::AsRef<Op> + Copy + 'a,
address: u64,
len: u64,
) -> impl Iterator<Item = Mapping> + 'a {
let vmin = address & !(PAGE_SIZE as u64 - 1);
let vmax = core::cmp::min(address + len, 1u64 << VA_BITS);
modify_ptes::<31, 22, Op, _>(MapRequest {
table_base: op.as_ref().root_table(),
vmin,
len: vmax.saturating_sub(vmin),
update_parent: UpdateParentNone {},
})
.filter_map(move |r| unsafe { require_pte_exist(op.as_ref(), r) })
.flat_map(modify_ptes::<21, 12, Op, _>)
.filter_map(move |r| {
let pte = unsafe { read_pte_if_present(op.as_ref(), r.entry_ptr) }?;
let phys_addr = pte & PTE_ADDR_MASK;
let avl = pte & PTE_AVL_MASK;
let kind = if avl == PAGE_AVL_COW {
MappingKind::Cow(CowMapping {
readable: true,
executable: true,
})
} else {
MappingKind::Basic(BasicMapping {
readable: true,
writable: (pte & PAGE_RW) != 0,
executable: true,
})
};
Some(Mapping {
phys_base: phys_addr,
virt_base: r.vmin,
len: PAGE_SIZE as u64,
kind,
user_accessible: (pte & PAGE_USER) != 0,
})
})
}