use core::fmt::Debug;
use page_table_generic::{MemAttributes, PageTableEntry};
use tock_registers::{interfaces::*, register_bitfields, registers::*};
register_bitfields![u64,
PTE_DIR [
VALID OFFSET(0) NUMBITS(1) [],
DIRTY OFFSET(1) NUMBITS(1) [],
PLV OFFSET(2) NUMBITS(2) [
PLV0 = 0b00, PLV1 = 0b01, PLV2 = 0b10, PLV3 = 0b11 ],
CACHE OFFSET(4) NUMBITS(2) [
SUC = 0b00, CC = 0b01, WUC = 0b10 ],
H OFFSET(6) NUMBITS(1) [],
PRESENT OFFSET(7) NUMBITS(1) [],
WRITE OFFSET(8) NUMBITS(1) [],
G OFFSET(12) NUMBITS(1) [],
PHYS_ADDR OFFSET(12) NUMBITS(40) [],
NO_READ OFFSET(61) NUMBITS(1) [],
NO_EXEC OFFSET(62) NUMBITS(1) [],
RPLV OFFSET(63) NUMBITS(1) [],
],
PTE [
VALID OFFSET(0) NUMBITS(1) [],
DIRTY OFFSET(1) NUMBITS(1) [],
PLV OFFSET(2) NUMBITS(2) [
PLV0 = 0b00, PLV1 = 0b01, PLV2 = 0b10, PLV3 = 0b11 ],
CACHE OFFSET(4) NUMBITS(2) [
SUC = 0b00, CC = 0b01, WUC = 0b10 ],
G OFFSET(6) NUMBITS(1) [],
PRESENT OFFSET(7) NUMBITS(1) [],
WRITE OFFSET(8) NUMBITS(1) [],
PHYS_ADDR OFFSET(12) NUMBITS(40) [],
NO_READ OFFSET(61) NUMBITS(1) [],
NO_EXEC OFFSET(62) NUMBITS(1) [],
RPLV OFFSET(63) NUMBITS(1) [],
],
];
type PteRegister = ReadWrite<u64, PTE::Register>;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Entry(u64);
impl Entry {
#[inline(always)]
fn as_base(&self) -> &PteRegister {
unsafe { &*(self as *const Self as *const PteRegister) }
}
#[inline(always)]
fn as_dir(&self) -> &ReadWrite<u64, PTE_DIR::Register> {
unsafe { &*(self as *const Self as *const _) }
}
pub const fn empty() -> Self {
Self(0)
}
#[allow(dead_code)]
pub(crate) fn debug(
&self,
is_dir: bool,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
if is_dir {
self.as_dir().debug().fmt(f)
} else {
self.as_base().debug().fmt(f)
}
}
fn from_huge(config: page_table_generic::PteConfig) -> u64 {
let mut val = PTE_DIR::H::SET;
if config.valid {
val = val + PTE_DIR::VALID::SET + PTE_DIR::PRESENT::SET;
}
if !config.read {
val += PTE_DIR::NO_READ::SET;
}
if config.writable {
val += PTE_DIR::WRITE::SET + PTE_DIR::DIRTY::SET;
}
if !config.executable {
val += PTE_DIR::NO_EXEC::SET;
}
val += if config.lower {
PTE_DIR::PLV::PLV3
} else {
PTE_DIR::PLV::PLV0
};
let ppn = (config.paddr.raw() as u64) >> 12;
val += PTE_DIR::PHYS_ADDR.val(ppn);
if config.global {
val += PTE_DIR::G::SET;
}
val += match config.mem_attr {
MemAttributes::Device => PTE_DIR::CACHE::SUC, MemAttributes::Normal | MemAttributes::PerCpu => PTE_DIR::CACHE::CC, MemAttributes::Uncached => PTE_DIR::CACHE::WUC, };
val.value
}
fn from_dir(config: page_table_generic::PteConfig) -> u64 {
let paddr = config.paddr.raw();
PTE_DIR::PHYS_ADDR.val((paddr >> 12) as u64).value
}
fn from_base(config: page_table_generic::PteConfig) -> u64 {
let mut val = PTE::VALID::CLEAR;
if config.valid {
val = PTE::VALID::SET + PTE::PRESENT::SET;
}
if !config.read {
val += PTE::NO_READ::SET;
}
if config.writable {
val += PTE::WRITE::SET + PTE::DIRTY::SET;
}
if !config.executable {
val += PTE::NO_EXEC::SET;
}
val += if config.lower {
PTE::PLV::PLV3
} else {
PTE::PLV::PLV0
};
let ppn = (config.paddr.raw() as u64) >> 12;
val += PTE::PHYS_ADDR.val(ppn);
if config.global {
val += PTE::G::SET;
}
val += match config.mem_attr {
MemAttributes::Device => PTE::CACHE::SUC, MemAttributes::Normal | MemAttributes::PerCpu => PTE::CACHE::CC, MemAttributes::Uncached => PTE::CACHE::WUC, };
val.value
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub(crate) struct EntryDebug(Entry, bool);
impl Debug for EntryDebug {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.debug(self.1, f)
}
}
impl PageTableEntry for Entry {
fn from_config(config: page_table_generic::PteConfig) -> Self {
let val = if config.is_dir {
if config.huge {
Self::from_huge(config)
} else {
Self::from_dir(config)
}
} else {
Self::from_base(config)
};
Self(val)
}
fn to_config(&self, is_dir: bool) -> page_table_generic::PteConfig {
let valid = self.as_base().is_set(PTE::VALID);
let mut paddr = self.as_base().read(PTE::PHYS_ADDR) << 12;
let huge = if is_dir {
self.as_dir().is_set(PTE_DIR::H)
} else {
false
};
if huge {
paddr &= !0x1FFF;
}
let global = if huge {
self.as_dir().is_set(PTE_DIR::G)
} else {
self.as_base().is_set(PTE::G)
};
let mem_attr = match self.as_base().read_as_enum(PTE::CACHE) {
Some(PTE::CACHE::Value::SUC) => MemAttributes::Device,
Some(PTE::CACHE::Value::CC) => MemAttributes::Normal,
Some(PTE::CACHE::Value::WUC) => MemAttributes::Uncached,
_ => MemAttributes::Normal,
};
page_table_generic::PteConfig {
paddr: paddr.into(),
valid,
read: valid, writable: self.as_base().is_set(PTE::WRITE),
executable: !self.as_base().is_set(PTE::NO_EXEC),
lower: matches!(
self.as_base().read_as_enum(PTE::PLV),
Some(PTE::PLV::Value::PLV3)
),
dirty: self.as_base().is_set(PTE::DIRTY),
global,
is_dir,
huge,
mem_attr,
}
}
fn valid(&self) -> bool {
self.as_base().is_set(PTE::VALID)
}
}
impl core::fmt::Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let d = self.as_base().debug();
d.fmt(f)
}
}