use core::{convert::TryFrom, fmt};
use axvm_types::{HostPhysAddr, MappingFlags};
use bit_field::BitField;
use page_table_generic as ptg;
use super::runtime::{config_to_flags, flush_nested_page_table};
bitflags::bitflags! {
struct EptFlags: u64 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
const MEM_TYPE_MASK = 0b111 << 3;
const IGNORE_PAT = 1 << 6;
const HUGE_PAGE = 1 << 7;
const ACCESSED = 1 << 8;
const DIRTY = 1 << 9;
const EXECUTE_FOR_USER = 1 << 10;
}
}
numeric_enum_macro::numeric_enum! {
#[repr(u8)]
#[derive(Debug, PartialEq, Clone, Copy)]
enum EptMemoryType {
Uncached = 0,
WriteCombining = 1,
WriteThrough = 4,
WriteProtected = 5,
WriteBack = 6,
}
}
impl EptFlags {
fn set_memory_type(&mut self, memory_type: EptMemoryType) {
let mut bits = self.bits();
bits.set_bits(3..6, memory_type as u64);
*self = Self::from_bits_truncate(bits)
}
fn memory_type(self) -> Result<EptMemoryType, u8> {
EptMemoryType::try_from(self.bits().get_bits(3..6) as u8)
}
}
impl From<MappingFlags> for EptFlags {
fn from(flags: MappingFlags) -> Self {
if flags.is_empty() {
return Self::empty();
}
let mut result = Self::empty();
if flags.contains(MappingFlags::READ) {
result |= Self::READ;
}
if flags.contains(MappingFlags::WRITE) {
result |= Self::WRITE;
}
if flags.contains(MappingFlags::EXECUTE) {
result |= Self::EXECUTE;
}
if flags.contains(MappingFlags::DEVICE) || flags.contains(MappingFlags::UNCACHED) {
result.set_memory_type(EptMemoryType::Uncached);
} else {
result.set_memory_type(EptMemoryType::WriteBack);
}
result
}
}
impl From<EptFlags> for MappingFlags {
fn from(flags: EptFlags) -> Self {
let mut result = MappingFlags::empty();
if flags.contains(EptFlags::READ) {
result |= MappingFlags::READ;
}
if flags.contains(EptFlags::WRITE) {
result |= MappingFlags::WRITE;
}
if flags.contains(EptFlags::EXECUTE) {
result |= MappingFlags::EXECUTE;
}
if matches!(flags.memory_type(), Ok(EptMemoryType::Uncached)) {
result |= MappingFlags::DEVICE;
}
result
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub(super) struct EptEntry(u64);
impl EptEntry {
const PHYS_ADDR_MASK: u64 = 0x000f_ffff_ffff_f000;
fn paddr(self) -> HostPhysAddr {
HostPhysAddr::from((self.0 & Self::PHYS_ADDR_MASK) as usize)
}
fn flags(self) -> MappingFlags {
EptFlags::from_bits_truncate(self.0).into()
}
}
impl ptg::PageTableEntry for EptEntry {
fn from_config(config: ptg::PteConfig) -> Self {
if !config.valid {
return Self(0);
}
let flags = if config.is_dir && !config.huge {
EptFlags::READ | EptFlags::WRITE | EptFlags::EXECUTE
} else {
let mut flags = EptFlags::from(config_to_flags(config));
if config.huge {
flags |= EptFlags::HUGE_PAGE;
}
flags
};
Self(flags.bits() | (config.paddr.raw() as u64 & Self::PHYS_ADDR_MASK))
}
fn to_config(&self, is_dir: bool) -> ptg::PteConfig {
let flags = EptFlags::from_bits_truncate(self.0);
let valid = self.valid();
let huge = is_dir && flags.contains(EptFlags::HUGE_PAGE);
let mapping_flags = MappingFlags::from(flags);
ptg::PteConfig {
paddr: ptg::PhysAddr::new(self.paddr().as_usize()),
valid,
read: mapping_flags.contains(MappingFlags::READ),
writable: mapping_flags.contains(MappingFlags::WRITE),
executable: mapping_flags.contains(MappingFlags::EXECUTE),
lower: mapping_flags.contains(MappingFlags::USER),
is_dir: is_dir && valid && !huge,
huge,
mem_attr: if mapping_flags.contains(MappingFlags::DEVICE) {
ptg::MemAttributes::Device
} else {
ptg::MemAttributes::Normal
},
..Default::default()
}
}
fn valid(&self) -> bool {
self.0 & 0x7 != 0
}
}
impl fmt::Debug for EptEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EptEntry")
.field("raw", &self.0)
.field("hpaddr", &self.paddr())
.field("flags", &self.flags())
.field(
"memory_type",
&EptFlags::from_bits_truncate(self.0).memory_type(),
)
.finish()
}
}
#[derive(Clone, Copy)]
pub(super) struct EptPageTableMetadata;
impl ptg::TableMeta for EptPageTableMetadata {
type P = EptEntry;
const PAGE_SIZE: usize = ax_memory_addr::PAGE_SIZE_4K;
const LEVEL_BITS: &[usize] = &[9, 9, 9, 9];
const MAX_BLOCK_LEVEL: usize = 3;
const STRICT_ADDRESS_WIDTH: bool = true;
fn flush(vaddr: Option<ptg::VirtAddr>) {
flush_nested_page_table(vaddr);
}
}