use alloc::collections::BTreeMap;
use alloc::collections::btree_map::Entry;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
use crate::vmem::sealed::TableMovabilityBase;
use crate::vmem::{
BasicMapping, CowMapping, MapRequest, MapResponse, Mapping, MappingKind, SpaceAwareMapping,
SpaceId, SpaceReferenceMapping, TableOps, TableReadOps, UpdateParent, UpdateParentNone,
UpdateParentTable, Void, bits, modify_ptes, write_entry_updating,
};
pub const PAGE_SIZE: usize = 4096;
pub const PAGE_TABLE_SIZE: usize = 4096;
pub const PAGE_PRESENT: u64 = 1; pub const PTE_ADDR_MASK: u64 = 0x0000_FFFF_FFFF_F000; pub type PageTableEntry = u64;
pub type VirtAddr = u64;
pub type PhysAddr = u64;
const VA_BITS: usize = 48;
pub const ATTR_INDEX_NORMAL: u8 = 0;
const SOFTWARE_USE_COW: u8 = 0b1;
impl<
Op: TableOps<TableMovability = crate::vmem::MayMoveTable>,
P: UpdateParent<Op, TableMoveInfo = Op::TableAddr>,
> UpdateParent<Op> for UpdateParentTable<Op, P>
{
type TableMoveInfo = Op::TableAddr;
type ChildType = UpdateParentTable<Op, Self>;
fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
let pte = desc_for_table::<Op>(new_ptr);
unsafe {
write_entry_updating(op, self.parent, self.entry_ptr, pte);
}
}
fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
Self::ChildType::new(self, entry_ptr)
}
}
#[derive(Copy, Clone)]
pub(in crate::vmem) struct UpdateParentRoot {}
impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> UpdateParent<Op>
for UpdateParentRoot
{
type TableMoveInfo = Op::TableAddr;
type ChildType = UpdateParentTable<Op, Self>;
fn update_parent(self, op: &Op, new_ptr: Op::TableAddr) {
unsafe {
op.update_root(new_ptr);
}
}
fn for_child_at_entry(self, entry_ptr: Op::TableAddr) -> Self::ChildType {
Self::ChildType::new(self, entry_ptr)
}
}
pub(in crate::vmem) trait TableMovability<Op: TableReadOps + ?Sized, TableMoveInfo> {
type RootUpdateParent: UpdateParent<Op, TableMoveInfo = TableMoveInfo>;
fn root_update_parent() -> Self::RootUpdateParent;
}
impl<Op: TableOps<TableMovability = crate::vmem::MayMoveTable>> TableMovability<Op, Op::TableAddr>
for crate::vmem::MayMoveTable
{
type RootUpdateParent = UpdateParentRoot;
fn root_update_parent() -> Self::RootUpdateParent {
UpdateParentRoot {}
}
}
impl<Op: TableReadOps> TableMovability<Op, Void> for crate::vmem::MayNotMoveTable {
type RootUpdateParent = UpdateParentNone;
fn root_update_parent() -> Self::RootUpdateParent {
UpdateParentNone {}
}
}
#[allow(clippy::identity_op)]
#[allow(clippy::precedence)]
fn desc_for_table<Op: TableOps>(table_addr: Op::TableAddr) -> u64 {
Op::to_phys(table_addr) |
0b11 }
#[allow(clippy::identity_op)]
#[allow(clippy::precedence)]
fn desc_for_page(
page_addr: u64,
_readable: bool,
writable: bool,
executable: bool,
software_use: u8,
user_accessible: bool,
) -> u64 {
let xn = match (executable, user_accessible) {
(true, true) => 0,
(true, false) => 0b10,
(false, _) => 0b11,
};
let ap = match (writable, user_accessible) {
(true, true) => 0b01,
(true, false) => 0b00,
(false, true) => 0b11,
(false, false) => 0b10,
};
page_addr |
((software_use as u64 & 0xf) << 55) |
(xn << 53) |
(0b1 << 11) | (0b1 << 10) | (0b11 << 8) | (ap << 6) |
((ATTR_INDEX_NORMAL as u64) << 2) |
0b11
}
#[allow(clippy::identity_op)]
#[allow(clippy::precedence)]
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 presumed_base = mapping.phys_base + (r.vmin - mapping.virt_base);
let desc = match &mapping.kind {
MappingKind::Basic(bm) => desc_for_page(
presumed_base,
bm.readable,
bm.writable,
bm.executable,
0,
false,
),
MappingKind::Cow(cm) => desc_for_page(
presumed_base,
cm.readable,
false,
cm.executable,
SOFTWARE_USE_COW,
false,
),
MappingKind::Unmapped => 0,
};
unsafe {
write_entry_updating(op, r.update_parent, r.entry_ptr, desc);
}
}
enum FinalLevelDescriptorKind {
Page,
}
enum EarlyLevelDescriptorKind {
Block,
Table,
}
fn final_level_descriptor_kind(desc: u64) -> Option<FinalLevelDescriptorKind> {
if desc & 3 == 3 {
Some(FinalLevelDescriptorKind::Page)
} else {
None
}
}
fn early_level_descriptor_kind(desc: u64) -> Option<EarlyLevelDescriptorKind> {
match desc & 0b11 {
0b01 => Some(EarlyLevelDescriptorKind::Block),
0b11 => Some(EarlyLevelDescriptorKind::Table),
_ => None,
}
}
unsafe fn next_level_table_if_present<Op: TableReadOps>(
op: &Op,
addr: Op::TableAddr,
) -> Option<Op::TableAddr> {
let desc: u64 = unsafe { op.read_entry(addr) };
if let Some(EarlyLevelDescriptorKind::Table) = early_level_descriptor_kind(desc) {
Some(Op::from_phys(bits::<47, 12>(desc) << 12))
} else {
None
}
}
unsafe fn alloc_table_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(table_base) = unsafe { next_level_table_if_present(op, x.entry_ptr) } {
return MapRequest {
table_base,
vmin: x.vmin,
len: x.len,
update_parent: new_update_parent,
};
}
let page_addr = unsafe { op.alloc_table() };
let pte = desc_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 require_table_exist<Op: TableReadOps, P: UpdateParent<Op>>(
op: &Op,
x: MapResponse<Op, P>,
) -> Option<MapRequest<Op, P::ChildType>>
where
P::ChildType: UpdateParent<Op>,
{
unsafe {
next_level_table_if_present(op, x.entry_ptr).map(|table_base| MapRequest {
table_base,
vmin: x.vmin,
len: x.len,
update_parent: x.update_parent.for_child_at_entry(x.entry_ptr),
})
}
}
enum WalkNextLevelResponse<Op: TableReadOps, P: UpdateParent<Op>> {
WalkNextLevel(MapResponse<Op, P>),
AlreadySeen(SpaceReferenceMapping),
}
enum WalkNextLevelRequest<Op: TableReadOps, P: UpdateParent<Op>> {
WalkNextLevel(MapRequest<Op, P>),
AlreadySeen(SpaceReferenceMapping),
}
fn walk_check_request_seen<Op: TableReadOps, P: UpdateParent<Op>>(
seen_pts: &Option<Rc<RefCell<BTreeMap<u64, SpaceReferenceMapping>>>>,
space: SpaceId,
depth: usize,
rq: MapRequest<Op, P>,
) -> WalkNextLevelRequest<Op, P> {
let Some(seen_pts) = seen_pts else {
return WalkNextLevelRequest::WalkNextLevel(rq);
};
match seen_pts.borrow_mut().entry(Op::to_phys(rq.table_base)) {
Entry::Vacant(ve) => {
ve.insert(SpaceReferenceMapping {
depth,
space,
our_va: 0,
their_va: rq.vmin,
});
WalkNextLevelRequest::WalkNextLevel(rq)
}
Entry::Occupied(oe) => {
let mut sm = *oe.get();
if sm.depth != depth {
WalkNextLevelRequest::WalkNextLevel(rq)
} else {
sm.our_va = rq.vmin;
WalkNextLevelRequest::AlreadySeen(sm)
}
}
}
}
fn walk_next_level_table<Op: TableReadOps, P: UpdateParent<Op>>(
op: &Op,
x: WalkNextLevelResponse<Op, P>,
next_depth: usize,
space: SpaceId,
seen_pts: &Option<Rc<RefCell<BTreeMap<u64, SpaceReferenceMapping>>>>,
) -> Option<WalkNextLevelRequest<Op, P::ChildType>>
where
P::ChildType: UpdateParent<Op>,
{
let rq = match x {
WalkNextLevelResponse::WalkNextLevel(rq) => rq,
WalkNextLevelResponse::AlreadySeen(sm) => {
return Some(WalkNextLevelRequest::AlreadySeen(sm));
}
};
let next_base = unsafe { require_table_exist(op, rq)? };
Some(walk_check_request_seen(
seen_pts, space, next_depth, next_base,
))
}
fn do_walk_next_level<
const HIGH_BIT: u8,
const LOW_BIT: u8,
Op: TableReadOps,
P: UpdateParent<Op>,
>(
x: WalkNextLevelRequest<Op, P>,
) -> impl Iterator<Item = WalkNextLevelResponse<Op, P>> {
let (iter_a, iter_b) = match x {
WalkNextLevelRequest::WalkNextLevel(rq) => (
Some(
modify_ptes::<HIGH_BIT, LOW_BIT, Op, P>(rq)
.map(|r| WalkNextLevelResponse::WalkNextLevel(r)),
),
None,
),
WalkNextLevelRequest::AlreadySeen(sm) => (
None,
Some(core::iter::once(WalkNextLevelResponse::AlreadySeen(sm))),
),
};
iter_a
.into_iter()
.flatten()
.chain(iter_b.into_iter().flatten())
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn map<Op: TableOps>(op: &Op, mapping: Mapping) {
modify_ptes::<47, 39, 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_table_if_needed(op, r) })
.flat_map(modify_ptes::<38, 30, Op, _>)
.map(|r| unsafe { alloc_table_if_needed(op, r) })
.flat_map(modify_ptes::<29, 21, Op, _>)
.map(|r| unsafe { alloc_table_if_needed(op, r) })
.flat_map(modify_ptes::<20, 12, Op, _>)
.map(|r| unsafe { map_page(op, &mapping, r) })
.for_each(drop);
}
#[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 roots = core::iter::once(op.as_ref().root_table());
unsafe {
internal_walk_va_spaces(op, roots, false, address, len)
.flat_map(|(_, mappings)| mappings)
.filter_map(|saw| match saw {
SpaceAwareMapping::ThisSpace(m) => Some(m),
SpaceAwareMapping::AnotherSpace(_) => None,
})
}
}
#[allow(clippy::missing_safety_doc)]
unsafe fn internal_walk_va_spaces<'a, Op: TableReadOps + 'a>(
op: impl core::convert::AsRef<Op> + Copy + 'a,
roots: impl Iterator<Item = Op::TableAddr> + 'a,
do_dedup: bool,
address: u64,
len: u64,
) -> impl Iterator<
Item = (
SpaceId,
impl Iterator<Item = crate::vmem::SpaceAwareMapping>,
),
> + 'a {
let addr = address & ((1u64 << VA_BITS) - 1);
let vmin = addr & !((PAGE_SIZE as u64) - 1);
let vmax = core::cmp::min(addr + len, 1u64 << VA_BITS);
let seen_pts: Option<Rc<RefCell<BTreeMap<u64, SpaceReferenceMapping>>>> = if do_dedup {
Some(Rc::new(RefCell::new(BTreeMap::new())))
} else {
None
};
roots.into_iter().map(move |root| {
let root_id = Op::to_phys(root);
let root_req = walk_check_request_seen(
&seen_pts,
root_id,
0,
MapRequest {
table_base: root,
vmin,
len: vmax.saturating_sub(vmin),
update_parent: UpdateParentNone {},
},
);
let seen_pts_1 = seen_pts.clone();
let seen_pts_2 = seen_pts.clone();
let seen_pts_3 = seen_pts.clone();
let iter = do_walk_next_level::<47, 39, Op, _>(root_req)
.filter_map(move |r| walk_next_level_table(op.as_ref(), r, 1, root_id, &seen_pts_1))
.flat_map(do_walk_next_level::<38, 30, Op, _>)
.filter_map(move |r| walk_next_level_table(op.as_ref(), r, 2, root_id, &seen_pts_2))
.flat_map(do_walk_next_level::<29, 21, Op, _>)
.filter_map(move |r| walk_next_level_table(op.as_ref(), r, 3, root_id, &seen_pts_3))
.flat_map(do_walk_next_level::<20, 12, Op, _>)
.filter_map(move |r| {
let rq = match r {
WalkNextLevelResponse::AlreadySeen(sm) => {
return Some(SpaceAwareMapping::AnotherSpace(sm));
}
WalkNextLevelResponse::WalkNextLevel(rq) => rq,
};
let desc = unsafe { op.as_ref().read_entry(rq.entry_ptr) };
if let Some(FinalLevelDescriptorKind::Page) = final_level_descriptor_kind(desc) {
let phys_addr = bits::<47, 12>(desc) << 12;
let virt_addr = rq.vmin;
let executable = bits::<53, 53>(desc) == 0;
let _user_accessible = bits::<6, 6>(desc) != 0; let kind = if bits::<58, 55>(desc) == SOFTWARE_USE_COW as u64 {
MappingKind::Cow(CowMapping {
readable: true,
executable,
})
} else {
MappingKind::Basic(BasicMapping {
readable: true,
writable: bits::<7, 7>(desc) == 0, executable,
})
};
Some(SpaceAwareMapping::ThisSpace(Mapping {
phys_base: phys_addr,
virt_base: virt_addr,
len: PAGE_SIZE as u64,
kind,
}))
} else {
None }
});
(root_id, iter)
})
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn walk_va_spaces<Op: TableReadOps>(
op: impl core::convert::AsRef<Op> + Copy,
roots: &[Op::TableAddr],
address: u64,
len: u64,
) -> Vec<(SpaceId, Vec<crate::vmem::SpaceAwareMapping>)> {
unsafe {
internal_walk_va_spaces(&op, roots.iter().cloned(), true, address, len)
.map(|(id, mappings)| (id, mappings.collect::<Vec<_>>()))
.collect::<Vec<_>>()
}
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn space_aware_map<Op: TableOps>(
_op: &Op,
_ref_map: crate::vmem::SpaceReferenceMapping,
_built_roots: &::alloc::collections::BTreeMap<crate::vmem::SpaceId, Op::TableAddr>,
) {
debug_assert!(false, "space_aware_map is not yet supported on aarch64");
}