// SPDX-License-Identifier: MIT OR Apache-2.0 // // Copyright (c) 2020 Andre Richter //! Memory Management Unit. //! //! In order to decouple `BSP` and `arch` parts of the MMU code (to keep them pluggable), this file //! provides types for composing an architecture-agnostic description of the kernel 's virtual //! memory layout. //! //! The `BSP` provides such a description through the `bsp::memory::mmu::virt_mem_layout()` //! function. //! //! The `MMU` driver of the `arch` code uses `bsp::memory::mmu::virt_mem_layout()` to compile and //! install respective translation tables. #[cfg(target_arch = "aarch64")] #[path = "../_arch/aarch64/memory/mmu.rs"] mod arch_mmu; pub use arch_mmu::*; use core::{fmt, ops::RangeInclusive}; //-------------------------------------------------------------------------------------------------- // Public Definitions //-------------------------------------------------------------------------------------------------- /// Memory Management interfaces. pub mod interface { /// MMU functions. pub trait MMU { /// Called by the kernel during early init. Supposed to take the translation tables from the /// `BSP`-supplied `virt_mem_layout()` and install/activate them for the respective MMU. /// /// # Safety /// /// - Changes the HW's global state. unsafe fn init(&self) -> Result<(), &'static str>; } } /// Architecture agnostic translation types. #[allow(missing_docs)] #[derive(Copy, Clone)] pub enum Translation { Identity, Offset(usize), } /// Architecture agnostic memory attributes. #[allow(missing_docs)] #[derive(Copy, Clone)] pub enum MemAttributes { CacheableDRAM, Device, } /// Architecture agnostic access permissions. #[allow(missing_docs)] #[derive(Copy, Clone)] pub enum AccessPermissions { ReadOnly, ReadWrite, } /// Collection of memory attributes. #[allow(missing_docs)] #[derive(Copy, Clone)] pub struct AttributeFields { pub mem_attributes: MemAttributes, pub acc_perms: AccessPermissions, pub execute_never: bool, } /// Architecture agnostic descriptor for a memory range. #[allow(missing_docs)] pub struct TranslationDescriptor { pub name: &'static str, pub virtual_range: fn() -> RangeInclusive, pub physical_range_translation: Translation, pub attribute_fields: AttributeFields, } /// Type for expressing the kernel's virtual memory layout. pub struct KernelVirtualLayout { /// The last (inclusive) address of the address space. max_virt_addr_inclusive: usize, /// Array of descriptors for non-standard (normal cacheable DRAM) memory regions. inner: [TranslationDescriptor; NUM_SPECIAL_RANGES], } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- impl Default for AttributeFields { fn default() -> AttributeFields { AttributeFields { mem_attributes: MemAttributes::CacheableDRAM, acc_perms: AccessPermissions::ReadWrite, execute_never: true, } } } /// Human-readable output of a TranslationDescriptor. impl fmt::Display for TranslationDescriptor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Call the function to which self.range points, and dereference the result, which causes // Rust to copy the value. let start = *(self.virtual_range)().start(); let end = *(self.virtual_range)().end(); let size = end - start + 1; // log2(1024). const KIB_RSHIFT: u32 = 10; // log2(1024 * 1024). const MIB_RSHIFT: u32 = 20; let (size, unit) = if (size >> MIB_RSHIFT) > 0 { (size >> MIB_RSHIFT, "MiB") } else if (size >> KIB_RSHIFT) > 0 { (size >> KIB_RSHIFT, "KiB") } else { (size, "Byte") }; let attr = match self.attribute_fields.mem_attributes { MemAttributes::CacheableDRAM => "C", MemAttributes::Device => "Dev", }; let acc_p = match self.attribute_fields.acc_perms { AccessPermissions::ReadOnly => "RO", AccessPermissions::ReadWrite => "RW", }; let xn = if self.attribute_fields.execute_never { "PXN" } else { "PX" }; write!( f, " {:#010x} - {:#010x} | {: >3} {} | {: <3} {} {: <3} | {}", start, end, size, unit, attr, acc_p, xn, self.name ) } } impl KernelVirtualLayout<{ NUM_SPECIAL_RANGES }> { /// Create a new instance. pub const fn new(max: usize, layout: [TranslationDescriptor; NUM_SPECIAL_RANGES]) -> Self { Self { max_virt_addr_inclusive: max, inner: layout, } } /// For a virtual address, find and return the physical output address and corresponding /// attributes. /// /// If the address is not found in `inner`, return an identity mapped default with normal /// cacheable DRAM attributes. pub fn virt_addr_properties( &self, virt_addr: usize, ) -> Result<(usize, AttributeFields), &'static str> { if virt_addr > self.max_virt_addr_inclusive { return Err("Address out of range"); } for i in self.inner.iter() { if (i.virtual_range)().contains(&virt_addr) { let output_addr = match i.physical_range_translation { Translation::Identity => virt_addr, Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()), }; return Ok((output_addr, i.attribute_fields)); } } Ok((virt_addr, AttributeFields::default())) } /// Print the memory layout. pub fn print_layout(&self) { use crate::info; for i in self.inner.iter() { info!("{}", i); } } }