Files
zynq7000-rs/zynq-rt/src/mmu.rs

343 lines
13 KiB
Rust

//! 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)
);
}
}