//! The overview of translation table memory attributes is described below. //! //!| | Memory Range | Definition in Translation Table | //!|-----------------------|-------------------------|-----------------------------------| //!| DDR | 0x00000000 - 0x3FFFFFFF | Normal write-back Cacheable | //!| PL | 0x40000000 - 0xBFFFFFFF | Strongly Ordered | //!| Reserved | 0xC0000000 - 0xDFFFFFFF | Unassigned | //!| Memory mapped devices | 0xE0000000 - 0xE02FFFFF | Device Memory | //!| Reserved | 0xE0300000 - 0xE0FFFFFF | Unassigned | //!| NAND, NOR | 0xE1000000 - 0xE3FFFFFF | Device memory | //!| SRAM | 0xE4000000 - 0xE5FFFFFF | Normal write-back Cacheable | //!| Reserved | 0xE6000000 - 0xF7FFFFFF | Unassigned | //!| AMBA APB Peripherals | 0xF8000000 - 0xF8FFFFFF | Device Memory | //!| Reserved | 0xF9000000 - 0xFBFFFFFF | Unassigned | //!| Linear QSPI - XIP | 0xFC000000 - 0xFDFFFFFF | Normal write-through cacheable | //!| Reserved | 0xFE000000 - 0xFFEFFFFF | Unassigned | //!| OCM | 0xFFF00000 - 0xFFFFFFFF | Normal inner write-back cacheable | //! //! For region 0x00000000 - 0x3FFFFFFF, a system where DDR is less than 1 GB, //! region after DDR and before PL is marked as undefined/reserved in translation //! table. In 0xF8000000 - 0xF8FFFFFF, 0xF8000C00 - 0xF8000FFF, 0xF8010000 - //! 0xF88FFFFF and 0xF8F03000 to 0xF8FFFFFF are reserved but due to granual size //! of 1 MB, it is not possible to define separate regions for them. For region //! 0xFFF00000 - 0xFFFFFFFF, 0xFFF00000 to 0xFFFB0000 is reserved but due to 1MB //! granual size, it is not possible to define separate region for it. use core::arch::asm; use crate::mmu_table::MMU_L1_PAGE_TABLE; pub const OFFSET_DDR: usize = 0; pub const OFFSET_DDR_ALL_ACCESSIBLE: usize = 0x10_0000; pub const OFFSET_FPGA_SLAVE_0: usize = 0x4000_0000; pub const OFFSET_FPGA_SLAVE_1_START: usize = 0x8000_0000; pub const OFFSET_FPGA_SLAVE_1_END: usize = 0xC000_0000; pub const OFFSET_IO_PERIPHERALS_START: usize = 0xE000_0000; pub const OFFSET_IO_PERIPHERALS_END: usize = 0xE030_0000; pub const OFFSET_NAND_MEMORY: usize = 0xE100_0000; pub const OFFSET_NOR_MEMORY: usize = 0xE200_0000; pub const OFFSET_SRAM_MEMORY: usize = 0xE400_0000; pub const OFFSET_SMC_MEMORIES_END: usize = 0xE600_0000; /// 0xf8000c00 to 0xf8000fff, 0xf8010000 to 0xf88fffff and /// 0xf8f03000 to 0xf8ffffff are reserved but due to granual size of /// 1MB, it is not possible to define separate regions for them. pub const OFFSET_AMBA_APB_START: usize = 0xF800_0000; pub const OFFSET_AMBA_APB_END: usize = 0xF900_0000; pub const OFFSET_QSPI_XIP_START: usize = 0xFC00_0000; pub const OFFSET_QSPI_XIP_END: usize = 0xFE00_0000; /// 0xfff00000 to 0xfffb0000 is reserved but due to granual size of /// 1MB, it is not possible to define separate region for it pub const OFFSET_OCM_MAPPED_HIGH_START: usize = 0xFFF0_0000; pub const OFFSET_OCM_MAPPED_HIGH_END: u64 = 0x1_0000_0000; pub const MAX_DDR_SIZE: usize = 0x4000_0000; pub const ONE_MB: usize = 0x10_0000; /// First 1 MB of DDR has special treatment, access is dependant on SCU/OCM state. /// Refer to Zynq TRM UG585 p.106 for more details. pub const SEGMENTS_DDR_FULL_ACCESSIBLE: usize = (MAX_DDR_SIZE - ONE_MB) / ONE_MB; pub const SEGMENTS_FPGA_SLAVE: usize = (OFFSET_FPGA_SLAVE_1_START - OFFSET_FPGA_SLAVE_0) / ONE_MB; pub const SEGMENTS_UNASSIGNED_0: usize = (OFFSET_IO_PERIPHERALS_START - OFFSET_FPGA_SLAVE_1_END) / ONE_MB; pub const SEGMENTS_IO_PERIPHS: usize = (OFFSET_IO_PERIPHERALS_END - OFFSET_IO_PERIPHERALS_START) / ONE_MB; pub const SEGMENTS_UNASSIGNED_1: usize = (OFFSET_NAND_MEMORY - OFFSET_IO_PERIPHERALS_END) / ONE_MB; pub const SEGMENTS_NAND: usize = (OFFSET_NOR_MEMORY - OFFSET_NAND_MEMORY) / ONE_MB; pub const SEGMENTS_NOR: usize = (OFFSET_SRAM_MEMORY - OFFSET_NOR_MEMORY) / ONE_MB; pub const SEGMENTS_SRAM: usize = (OFFSET_SMC_MEMORIES_END - OFFSET_SRAM_MEMORY) / ONE_MB; pub const SEGMENTS_UNASSIGNED_2: usize = (OFFSET_AMBA_APB_START - OFFSET_SMC_MEMORIES_END) / ONE_MB; pub const SEGMENTS_AMBA_APB: usize = (OFFSET_AMBA_APB_END - OFFSET_AMBA_APB_START) / ONE_MB; pub const SEGMENTS_UNASSIGNED_3: usize = (OFFSET_QSPI_XIP_START - OFFSET_AMBA_APB_END) / ONE_MB; pub const SEGMENTS_QSPI_XIP: usize = (OFFSET_QSPI_XIP_END - OFFSET_QSPI_XIP_START) / ONE_MB; pub const SEGMENTS_UNASSIGNED_4: usize = (OFFSET_OCM_MAPPED_HIGH_START - OFFSET_QSPI_XIP_END) / ONE_MB; pub const SEGMENTS_OCM_MAPPED_HIGH: usize = ((OFFSET_OCM_MAPPED_HIGH_END - OFFSET_OCM_MAPPED_HIGH_START as u64) / ONE_MB as u64) as usize; #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum AccessPermissions { PermissionFault = 0b000, PrivilegedOnly = 0b001, NoUserWrite = 0b010, FullAccess = 0b011, _Reserved1 = 0b100, PrivilegedReadOnly = 0b101, ReadOnly = 0b110, _Reserved2 = 0b111, } impl AccessPermissions { const fn ap(&self) -> u8 { (*self as u8) & 0b11 } const fn apx(&self) -> bool { (*self as u8) > (AccessPermissions::FullAccess as u8) } } #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum L1EntryType { /// Access generates an abort exception. Indicates an unmapped virtual address. Fault = 0b00, /// Entry points to a L2 translation table, allowing 1 MB of memory to be further divided PageTable = 0b01, /// Maps a 1 MB region to a physical address. Section = 0b10, /// Special 1MB section entry which requires 16 entries in the translation table. Supersection = 0b11, } /// The ARM Cortex-A architecture reference manual p.1363 specifies these attributes in more detail. /// /// The B (Bufferable), C (Cacheable), and TEX (Type extension) bit names are inherited from /// earlier versions of the architecture. These names no longer adequately describe the function /// of the B, C, and TEX bits. #[derive(Debug, Copy, Clone)] pub struct MemoryRegionAttributesRaw { /// TEX bits type_extensions: u8, c: bool, b: bool, } impl MemoryRegionAttributesRaw { pub const fn new(type_extensions: u8, c: bool, b: bool) -> Self { Self { type_extensions, c, b, } } } #[derive(Debug, Copy, Clone)] pub enum CacheableMemoryAttribute { NonCacheable = 0b00, WriteBackWriteAlloc = 0b01, WriteThroughNoWriteAlloc = 0b10, WriteBackNoWriteAlloc = 0b11, } #[derive(Debug, Copy, Clone)] pub enum MemoryRegionAttributes { StronglyOrdered, ShareableDevice, OuterAndInnerWriteThroughNoWriteAlloc, OuterAndInnerWriteBackNoWriteAlloc, OuterAndInnerNonCacheable, OuterAndInnerWriteBackWriteAlloc, NonShareableDevice, CacheableMemory { inner: CacheableMemoryAttribute, outer: CacheableMemoryAttribute, }, } impl MemoryRegionAttributes { pub const fn as_raw(&self) -> MemoryRegionAttributesRaw { match self { MemoryRegionAttributes::StronglyOrdered => { MemoryRegionAttributesRaw::new(0b000, false, false) } MemoryRegionAttributes::ShareableDevice => { MemoryRegionAttributesRaw::new(0b000, false, true) } MemoryRegionAttributes::OuterAndInnerWriteThroughNoWriteAlloc => { MemoryRegionAttributesRaw::new(0b000, true, false) } MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc => { MemoryRegionAttributesRaw::new(0b000, true, true) } MemoryRegionAttributes::OuterAndInnerNonCacheable => { MemoryRegionAttributesRaw::new(0b001, false, false) } MemoryRegionAttributes::OuterAndInnerWriteBackWriteAlloc => { MemoryRegionAttributesRaw::new(0b001, true, true) } MemoryRegionAttributes::NonShareableDevice => { MemoryRegionAttributesRaw::new(0b010, false, false) } MemoryRegionAttributes::CacheableMemory { inner, outer } => { MemoryRegionAttributesRaw::new( 1 << 2 | (*outer as u8), (*inner as u8 & 0b10) != 0, (*inner as u8 & 0b01) != 0, ) } } } } #[derive(Debug, Copy, Clone)] pub struct SectionAttributes { /// NG bit non_global: bool, /// Implementation defined bit. p_bit: bool, shareable: bool, /// AP bits access: AccessPermissions, memory_attrs: MemoryRegionAttributesRaw, domain: u8, /// xN bit. execute_never: bool, } /// 1 MB section translation entry, mapping a 1 MB region to a physical address. #[derive(Debug, Copy, Clone)] pub struct L1Section(pub u32); impl L1Section { /// The physical base address. The uppermost 12 bits define which 1 MB of virtual address /// space are being accessed. They will be stored in the L1 section table. This address /// MUST be aligned to 1 MB. This code will panic if this is not the case. pub const fn new(phys_base: u32, section_attrs: SectionAttributes) -> Self { // Must be aligned to 1 MB if phys_base & 0x000F_FFFF != 0 { panic!("physical base address for L1 section must be aligned to 1 MB"); } let higher_bits = phys_base >> 20; let raw = (higher_bits << 20) | ((section_attrs.non_global as u32) << 17) | ((section_attrs.shareable as u32) << 16) | ((section_attrs.access.apx() as u32) << 15) | ((section_attrs.memory_attrs.type_extensions as u32) << 12) | ((section_attrs.access.ap() as u32) << 10) | ((section_attrs.p_bit as u32) << 9) | ((section_attrs.domain as u32) << 5) | ((section_attrs.execute_never as u32) << 4) | ((section_attrs.memory_attrs.c as u32) << 3) | ((section_attrs.memory_attrs.b as u32) << 2) | L1EntryType::Section as u32; L1Section(raw) } } pub const SECTION_ATTRS_DDR: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: true, access: AccessPermissions::FullAccess, // Manager domain domain: 0b1111, execute_never: false, memory_attrs: MemoryRegionAttributes::CacheableMemory { inner: CacheableMemoryAttribute::WriteBackWriteAlloc, outer: CacheableMemoryAttribute::WriteBackWriteAlloc, } .as_raw(), }; pub const SECTION_ATTRS_FPGA_SLAVES: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::FullAccess, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::StronglyOrdered.as_raw(), }; pub const SECTION_ATTRS_SHAREABLE_DEVICE: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::FullAccess, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::ShareableDevice.as_raw(), }; pub const SECTION_ATTRS_SRAM: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::FullAccess, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc.as_raw(), }; pub const SECTION_ATTRS_QSPI_XIP: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::FullAccess, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteThroughNoWriteAlloc.as_raw(), }; pub const SECTION_ATTRS_OCM_MAPPED_HIGH: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::FullAccess, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::CacheableMemory { inner: CacheableMemoryAttribute::WriteThroughNoWriteAlloc, outer: CacheableMemoryAttribute::NonCacheable, } .as_raw(), }; pub const SECTION_ATTRS_UNASSIGNED_RESERVED: SectionAttributes = SectionAttributes { non_global: false, p_bit: false, shareable: false, access: AccessPermissions::PermissionFault, domain: 0b0000, execute_never: false, memory_attrs: MemoryRegionAttributes::StronglyOrdered.as_raw(), }; pub const NUM_L1_PAGE_TABLE_ENTRIES: usize = 4096; #[repr(C, align(16384))] pub struct L1Table(pub(crate) [u32; NUM_L1_PAGE_TABLE_ENTRIES]); /// Load the MMU translation table base address into the MMU. /// /// # Safety /// /// This function is unsafe because it directly writes to the MMU related registers. It has to be /// called once in the boot code before enabling the MMU, and it should be called while the MMU is /// disabled. #[unsafe(no_mangle)] unsafe extern "C" fn load_mmu_table() { let table_base = &MMU_L1_PAGE_TABLE.0 as *const _ as u32; unsafe { core::arch::asm!( "orr {0}, {0}, #0x5B", // Outer-cacheable, WB "mcr p15, 0, {0}, c2, c0, 0", // Load table pointer inout(reg) table_base => _, options(nostack, preserves_flags) ); } }