// SPDX-License-Identifier: MIT OR Apache-2.0 // // Copyright (c) 2021 Andre Richter //! Architectural translation table. //! //! Only 64 KiB granule is supported. //! //! # Orientation //! //! Since arch modules are imported into generic modules using the path attribute, the path of this //! file is: //! //! crate::memory::mmu::translation_table::arch_translation_table use crate::{ bsp, memory, memory::{ mmu::{ arch_mmu::{Granule512MiB, Granule64KiB}, AccessPermissions, AttributeFields, MemAttributes, Page, PageSliceDescriptor, }, Address, Physical, Virtual, }, }; use core::convert; use tock_registers::{ interfaces::{Readable, Writeable}, register_bitfields, registers::InMemoryRegister, }; //-------------------------------------------------------------------------------------------------- // Private Definitions //-------------------------------------------------------------------------------------------------- // A table descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-15. register_bitfields! {u64, STAGE1_TABLE_DESCRIPTOR [ /// Physical address of the next descriptor. NEXT_LEVEL_TABLE_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16] TYPE OFFSET(1) NUMBITS(1) [ Block = 0, Table = 1 ], VALID OFFSET(0) NUMBITS(1) [ False = 0, True = 1 ] ] } // A level 3 page descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-17. register_bitfields! {u64, STAGE1_PAGE_DESCRIPTOR [ /// Unprivileged execute-never. UXN OFFSET(54) NUMBITS(1) [ False = 0, True = 1 ], /// Privileged execute-never. PXN OFFSET(53) NUMBITS(1) [ False = 0, True = 1 ], /// Physical address of the next table descriptor (lvl2) or the page descriptor (lvl3). OUTPUT_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16] /// Access flag. AF OFFSET(10) NUMBITS(1) [ False = 0, True = 1 ], /// Shareability field. SH OFFSET(8) NUMBITS(2) [ OuterShareable = 0b10, InnerShareable = 0b11 ], /// Access Permissions. AP OFFSET(6) NUMBITS(2) [ RW_EL1 = 0b00, RW_EL1_EL0 = 0b01, RO_EL1 = 0b10, RO_EL1_EL0 = 0b11 ], /// Memory attributes index into the MAIR_EL1 register. AttrIndx OFFSET(2) NUMBITS(3) [], TYPE OFFSET(1) NUMBITS(1) [ Reserved_Invalid = 0, Page = 1 ], VALID OFFSET(0) NUMBITS(1) [ False = 0, True = 1 ] ] } /// A table descriptor for 64 KiB aperture. /// /// The output points to the next table. #[derive(Copy, Clone)] #[repr(C)] struct TableDescriptor { value: u64, } /// A page descriptor with 64 KiB aperture. /// /// The output points to physical memory. #[derive(Copy, Clone)] #[repr(C)] struct PageDescriptor { value: u64, } trait StartAddr { fn virt_start_addr(&self) -> Address; } //-------------------------------------------------------------------------------------------------- // Public Definitions //-------------------------------------------------------------------------------------------------- /// Big monolithic struct for storing the translation tables. Individual levels must be 64 KiB /// aligned, so the lvl3 is put first. #[repr(C)] #[repr(align(65536))] pub struct FixedSizeTranslationTable { /// Page descriptors, covering 64 KiB windows per entry. lvl3: [[PageDescriptor; 8192]; NUM_TABLES], /// Table descriptors, covering 512 MiB windows. lvl2: [TableDescriptor; NUM_TABLES], /// Index of the next free MMIO page. cur_l3_mmio_index: usize, /// Have the tables been initialized? initialized: bool, } //-------------------------------------------------------------------------------------------------- // Private Code //-------------------------------------------------------------------------------------------------- impl StartAddr for [T; N] { fn virt_start_addr(&self) -> Address { Address::new(self as *const _ as usize) } } impl TableDescriptor { /// Create an instance. /// /// Descriptor is invalid by default. pub const fn new_zeroed() -> Self { Self { value: 0 } } /// Create an instance pointing to the supplied address. pub fn from_next_lvl_table_addr(phys_next_lvl_table_addr: Address) -> Self { let val = InMemoryRegister::::new(0); let shifted = phys_next_lvl_table_addr.into_usize() >> Granule64KiB::SHIFT; val.write( STAGE1_TABLE_DESCRIPTOR::NEXT_LEVEL_TABLE_ADDR_64KiB.val(shifted as u64) + STAGE1_TABLE_DESCRIPTOR::TYPE::Table + STAGE1_TABLE_DESCRIPTOR::VALID::True, ); TableDescriptor { value: val.get() } } } /// Convert the kernel's generic memory attributes to HW-specific attributes of the MMU. impl convert::From for tock_registers::fields::FieldValue { fn from(attribute_fields: AttributeFields) -> Self { // Memory attributes. let mut desc = match attribute_fields.mem_attributes { MemAttributes::CacheableDRAM => { STAGE1_PAGE_DESCRIPTOR::SH::InnerShareable + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(memory::mmu::arch_mmu::mair::NORMAL) } MemAttributes::Device => { STAGE1_PAGE_DESCRIPTOR::SH::OuterShareable + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(memory::mmu::arch_mmu::mair::DEVICE) } }; // Access Permissions. desc += match attribute_fields.acc_perms { AccessPermissions::ReadOnly => STAGE1_PAGE_DESCRIPTOR::AP::RO_EL1, AccessPermissions::ReadWrite => STAGE1_PAGE_DESCRIPTOR::AP::RW_EL1, }; // The execute-never attribute is mapped to PXN in AArch64. desc += if attribute_fields.execute_never { STAGE1_PAGE_DESCRIPTOR::PXN::True } else { STAGE1_PAGE_DESCRIPTOR::PXN::False }; // Always set unprivileged exectue-never as long as userspace is not implemented yet. desc += STAGE1_PAGE_DESCRIPTOR::UXN::True; desc } } /// Convert the HW-specific attributes of the MMU to kernel's generic memory attributes. impl convert::TryFrom> for AttributeFields { type Error = &'static str; fn try_from( desc: InMemoryRegister, ) -> Result { let mem_attributes = match desc.read(STAGE1_PAGE_DESCRIPTOR::AttrIndx) { memory::mmu::arch_mmu::mair::NORMAL => MemAttributes::CacheableDRAM, memory::mmu::arch_mmu::mair::DEVICE => MemAttributes::Device, _ => return Err("Unexpected memory attribute"), }; let acc_perms = match desc.read_as_enum(STAGE1_PAGE_DESCRIPTOR::AP) { Some(STAGE1_PAGE_DESCRIPTOR::AP::Value::RO_EL1) => AccessPermissions::ReadOnly, Some(STAGE1_PAGE_DESCRIPTOR::AP::Value::RW_EL1) => AccessPermissions::ReadWrite, _ => return Err("Unexpected access permission"), }; let execute_never = desc.read(STAGE1_PAGE_DESCRIPTOR::PXN) > 0; Ok(AttributeFields { mem_attributes, acc_perms, execute_never, }) } } impl PageDescriptor { /// Create an instance. /// /// Descriptor is invalid by default. pub const fn new_zeroed() -> Self { Self { value: 0 } } /// Create an instance. pub fn from_output_page( phys_output_page: *const Page, attribute_fields: &AttributeFields, ) -> Self { let val = InMemoryRegister::::new(0); let shifted = phys_output_page as u64 >> Granule64KiB::SHIFT; val.write( STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB.val(shifted) + STAGE1_PAGE_DESCRIPTOR::AF::True + STAGE1_PAGE_DESCRIPTOR::TYPE::Page + STAGE1_PAGE_DESCRIPTOR::VALID::True + (*attribute_fields).into(), ); Self { value: val.get() } } /// Returns the valid bit. fn is_valid(&self) -> bool { InMemoryRegister::::new(self.value) .is_set(STAGE1_PAGE_DESCRIPTOR::VALID) } /// Returns the output page. fn output_page(&self) -> *const Page { let shifted = InMemoryRegister::::new(self.value) .read(STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB); let addr = shifted << Granule64KiB::SHIFT; addr as *const Page } /// Returns the attributes. fn try_attributes(&self) -> Result { InMemoryRegister::::new(self.value).try_into() } } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- impl memory::mmu::AssociatedTranslationTable for memory::mmu::AddressSpace where [u8; Self::SIZE >> Granule512MiB::SHIFT]: Sized, { type TableStartFromBottom = FixedSizeTranslationTable<{ Self::SIZE >> Granule512MiB::SHIFT }>; } impl FixedSizeTranslationTable { // Reserve the last 256 MiB of the address space for MMIO mappings. const L2_MMIO_START_INDEX: usize = NUM_TABLES - 1; const L3_MMIO_START_INDEX: usize = 8192 / 2; /// Create an instance. #[allow(clippy::assertions_on_constants)] const fn _new(for_precompute: bool) -> Self { assert!(bsp::memory::mmu::KernelGranule::SIZE == Granule64KiB::SIZE); // Can't have a zero-sized address space. assert!(NUM_TABLES > 0); Self { lvl3: [[PageDescriptor::new_zeroed(); 8192]; NUM_TABLES], lvl2: [TableDescriptor::new_zeroed(); NUM_TABLES], cur_l3_mmio_index: Self::L3_MMIO_START_INDEX, initialized: for_precompute, } } pub const fn new_for_precompute() -> Self { Self::_new(true) } #[cfg(test)] pub fn new_for_runtime() -> Self { Self::_new(false) } /// The start address of the table's MMIO range. #[inline(always)] fn mmio_start_addr(&self) -> Address { Address::new( (Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT) | (Self::L3_MMIO_START_INDEX << Granule64KiB::SHIFT), ) } /// The inclusive end address of the table's MMIO range. #[inline(always)] fn mmio_end_addr_inclusive(&self) -> Address { Address::new( (Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT) | (8191 << Granule64KiB::SHIFT) | (Granule64KiB::SIZE - 1), ) } /// Helper to calculate the lvl2 and lvl3 indices from an address. #[inline(always)] fn lvl2_lvl3_index_from_page( &self, virt_page: *const Page, ) -> Result<(usize, usize), &'static str> { let addr = virt_page as usize; let lvl2_index = addr >> Granule512MiB::SHIFT; let lvl3_index = (addr & Granule512MiB::MASK) >> Granule64KiB::SHIFT; if lvl2_index > (NUM_TABLES - 1) { return Err("Virtual page is out of bounds of translation table"); } Ok((lvl2_index, lvl3_index)) } /// Returns the PageDescriptor corresponding to the supplied page address. #[inline(always)] fn page_descriptor_from_page( &self, virt_page: *const Page, ) -> Result<&PageDescriptor, &'static str> { let (lvl2_index, lvl3_index) = self.lvl2_lvl3_index_from_page(virt_page)?; let desc = &self.lvl3[lvl2_index][lvl3_index]; Ok(desc) } /// Sets the PageDescriptor corresponding to the supplied page address. /// /// Doesn't allow overriding an already valid page. #[inline(always)] fn set_page_descriptor_from_page( &mut self, virt_page: *const Page, new_desc: &PageDescriptor, ) -> Result<(), &'static str> { let (lvl2_index, lvl3_index) = self.lvl2_lvl3_index_from_page(virt_page)?; let desc = &mut self.lvl3[lvl2_index][lvl3_index]; if desc.is_valid() { return Err("Virtual page is already mapped"); } *desc = *new_desc; Ok(()) } } //------------------------------------------------------------------------------ // OS Interface Code //------------------------------------------------------------------------------ impl memory::mmu::translation_table::interface::TranslationTable for FixedSizeTranslationTable { fn init(&mut self) -> Result<(), &'static str> { if self.initialized { return Ok(()); } // Populate the l2 entries. for (lvl2_nr, lvl2_entry) in self.lvl2.iter_mut().enumerate() { let virt_table_addr = self.lvl3[lvl2_nr].virt_start_addr(); let phys_table_addr = memory::mmu::try_kernel_virt_addr_to_phys_addr(virt_table_addr)?; let new_desc = TableDescriptor::from_next_lvl_table_addr(phys_table_addr); *lvl2_entry = new_desc; } self.cur_l3_mmio_index = Self::L3_MMIO_START_INDEX; self.initialized = true; Ok(()) } unsafe fn map_pages_at( &mut self, virt_pages: &PageSliceDescriptor, phys_pages: &PageSliceDescriptor, attr: &AttributeFields, ) -> Result<(), &'static str> { assert!(self.initialized, "Translation tables not initialized"); let v = virt_pages.as_slice(); let p = phys_pages.as_slice(); // No work to do for empty slices. if v.is_empty() { return Ok(()); } if v.len() != p.len() { return Err("Tried to map page slices with unequal sizes"); } if p.last().unwrap().as_ptr() >= bsp::memory::mmu::phys_addr_space_end_page() { return Err("Tried to map outside of physical address space"); } let iter = p.iter().zip(v.iter()); for (phys_page, virt_page) in iter { let new_desc = PageDescriptor::from_output_page(phys_page.as_ptr(), attr); let virt_page = virt_page.as_ptr(); self.set_page_descriptor_from_page(virt_page, &new_desc)?; } Ok(()) } fn next_mmio_virt_page_slice( &mut self, num_pages: usize, ) -> Result, &'static str> { assert!(self.initialized, "Translation tables not initialized"); if num_pages == 0 { return Err("num_pages == 0"); } if (self.cur_l3_mmio_index + num_pages) > 8191 { return Err("Not enough MMIO space left"); } let addr = Address::new( (Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT) | (self.cur_l3_mmio_index << Granule64KiB::SHIFT), ); self.cur_l3_mmio_index += num_pages; Ok(PageSliceDescriptor::from_addr(addr, num_pages)) } fn is_virt_page_slice_mmio(&self, virt_pages: &PageSliceDescriptor) -> bool { let start_addr = virt_pages.start_addr(); let end_addr_inclusive = virt_pages.end_addr_inclusive(); for i in [start_addr, end_addr_inclusive].iter() { if (*i >= self.mmio_start_addr()) && (*i <= self.mmio_end_addr_inclusive()) { return true; } } false } fn try_virt_page_to_phys_page( &self, virt_page: *const Page, ) -> Result<*const Page, &'static str> { let page_desc = self.page_descriptor_from_page(virt_page)?; if !page_desc.is_valid() { return Err("Page marked invalid"); } Ok(page_desc.output_page()) } fn try_page_attributes( &self, virt_page: *const Page, ) -> Result { let page_desc = self.page_descriptor_from_page(virt_page)?; if !page_desc.is_valid() { return Err("Page marked invalid"); } page_desc.try_attributes() } /// Try to translate a virtual address to a physical address. /// /// Will only succeed if there exists a valid mapping for the input address. fn try_virt_addr_to_phys_addr( &self, virt_addr: Address, ) -> Result, &'static str> { let page = self.try_virt_page_to_phys_page(virt_addr.as_page_ptr())?; Ok(Address::new(page as usize + virt_addr.offset_into_page())) } } //-------------------------------------------------------------------------------------------------- // Testing //-------------------------------------------------------------------------------------------------- #[cfg(test)] pub type MinSizeTranslationTable = FixedSizeTranslationTable<1>; #[cfg(test)] mod tests { use super::*; use test_macros::kernel_test; /// Check if the size of `struct TableDescriptor` is as expected. #[kernel_test] fn size_of_tabledescriptor_equals_64_bit() { assert_eq!( core::mem::size_of::(), core::mem::size_of::() ); } /// Check if the size of `struct PageDescriptor` is as expected. #[kernel_test] fn size_of_pagedescriptor_equals_64_bit() { assert_eq!( core::mem::size_of::(), core::mem::size_of::() ); } }