From 5666fcbec95bc2fa554163112ef266129199fd4b Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Mon, 21 Jan 2019 02:00:24 +0200 Subject: [PATCH] Add mmu::init() from Andre Richter's tutorial * Does not work on real rpi Gpu memory is corrupted with semi-random patterns --- src/arch/aarch64/mmu.rs | 249 ++++++++++++++++++++++++++++++++++++++++ src/arch/aarch64/mod.rs | 3 +- src/main.rs | 4 + src/platform/rpi3.rs | 4 +- 4 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 src/arch/aarch64/mmu.rs diff --git a/src/arch/aarch64/mmu.rs b/src/arch/aarch64/mmu.rs new file mode 100644 index 0000000..7bd4e5d --- /dev/null +++ b/src/arch/aarch64/mmu.rs @@ -0,0 +1,249 @@ +/* + * MIT License + * + * Copyright (c) 2018-2019 Andre Richter + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// use super::uart; +use cortex_a::{barrier, regs::*}; +use register::register_bitfields; + +/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported +/// MMU features. +// pub fn print_features(uart: &uart::Uart) { +// let mmfr = ID_AA64MMFR0_EL1.extract(); + +// if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) = +// mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4) +// { +// uart.puts("[i] MMU: 4 KiB granule supported!\n"); +// } + +// if let Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) = +// mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange) +// { +// uart.puts("[i] MMU: Up to 40 Bit physical address range supported!\n"); +// } +// } + +register_bitfields! {u64, + // AArch64 Reference Manual page 2150 + STAGE1_DESCRIPTOR [ + /// Execute-never + XN OFFSET(54) 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] + + /// 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) [ + Block = 0, + Table = 1 + ], + + VALID OFFSET(0) NUMBITS(1) [ + False = 0, + True = 1 + ] + ] +} + +trait BaseAddr { + fn base_addr(&self) -> u64; +} + +impl BaseAddr for [u64; 512] { + fn base_addr(&self) -> u64 { + self as *const u64 as u64 + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// We need a wrapper struct here so that we can make use of the align attribute. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; +static mut SINGLE_LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 gigabyte of address +/// space. +pub unsafe fn init() { + // First, define the two memory types that we will map. Normal DRAM and + // device. + MAIR_EL1.write( + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Device + + MAIR_EL1::Attr1_LOW_DEVICE::Device_nGnRE + // Attribute 0 + + MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + + MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc, + ); + + // Two descriptive consts for indexing into the correct MAIR_EL1 attributes. + mod mair { + pub const NORMAL: u64 = 0; + pub const DEVICE: u64 = 1; + } + + // Set up the first LVL2 entry, pointing to a 4KiB table base address. + let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12; + LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base)) + .value; + + // For educational purposes and fun, let the start of the second 2 MiB block + // point to the 2 MiB aperture which contains the UART's base address. + // let uart_phys_base: u64 = (uart::UART_PHYS_BASE >> 21).into(); + // LVL2_TABLE.entries[1] = (STAGE1_DESCRIPTOR::VALID::True + // + STAGE1_DESCRIPTOR::TYPE::Block + // + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + // + STAGE1_DESCRIPTOR::AP::RW_EL1 + // + STAGE1_DESCRIPTOR::SH::OuterShareable + // + STAGE1_DESCRIPTOR::AF::True + // + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(uart_phys_base) + // + STAGE1_DESCRIPTOR::XN::True) + // .value; + + // Fill the rest of the LVL2 (2MiB) entries as block + // descriptors. Differentiate between normal and device mem. + let mmio_base: u64 = (super::BcmHost::get_peripheral_address() >> 21).into(); + let common = STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Block + + STAGE1_DESCRIPTOR::AP::RW_EL1 + + STAGE1_DESCRIPTOR::AF::True + + STAGE1_DESCRIPTOR::XN::True; + + // Notice the skip(2) + for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) { + let j: u64 = i as u64; + + let mem_attr = if j >= mmio_base { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } else { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + }; + + *entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value; + } + + // Finally, fill the single LVL3 table (4 KiB granule). Differentiate + // between code+RO and RW pages. + // + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols. + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static mut __ro_start: u64; + + // The non-inclusive end of the read-only area, aka the address of the + // first byte _after_ the RO area. + static mut __ro_end: u64; + } + + const PAGESIZE: u64 = 4096; + let ro_first_page_index: u64 = &__ro_start as *const _ as u64 / PAGESIZE; + + // Notice the subtraction to calculate the last page index of the RO area + // and not the first page index after the RO area. + let ro_last_page_index: u64 = (&__ro_end as *const _ as u64 / PAGESIZE) - 1; + + let common = STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + + STAGE1_DESCRIPTOR::SH::InnerShareable + + STAGE1_DESCRIPTOR::AF::True; + + for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { + let j: u64 = i as u64; + + let mem_attr = if j < ro_first_page_index || j > ro_last_page_index { + STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True + } else { + STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False + }; + + *entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; + } + + // Point to the LVL2 table base address in TTBR0. + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr()); + + // 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 + + TCR_EL1::IPS.val(ips) + + 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), // Start walks at level 2 + ); + + // Switch the MMU on. + // + // First, force all previous changes to be seen before the MMU is enabled. + barrier::isb(barrier::SY); + + // 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 + barrier::isb(barrier::SY); +} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index c3a52c5..a44a5dd 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -2,6 +2,7 @@ mod boot; mod memory; +pub mod mmu; pub use self::memory::{PhysicalAddress, VirtualAddress}; use cortex_a::{asm, barrier, regs::*}; @@ -197,7 +198,7 @@ pub struct BcmHost; impl BcmHost { // As per https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md /// This returns the ARM-side physical address where peripherals are mapped. - pub fn get_peripheral_address() -> PhysicalAddress { + pub const fn get_peripheral_address() -> u32 { 0x3f00_0000 } diff --git a/src/main.rs b/src/main.rs index a5f670e..f443968 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,6 +51,10 @@ fn kmain() -> ! { // uart.puts("Hey there, mini uart talking!\n"); // shall this work though? // uart.write_str(); // shall this? + unsafe { + aarch64::mmu::init(); + } + if let Some(mut display) = VC::init_fb(Size2d { x: 800, y: 600 } /*, &mut uart*/) { display.rect(10, 10, 250, 250, Color::rgb(32, 96, 64)); display.draw_text(50, 50, "Hello there!", Color::rgb(128, 192, 255)); diff --git a/src/platform/rpi3.rs b/src/platform/rpi3.rs index 756aa6a..0e67b4b 100644 --- a/src/platform/rpi3.rs +++ b/src/platform/rpi3.rs @@ -19,12 +19,12 @@ pub fn bus2phys(address: u32) -> u32 { // @todo use BcmHost::get_peripheral_address() instead pub const PERIPHERAL_BASE: u32 = phys2virt(0x3F00_0000); // Base address for all peripherals -pub struct BcmHost; +pub struct BcmHost; // One dupe of this is in arch::aarch64 mod NOW! impl BcmHost { // As per https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md /// This returns the ARM-side physical address where peripherals are mapped. - pub fn get_peripheral_address() -> usize { + pub fn get_peripheral_address() -> u32 { 0x3f00_0000 }