/* * SPDX-License-Identifier: MIT OR BlueOak-1.0.0 * Copyright (c) 2018-2019 Andre Richter * Copyright (c) Berkus Decker * Original code distributed under MIT, additional changes are under BlueOak-1.0.0 */ //! MMU initialisation. //! //! Paging is mostly based on [previous version](https://os.phil-opp.com/page-tables/) of //! Phil Opp's [paging guide](https://os.phil-opp.com/paging-implementation/) and //! [ARMv8 ARM memory addressing](https://static.docs.arm.com/100940/0100/armv8_a_address%20translation_100940_0100_en.pdf). use { crate::{ arch::aarch64::memory::{ get_virt_addr_properties, AttributeFields, /*FrameAllocator, PhysAddr, VirtAddr,*/ }, println, }, // bitflags::bitflags, core::{ // convert::TryInto, // fmt, marker::PhantomData, ops::{Index, IndexMut}, // ptr::Unique, }, cortex_a::{ asm::barrier, registers::{ID_AA64MMFR0_EL1, SCTLR_EL1, TCR_EL1, TTBR0_EL1}, }, tock_registers::{ fields::FieldValue, interfaces::{ReadWriteable, Readable, Writeable}, register_bitfields, }, // ux::*, }; mod mair { use cortex_a::registers::MAIR_EL1; use tock_registers::interfaces::Writeable; /// Setup function for the MAIR_EL1 register. pub fn set_up() { // Define the three memory types that we will map. Normal DRAM, Uncached and device. MAIR_EL1.write( // Attribute 2 -- Device Memory MAIR_EL1::Attr2_Device::nonGathering_nonReordering_EarlyWriteAck // Attribute 1 -- Non Cacheable DRAM + MAIR_EL1::Attr1_Normal_Outer::NonCacheable + MAIR_EL1::Attr1_Normal_Inner::NonCacheable // Attribute 0 -- Regular Cacheable + MAIR_EL1::Attr0_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc + MAIR_EL1::Attr0_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc, ); } // Three descriptive consts for indexing into the correct MAIR_EL1 attributes. pub mod attr { pub const NORMAL: u64 = 0; pub const NORMAL_NON_CACHEABLE: u64 = 1; pub const DEVICE_NGNRE: u64 = 2; // DEVICE_GRE // DEVICE_NGNRNE } } /// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported MMU features. /// Print the current state of TCR register. pub fn print_features() { // use crate::cortex_a::regs::RegisterReadWrite; let sctlr = SCTLR_EL1.extract(); if let Some(SCTLR_EL1::M::Value::Enable) = sctlr.read_as_enum(SCTLR_EL1::M) { println!("[i] MMU currently enabled"); } if let Some(SCTLR_EL1::I::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::I) { println!("[i] MMU I-cache enabled"); } if let Some(SCTLR_EL1::C::Value::Cacheable) = sctlr.read_as_enum(SCTLR_EL1::C) { println!("[i] MMU D-cache enabled"); } let mmfr = ID_AA64MMFR0_EL1.extract(); if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) = mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4) { println!("[i] MMU: 4 KiB granule supported!"); } if let Some(ID_AA64MMFR0_EL1::TGran16::Value::Supported) = mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran16) { println!("[i] MMU: 16 KiB granule supported!"); } if let Some(ID_AA64MMFR0_EL1::TGran64::Value::Supported) = mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran64) { println!("[i] MMU: 64 KiB granule supported!"); } match mmfr.read_as_enum(ID_AA64MMFR0_EL1::ASIDBits) { Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_16) => { println!("[i] MMU: 16 bit ASIDs supported!") } Some(ID_AA64MMFR0_EL1::ASIDBits::Value::Bits_8) => { println!("[i] MMU: 8 bit ASIDs supported!") } _ => println!("[i] MMU: Invalid ASID bits specified!"), } match mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange) { Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_32) => { println!("[i] MMU: Up to 32 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_36) => { println!("[i] MMU: Up to 36 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) => { println!("[i] MMU: Up to 40 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_42) => { println!("[i] MMU: Up to 42 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_44) => { println!("[i] MMU: Up to 44 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_48) => { println!("[i] MMU: Up to 48 Bit physical address range supported!") } Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_52) => { println!("[i] MMU: Up to 52 Bit physical address range supported!") } _ => println!("[i] MMU: Invalid PARange specified!"), } let tcr = TCR_EL1.extract(); match tcr.read_as_enum(TCR_EL1::IPS) { Some(TCR_EL1::IPS::Value::Bits_32) => { println!("[i] MMU: 32 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_36) => { println!("[i] MMU: 36 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_40) => { println!("[i] MMU: 40 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_42) => { println!("[i] MMU: 42 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_44) => { println!("[i] MMU: 44 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_48) => { println!("[i] MMU: 48 Bit intermediate physical address size supported!") } Some(TCR_EL1::IPS::Value::Bits_52) => { println!("[i] MMU: 52 Bit intermediate physical address size supported!") } _ => println!("[i] MMU: Invalid IPS specified!"), } match tcr.read_as_enum(TCR_EL1::TG0) { Some(TCR_EL1::TG0::Value::KiB_4) => println!("[i] MMU: TTBR0 4 KiB granule active!"), Some(TCR_EL1::TG0::Value::KiB_16) => println!("[i] MMU: TTBR0 16 KiB granule active!"), Some(TCR_EL1::TG0::Value::KiB_64) => println!("[i] MMU: TTBR0 64 KiB granule active!"), _ => println!("[i] MMU: Invalid TTBR0 granule size specified!"), } let t0sz = tcr.read(TCR_EL1::T0SZ); println!("[i] MMU: T0sz = 64-{} = {} bits", t0sz, 64 - t0sz); match tcr.read_as_enum(TCR_EL1::TG1) { Some(TCR_EL1::TG1::Value::KiB_4) => println!("[i] MMU: TTBR1 4 KiB granule active!"), Some(TCR_EL1::TG1::Value::KiB_16) => println!("[i] MMU: TTBR1 16 KiB granule active!"), Some(TCR_EL1::TG1::Value::KiB_64) => println!("[i] MMU: TTBR1 64 KiB granule active!"), _ => println!("[i] MMU: Invalid TTBR1 granule size specified!"), } let t1sz = tcr.read(TCR_EL1::T1SZ); println!("[i] MMU: T1sz = 64-{} = {} bits", t1sz, 64 - t1sz); } register_bitfields! { u64, // AArch64 Reference Manual page 2150, D5-2445 STAGE1_DESCRIPTOR [ // In table descriptors NSTable_EL3 OFFSET(63) NUMBITS(1) [], /// Access Permissions for subsequent tables APTable OFFSET(61) NUMBITS(2) [ RW_EL1 = 0b00, RW_EL1_EL0 = 0b01, RO_EL1 = 0b10, RO_EL1_EL0 = 0b11 ], // User execute-never for subsequent tables UXNTable OFFSET(60) NUMBITS(1) [ Execute = 0, NeverExecute = 1 ], /// Privileged execute-never for subsequent tables PXNTable OFFSET(59) NUMBITS(1) [ Execute = 0, NeverExecute = 1 ], // In block descriptors // OS-specific data OSData OFFSET(55) NUMBITS(4) [], // User execute-never UXN OFFSET(54) NUMBITS(1) [ Execute = 0, NeverExecute = 1 ], /// Privileged execute-never PXN OFFSET(53) NUMBITS(1) [ Execute = 0, NeverExecute = 1 ], // @fixme ?? where is this described CONTIGUOUS OFFSET(52) NUMBITS(1) [ False = 0, True = 1 ], // @fixme ?? where is this described DIRTY OFFSET(51) NUMBITS(1) [ False = 0, True = 1 ], /// Various address fields, depending on use case LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21] NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12] // @fixme ?? where is this described NON_GLOBAL OFFSET(11) NUMBITS(1) [ False = 0, True = 1 ], /// 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 ], NS_EL3 OFFSET(5) NUMBITS(1) [], /// Memory attributes index into the MAIR_EL1 register AttrIndx OFFSET(2) NUMBITS(3) [], TYPE OFFSET(1) NUMBITS(1) [ Block = 0, Table = 1 ], VALID OFFSET(0) NUMBITS(1) [ False = 0, True = 1 ] ] } /// A function that maps the generic memory range attributes to HW-specific /// attributes of the MMU. fn into_mmu_attributes( attribute_fields: AttributeFields, ) -> FieldValue { use super::{AccessPermissions, MemAttributes}; // Memory attributes let mut desc = match attribute_fields.mem_attributes { MemAttributes::CacheableDRAM => { STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL) } MemAttributes::NonCacheableDRAM => { STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::NORMAL_NON_CACHEABLE) } MemAttributes::Device => { STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::attr::DEVICE_NGNRE) } }; // Access Permissions desc += match attribute_fields.acc_perms { AccessPermissions::ReadOnly => STAGE1_DESCRIPTOR::AP::RO_EL1, AccessPermissions::ReadWrite => STAGE1_DESCRIPTOR::AP::RW_EL1, }; // Execute Never desc += if attribute_fields.execute_never { STAGE1_DESCRIPTOR::PXN::NeverExecute } else { STAGE1_DESCRIPTOR::PXN::Execute }; desc } /* * With 4k page granule, a virtual address is split into 4 lookup parts * spanning 9 bits each: * * _______________________________________________ * | | | | | | | * | signx | Lv0 | Lv1 | Lv2 | Lv3 | off | * |_______|_______|_______|_______|_______|_______| * 63-48 47-39 38-30 29-21 20-12 11-00 * * mask page size * * Lv0: FF8000000000 -- * Lv1: 7FC0000000 1G * Lv2: 3FE00000 2M * Lv3: 1FF000 4K * off: FFF * * RPi3 supports 64K and 4K granules, also 40-bit physical addresses. * It also can address only 1G physical memory, so these 40-bit phys addresses are a fake. * * 48-bit virtual address space; different mappings in VBAR0 (EL0) and VBAR1 (EL1+). */ /// Number of entries in a 4KiB mmu table. pub const NUM_ENTRIES_4KIB: u64 = 512; /// Trait for abstracting over the possible page sizes, 4KiB, 16KiB, 2MiB, 1GiB. pub trait PageSize: Copy + Eq + PartialOrd + Ord { /// The page size in bytes. const SIZE: u64; /// A string representation of the page size for debug output. const SIZE_AS_DEBUG_STR: &'static str; /// The page shift in bits. const SHIFT: usize; /// The page mask in bits. const MASK: u64; } /// This trait is implemented for 4KiB, 16KiB, and 2MiB pages, but not for 1GiB pages. pub trait NotGiantPageSize: PageSize {} // @todo doesn't have to be pub?? /// A standard 4KiB page. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Size4KiB {} impl PageSize for Size4KiB { const SIZE: u64 = 4096; const SIZE_AS_DEBUG_STR: &'static str = "4KiB"; const SHIFT: usize = 12; const MASK: u64 = 0xfff; } impl NotGiantPageSize for Size4KiB {} /// A “huge” 2MiB page. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Size2MiB {} impl PageSize for Size2MiB { const SIZE: u64 = Size4KiB::SIZE * NUM_ENTRIES_4KIB; const SIZE_AS_DEBUG_STR: &'static str = "2MiB"; const SHIFT: usize = 21; const MASK: u64 = 0x1fffff; } impl NotGiantPageSize for Size2MiB {} type EntryFlags = tock_registers::fields::FieldValue; // type EntryRegister = register::LocalRegisterCopy; /// L0 table -- only pointers to L1 tables pub enum PageGlobalDirectory {} /// L1 tables -- pointers to L2 tables or giant 1GiB pages pub enum PageUpperDirectory {} /// L2 tables -- pointers to L3 tables or huge 2MiB pages pub enum PageDirectory {} /// L3 tables -- only pointers to 4/16KiB pages pub enum PageTable {} /// Shared trait for specific table levels. pub trait TableLevel {} /// Shared trait for hierarchical table levels. /// /// Specifies what is the next level of page table hierarchy. pub trait HierarchicalLevel: TableLevel { /// Level of the next translation table below this one. type NextLevel: TableLevel; } impl TableLevel for PageGlobalDirectory {} impl TableLevel for PageUpperDirectory {} impl TableLevel for PageDirectory {} impl TableLevel for PageTable {} impl HierarchicalLevel for PageGlobalDirectory { type NextLevel = PageUpperDirectory; } impl HierarchicalLevel for PageUpperDirectory { type NextLevel = PageDirectory; } impl HierarchicalLevel for PageDirectory { type NextLevel = PageTable; } // PageTables do not have next level, therefore they are not HierarchicalLevel /// MMU address translation table. /// Contains just u64 internally, provides enum interface on top #[repr(C)] #[repr(align(4096))] pub struct Table { entries: [u64; NUM_ENTRIES_4KIB as usize], level: PhantomData, } // Implementation code shared for all levels of page tables impl Table where L: TableLevel, { /// Zero out entire table. pub fn zero(&mut self) { for entry in self.entries.iter_mut() { *entry = 0; } } } impl Index for Table where L: TableLevel, { type Output = u64; fn index(&self, index: usize) -> &u64 { &self.entries[index] } } impl IndexMut for Table where L: TableLevel, { fn index_mut(&mut self, index: usize) -> &mut u64 { &mut self.entries[index] } } /// Type-safe enum wrapper covering Table's 64-bit entries. #[derive(Clone)] // #[repr(transparent)] enum PageTableEntry { /// Empty page table entry. Invalid, /// Table descriptor is a L0, L1 or L2 table pointing to another table. /// L0 tables can only point to L1 tables. /// A descriptor pointing to the next page table. TableDescriptor(EntryFlags), /// A Level2 block descriptor with 2 MiB aperture. /// /// The output points to physical memory. Lvl2BlockDescriptor(EntryFlags), /// A page PageTableEntry::descriptor with 4 KiB aperture. /// /// The output points to physical memory. PageDescriptor(EntryFlags), } /// A descriptor pointing to the next page table. (within PageTableEntry enum) // struct TableDescriptor(register::FieldValue); impl PageTableEntry { fn new_table_descriptor(next_lvl_table_addr: usize) -> Result { if next_lvl_table_addr % Size4KiB::SIZE as usize != 0 { // @todo SIZE must be usize return Err("TableDescriptor: Address is not 4 KiB aligned."); } let shifted = next_lvl_table_addr >> Size4KiB::SHIFT; Ok(PageTableEntry::TableDescriptor( STAGE1_DESCRIPTOR::VALID::True + STAGE1_DESCRIPTOR::TYPE::Table + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), )) } } /// A Level2 block descriptor with 2 MiB aperture. /// /// The output points to physical memory. // struct Lvl2BlockDescriptor(register::FieldValue); impl PageTableEntry { fn new_lvl2_block_descriptor( output_addr: usize, attribute_fields: AttributeFields, ) -> Result { if output_addr % Size2MiB::SIZE as usize != 0 { return Err("BlockDescriptor: Address is not 2 MiB aligned."); } let shifted = output_addr >> Size2MiB::SHIFT; Ok(PageTableEntry::Lvl2BlockDescriptor( STAGE1_DESCRIPTOR::VALID::True + STAGE1_DESCRIPTOR::AF::True + into_mmu_attributes(attribute_fields) + STAGE1_DESCRIPTOR::TYPE::Block + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(shifted as u64), )) } } /// A page descriptor with 4 KiB aperture. /// /// The output points to physical memory. impl PageTableEntry { fn new_page_descriptor( output_addr: usize, attribute_fields: AttributeFields, ) -> Result { if output_addr % Size4KiB::SIZE as usize != 0 { return Err("PageDescriptor: Address is not 4 KiB aligned."); } let shifted = output_addr >> Size4KiB::SHIFT; Ok(PageTableEntry::PageDescriptor( STAGE1_DESCRIPTOR::VALID::True + STAGE1_DESCRIPTOR::AF::True + into_mmu_attributes(attribute_fields) + STAGE1_DESCRIPTOR::TYPE::Table + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), )) } } impl From for PageTableEntry { fn from(_val: u64) -> PageTableEntry { // xxx0 -> Invalid // xx11 -> TableDescriptor on L0, L1 and L2 // xx10 -> Block Entry L1 and L2 // xx11 -> PageDescriptor L3 PageTableEntry::Invalid } } impl From for u64 { fn from(val: PageTableEntry) -> u64 { match val { PageTableEntry::Invalid => 0, PageTableEntry::TableDescriptor(x) | PageTableEntry::Lvl2BlockDescriptor(x) | PageTableEntry::PageDescriptor(x) => x.value, } } } static mut LVL2_TABLE: Table = Table:: { entries: [0; NUM_ENTRIES_4KIB as usize], level: PhantomData, }; static mut LVL3_TABLE: Table = Table:: { entries: [0; NUM_ENTRIES_4KIB as usize], level: PhantomData, }; trait BaseAddr { fn base_addr_u64(&self) -> u64; fn base_addr_usize(&self) -> usize; } impl BaseAddr for [u64; 512] { fn base_addr_u64(&self) -> u64 { self as *const u64 as u64 } fn base_addr_usize(&self) -> usize { self as *const u64 as usize } } /// Set up identity mapped page tables for the first 1 gigabyte of address space. /// default: 880 MB ARM ram, 128MB VC /// /// # Safety /// /// Completely unsafe, we're in the hardware land! Incorrectly initialised tables will just /// restart the CPU. pub unsafe fn init() -> Result<(), &'static str> { // Prepare the memory attribute indirection register. mair::set_up(); // Point the first 2 MiB of virtual addresses to the follow-up LVL3 // page-table. LVL2_TABLE.entries[0] = PageTableEntry::new_table_descriptor(LVL3_TABLE.entries.base_addr_usize())?.into(); // Fill the rest of the LVL2 (2 MiB) entries as block descriptors. // // Notice the skip(1) which makes the iteration start at the second 2 MiB // block (0x20_0000). for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { let virt_addr = block_descriptor_nr << Size2MiB::SHIFT; let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { Err(s) => return Err(s), Ok((a, b)) => (a, b), }; let block_desc = match PageTableEntry::new_lvl2_block_descriptor(output_addr, attribute_fields) { Err(s) => return Err(s), Ok(desc) => desc, }; *entry = block_desc.into(); } // Finally, fill the single LVL3 table (4 KiB granule). for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() { let virt_addr = page_descriptor_nr << Size4KiB::SHIFT; let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { Err(s) => return Err(s), Ok((a, b)) => (a, b), }; let page_desc = match PageTableEntry::new_page_descriptor(output_addr, attribute_fields) { Err(s) => return Err(s), Ok(desc) => desc, }; *entry = page_desc.into(); } // Point to the LVL2 table base address in TTBR0. TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // User (lo-)space addresses // TTBR1_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Kernel (hi-)space addresses // Configure various settings of stage 1 of the EL1 translation regime. let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange); TCR_EL1.write( TCR_EL1::TBI0::Ignored // @todo TBI1 also set to Ignored?? + TCR_EL1::IPS.val(ips) // Intermediate Physical Address Size // ttbr0 user memory addresses + TCR_EL1::TG0::KiB_4 // 4 KiB granule + TCR_EL1::SH0::Inner + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + TCR_EL1::EPD0::EnableTTBR0Walks + TCR_EL1::T0SZ.val(34) // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2 // ttbr1 kernel memory addresses + TCR_EL1::TG1::KiB_4 // 4 KiB granule + TCR_EL1::SH1::Inner + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + TCR_EL1::EPD1::EnableTTBR1Walks + TCR_EL1::T1SZ.val(34), // ARMv8ARM Table D5-11 minimum TxSZ for starting table level 2 ); // Switch the MMU on. // // First, force all previous changes to be seen before the MMU is enabled. barrier::isb(barrier::SY); // use cortex_a::regs::RegisterReadWrite; // Enable the MMU and turn on data and instruction caching. SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); // Force MMU init to complete before next instruction /* * Invalidate the local I-cache so that any instructions fetched * speculatively from the PoC are discarded, since they may have * been dynamically patched at the PoU. */ barrier::isb(barrier::SY); Ok(()) }