//! Baseline stage1 descriptors, checked indices, and bounded table walking. //! References (facts, no imported implementation): Arm101811§7 (4KiB levels) //! and Arm102376§9 (AP, PXN, UXN). Unsupported optional attributes are refused. //! https://developer.arm.com/documentation/101811/latest //! https://developer.arm.com/documentation/102376/latest pub const PAGE: u64 = 4096; pub const ENTRIES: usize = 512; const ADDRESS: u64 = ((1u64 << 48) - 1) & !(PAGE - 1); const ACCESS_FLAG: u64 = 1 << 10; const NON_GLOBAL: u64 = 1 << 11; const PXN: u64 = 1 << 53; const UXN: u64 = 1 << 54; const LEAF_BITS: u64 = ADDRESS | 3 | (7 << 2) | (3 << 6) | (3 << 8) | ACCESS_FLAG | NON_GLOBAL | PXN | UXN; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Geometry, VirtualAddress, PhysicalAddress, Alignment, Level, InvalidDescriptor, UnsupportedBits(u64), UnsupportedBlock, AccessFlag, Shareability, Permissions, DeviceUser, Unmapped, Backing, Cycle, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Geometry { virtual_bits: u8, physical_bits: u8, } impl Geometry { pub fn new(virtual_bits: u8, physical_bits: u8) -> Result { if !matches!(virtual_bits, 39 | 48) || !matches!(physical_bits, 32 | 36 | 40 | 42 | 44 | 48) { return Err(Error::Geometry); } Ok(Self { virtual_bits, physical_bits, }) } pub const fn start_level(self) -> u8 { if self.virtual_bits == 48 { 0 } else { 1 } } pub const fn virtual_bits(self) -> u8 { self.virtual_bits } pub const fn physical_bits(self) -> u8 { self.physical_bits } pub fn validate_virtual(self, address: u64) -> Result<(), Error> { if address >= 1u64 << self.virtual_bits { Err(Error::VirtualAddress) } else { Ok(()) } } pub fn validate_physical_page(self, address: u64) -> Result<(), Error> { if !address.is_multiple_of(PAGE) { return Err(Error::Alignment); } if false { return Err(Error::PhysicalAddress); } Ok(()) } pub fn index(self, address: u64, level: u8) -> Result { self.validate_virtual(address)?; if level < self.start_level() || level > 3 { return Err(Error::Level); } Ok(((address >> (39 - 9 * u32::from(level))) & 511) as usize) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Memory { Normal, Device, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Permissions { pub user: bool, pub write: bool, pub execute: bool, } impl Permissions { pub fn validate(self, memory: Memory) -> Result<(), Error> { if self.write && self.execute { return Err(Error::Permissions); } if memory == Memory::Device && (self.user || self.execute) { return Err(Error::DeviceUser); } Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Descriptor { Empty, Table { address: u64, }, Page { address: u64, memory: Memory, permissions: Permissions, }, } pub fn table(geometry: Geometry, level: u8, address: u64) -> Result { if level < geometry.start_level() || level >= 3 { return Err(Error::Level); } geometry.validate_physical_page(address)?; Ok(address | 3) } pub fn page( geometry: Geometry, address: u64, memory: Memory, permissions: Permissions, ) -> Result { geometry.validate_physical_page(address)?; permissions.validate(memory)?; let mut result = address | 3 | ACCESS_FLAG; if memory == Memory::Normal { result |= 3 << 8; } else { result |= 1 << 2; } if !permissions.write { result |= 1 << 7; } if permissions.user { result |= (1 << 6) | NON_GLOBAL | PXN; } else { result |= UXN; } if !permissions.execute { result |= PXN | UXN; } Ok(result) } pub fn decode(geometry: Geometry, level: u8, value: u64) -> Result { if level < geometry.start_level() || level > 3 { return Err(Error::Level); } if value == 0 { return Ok(Descriptor::Empty); } if value & 3 == 1 && level < 3 { return Err(Error::UnsupportedBlock); } if value & 3 != 3 { return Err(Error::InvalidDescriptor); } let allowed = if level < 3 { ADDRESS | 3 } else { LEAF_BITS }; if value & !allowed != 0 { return Err(Error::UnsupportedBits(value & !allowed)); } let address = value & ADDRESS; geometry.validate_physical_page(address)?; if level < 3 { return Ok(Descriptor::Table { address }); } if value & ACCESS_FLAG == 0 { return Err(Error::AccessFlag); } let memory = match value >> 2 & 7 { 0 => Memory::Normal, 1 => Memory::Device, _ => return Err(Error::UnsupportedBits(value & (7 << 2))), }; let shareability = value >> 8 & 3; if shareability != if memory == Memory::Normal { 3 } else { 0 } { return Err(Error::Shareability); } let user = value & (1 << 6) != 0; let permissions = Permissions { user, write: value & (1 << 7) == 0, execute: value & if user { UXN } else { PXN } == 0, }; permissions.validate(memory)?; if user && (value & NON_GLOBAL == 0 || value & PXN == 0) || !user && value & UXN == 0 { return Err(Error::Permissions); } Ok(Descriptor::Page { address, memory, permissions, }) } /// The trusted adapter validates table/data backing against its scoped owner /// before any physical read. This read-only walk is not an access-lifetime pin. pub trait Reader { fn validate_table(&self, address: u64) -> Result<(), Error>; fn validate_page(&self, address: u64) -> Result<(), Error>; fn entry(&self, address: u64, index: usize) -> Result; } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Translation { pub physical: u64, pub memory: Memory, pub permissions: Permissions, } pub fn walk( geometry: Geometry, root: u64, virtual_address: u64, reader: &impl Reader, ) -> Result { geometry.validate_virtual(virtual_address)?; geometry.validate_physical_page(root)?; let mut current = root; let mut visited = [0; 4]; for (count, level) in (geometry.start_level()..=3).enumerate() { if visited[..count].contains(¤t) { return Err(Error::Cycle); } reader.validate_table(current)?; visited[count] = current; let entry = reader.entry(current, geometry.index(virtual_address, level)?)?; match decode(geometry, level, entry)? { Descriptor::Empty => return Err(Error::Unmapped), Descriptor::Table { address } => current = address, Descriptor::Page { address, memory, permissions, } => { reader.validate_page(address)?; return Ok(Translation { physical: address + (virtual_address & (PAGE - 1)), memory, permissions, }); } } } Err(Error::InvalidDescriptor) }