From 47996b4b78c599dffc83e77f4362d38eff108621 Mon Sep 17 00:00:00 2001 From: Andre Richter Date: Mon, 11 Feb 2019 19:04:34 +0100 Subject: [PATCH] Rewrite the kernel's static virtual memory mapping. --- 0C_virtual_memory/README.md | 15 + 0C_virtual_memory/kernel8 | Bin 71032 -> 73008 bytes 0C_virtual_memory/kernel8.img | Bin 2176 -> 3152 bytes 0C_virtual_memory/src/gpio.rs | 19 +- 0C_virtual_memory/src/main.rs | 34 +- 0C_virtual_memory/src/mbox.rs | 19 +- 0C_virtual_memory/src/memory.rs | 221 +++++++++++ 0C_virtual_memory/src/memory/mmu.rs | 355 ++++++++++++++++++ 0C_virtual_memory/src/mmu.rs | 273 -------------- 0C_virtual_memory/src/uart.rs | 13 +- 0D_cache_performance/README.md | 22 +- 0D_cache_performance/kernel8 | Bin 71984 -> 73968 bytes 0D_cache_performance/kernel8.img | Bin 3420 -> 4416 bytes 0D_cache_performance/src/benchmark.rs | 31 +- 0D_cache_performance/src/gpio.rs | 19 +- 0D_cache_performance/src/main.rs | 23 +- 0D_cache_performance/src/mbox.rs | 19 +- 0D_cache_performance/src/memory.rs | 225 +++++++++++ 0D_cache_performance/src/memory/mmu.rs | 345 +++++++++++++++++ 0D_cache_performance/src/mmu.rs | 265 ------------- 0D_cache_performance/src/uart.rs | 13 +- 0E_global_println/kernel8 | Bin 88168 -> 90232 bytes 0E_global_println/kernel8.img | Bin 11093 -> 12117 bytes 0E_global_println/src/devices/hw.rs | 4 +- 0E_global_println/src/devices/hw/gpio.rs | 4 +- .../src/devices/hw/{pl011_uart.rs => uart.rs} | 22 +- .../src/devices/hw/videocore_mbox.rs | 4 +- 0E_global_println/src/devices/virt/console.rs | 10 +- 0E_global_println/src/main.rs | 14 +- 0E_global_println/src/memory.rs | 281 ++++++++++---- 0E_global_println/src/memory/mmu.rs | 294 ++++++++++----- 0F_DMA_memory/README.md | 19 +- 0F_DMA_memory/kernel8 | Bin 95736 -> 97008 bytes 0F_DMA_memory/kernel8.img | Bin 16416 -> 16416 bytes 0F_DMA_memory/src/devices/hw/gpio.rs | 4 +- 0F_DMA_memory/src/devices/hw/mini_uart.rs | 4 +- 0F_DMA_memory/src/devices/hw/pl011_uart.rs | 4 +- .../src/devices/hw/videocore_mbox.rs | 4 +- 0F_DMA_memory/src/main.rs | 32 +- 0F_DMA_memory/src/memory.rs | 315 ++++++++++++---- 0F_DMA_memory/src/memory/mmu.rs | 303 ++++++++++----- 10_exceptions_groundwork/README.md | 26 +- 10_exceptions_groundwork/kernel8 | Bin 101280 -> 102544 bytes 10_exceptions_groundwork/kernel8.img | Bin 20512 -> 20512 bytes .../src/devices/hw/gpio.rs | 4 +- .../src/devices/hw/mini_uart.rs | 4 +- .../src/devices/hw/pl011_uart.rs | 4 +- .../src/devices/hw/videocore_mbox.rs | 4 +- 10_exceptions_groundwork/src/main.rs | 32 +- 10_exceptions_groundwork/src/memory.rs | 315 ++++++++++++---- 10_exceptions_groundwork/src/memory/mmu.rs | 303 ++++++++++----- 51 files changed, 2679 insertions(+), 1242 deletions(-) create mode 100644 0C_virtual_memory/src/memory.rs create mode 100644 0C_virtual_memory/src/memory/mmu.rs delete mode 100644 0C_virtual_memory/src/mmu.rs create mode 100644 0D_cache_performance/src/memory.rs create mode 100644 0D_cache_performance/src/memory/mmu.rs delete mode 100644 0D_cache_performance/src/mmu.rs rename 0E_global_println/src/devices/hw/{pl011_uart.rs => uart.rs} (95%) diff --git a/0C_virtual_memory/README.md b/0C_virtual_memory/README.md index 2c323e2a..2d713220 100644 --- a/0C_virtual_memory/README.md +++ b/0C_virtual_memory/README.md @@ -65,3 +65,18 @@ kernel8::mmu::init::h53df3fab6e51e098: 80778: 0d a2 18 d5 msr MAIR_EL1, x13 ... ``` + +## Output + +```console +ferris@box:~$ make raspboot + +[0] UART is live! +[1] Press a key to continue booting... Greetings fellow Rustacean! +[i] MMU: 4 KiB granule supported! +[i] MMU: Up to 40 Bit physical address range supported! +[2] MMU online. + +Writing through the virtual mapping at 0x00000000001FF000. + +``` diff --git a/0C_virtual_memory/kernel8 b/0C_virtual_memory/kernel8 index 8d326c650932f6de33f1347eca7375f0af26c91e..44757979aa71f175dda37bbe6bf4a6aed26aa892 100755 GIT binary patch literal 73008 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 ym61(cRGDHHgMX~jr04F3Z{y%9wK diff --git a/0C_virtual_memory/kernel8.img b/0C_virtual_memory/kernel8.img index 69b1b2156b0650b794faedd55becb1ac6ffef37f..93ff1e1c617d3a7f43d9a505c592be556b3a07f8 100755 GIT binary patch literal 3152 zcmcguZEO?w8Gr6Bj_>S-koOQA?lLr#I3r~jN&`a5#R+W!MKIean^JI0a1&!UP8_Ge zQY{X9QE0XaM|7-g5-*!n&g?@qV3I#fgz*WgNdsy=ZNt!h$jdY}Z^dRX@Ar4?mUW}r zr&&7P-ShVUJ-_GWe36>svO!xIBXYLe@LWTK-FS8>LOh*LSNM54rI7NGiRe>_=!2ac zMe1vnf0aWtb^1q$D64T z)!8HeDNhz!lI?Wz&a#8o%ScQ0(YXbQ3*ULv#pxzx%)U4x=s(K~+sC=7g=;FsI%sO4 zS)o|ecrLg_A$?KcqDulFt*hnqm)o^evPz4sN1pQA-}VtlP8qrP?bK2gYqVH(M&9Xj zr_;_OkoED^>h#wr#uJt1>_pgA99!oohHaWBUkhxRzsPAZsep>bH!a1@;FUPXcgOw- zUA`8Z5ja)mB;R>~oa4Nfx>+2yn+#g)R(WZo$v`prU5eEiXpo(89r2|1w3yXUtY+-q z`*&SJkgYOg)8O^mD-`o`Vf(Ccty5W5rCv``wA%BGr{s;Rhx zPrke)h*7zo2EzvM*V;CikjQxqdF6PjU8}{a$_$R19G!9rl;N{Upx9ZyZ0`Kiw6m4* z$(7A@^WY<2+j`1G5mOnsE1_cf`@&!=xnI`i5{@{N#OmvWrsE>eKc|T`%ZT;{D*Vqd=cdlP(&xNaABY zvN*Czf6z*i^r)miT+i;Jo*p&g%Ex8Em4{2f#p~%pO3gSZHSMC5HJv_<+28ES&OV1( zt|_gL9OT1x7WXLh8CiFIVaR0u_@mDz1Qz8?5?G4#@ z)#|rk_c~!$@hknx;;^2rnfk_xjbKsRVZ$K5vC(Z6kY&zwgavo@p}P{WKY{Dt4P ztlc8^R81bR8CG&%1ZR!VEg^r*o^`o!ovNF}U3CTPipvdD)VJa0nG@hcEYK!Rf1x6? z!NT_5eCOCiRTKIwpXH4{OCTRM=i{)ggd964BWK)A3+2|+JjL$YXs`xzlELHO#VJD#{tiB_Wov*P6Jt0<%qznrzeIz>{CL$Coc9Ur?!)d`9OaPzH7jR0 z-USBcKZE`}fHT=QAuaH?vG4!%B*5$JoA9_-(3>}E3rSa={#mC%A5WaMFYU?KpN<%H z@j_+%9P%X8^Q+3ePpYR)bVt6En=CT?!7B>+JgAB<*vvboAe*{P5Oj0XI|rW zT^{GesK}Y4X3la8Ga8*0s-xgGS}#!Q1V>lQg6YHNf?adGk(}QtkS!17JP$r{y@g>3hzqhd{6Kt z6O13mM3!>DgP z>f4v8uL(7_+`7+K&()%?Db#l?m3E5gFA=@f@#`Pm5o4p1qEZ=`@}?F<>>o+$zTAQL z`^`dJT-_Ac>9}3wB&)c3MfL!E;QJayj8ewsdF(=(vPKxsnRnL9Ki(91I`XQ%wC5G{ z{SH0pYSSP0w(3s;HXSn(Vc*v2LQ>`JG%p&)t7M}2gfL#km=wYNeUEa#{T^pW~m?w;VbY^b8Ftf#Hx6ctZ)g`+GeNrmjuZl%7C;$lE{cQ4R(Ic<60tXi#1adOYlTNO{BK_XmzB_TizB zyT{}1ufe}r{JPZr%I@9!lt91V+s{r?_jm0G1_Qy2c$;D}bq2jGf)eTr28Mh4aQ7&0 zd4r*0w_kbFJutv@?vS$SD4e}nug%IXuTAL%`eDCE85$lK2n0i(Lx7#L*f+o+Z{DQX zydh@#UU>*sVI^n2PoLp?$7KqwI0rsQ}6A^m}nvT~r?@9pjP9BQ!n W13kX4uvV4bfS0r0>+b#EL;nk@HXU04 delta 1495 zcmZ8hU2GIp6h8Of4m-2kE#2SJADCIeSe5`~3z39}4pkD`NGh`{CKjPcjNPoX?m|RT zVFw;e5lk2cBq1a%#sp>!i4aXX55_LU2r*H%wHV_AOC`oyr9TU{#lU!GS%mm-b7#Ku zedlk^nd=Kw&oL?eErAmfNI%ZBe`W9>n=No`=w6fyNYVSxX3@ zr3pXfX$2#MQ{D3E2SXe6DSl_?VQVD)y~1q;g}SWal8sJ%f|#rC_z zvQ8DT6JlB$#SOPX5y3*=lcDUakxjp+EV>8aL|RZ}mRa;R$@$k-qv0kR@}<9=EePVy;ZPQF`zLmiLQyb= z4NqaJ_8(X86gtRn`Zb}!7pl|h<+_pb^>rh8)_0?5-Q{{s|HipxS$pJlDY`elXJ==; m>n&;1rdOoyE~%?~?}`;Gif{4V(%bQO-;}m?uheDN@_zx + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use core::ops; use register::{mmio::ReadWrite, register_bitfields}; @@ -67,8 +66,6 @@ register_bitfields! { ] } -const GPIO_BASE: u32 = MMIO_BASE + 0x200_000; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -99,23 +96,25 @@ pub struct RegisterBlock { } /// Public interface to the GPIO MMIO area -pub struct GPIO; +pub struct GPIO { + base_addr: usize, +} impl ops::Deref for GPIO { type Target = RegisterBlock; fn deref(&self) -> &Self::Target { - unsafe { &*Self::ptr() } + unsafe { &*self.ptr() } } } impl GPIO { - pub fn new() -> GPIO { - GPIO + pub fn new(base_addr: usize) -> GPIO { + GPIO { base_addr } } /// Returns a pointer to the register block - fn ptr() -> *const RegisterBlock { - GPIO_BASE as *const _ + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ } } diff --git a/0C_virtual_memory/src/main.rs b/0C_virtual_memory/src/main.rs index bd646dce..45ad5cb9 100644 --- a/0C_virtual_memory/src/main.rs +++ b/0C_virtual_memory/src/main.rs @@ -26,21 +26,19 @@ #![no_main] #![feature(range_contains)] -const MMIO_BASE: u32 = 0x3F00_0000; - mod delays; mod gpio; mod mbox; -mod mmu; +mod memory; mod uart; fn kernel_entry() -> ! { - let gpio = gpio::GPIO::new(); - let mut mbox = mbox::Mbox::new(); + let gpio = gpio::GPIO::new(memory::map::physical::GPIO_BASE); + let mut mbox = mbox::Mbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE); { // Before the MMU is live, instantiate a UART driver with the physical address - let uart = uart::Uart::new(uart::UART_PHYS_BASE); + let uart = uart::Uart::new(memory::map::physical::UART_BASE); // set up serial console match uart.init(&mut mbox, &gpio) { @@ -54,20 +52,24 @@ fn kernel_entry() -> ! { uart.getc(); uart.puts("Greetings fellow Rustacean!\n"); - mmu::print_features(&uart); + memory::mmu::print_features(&uart); - uart.puts("[2] Switching MMU on now... "); + match unsafe { memory::mmu::init() } { + Err(s) => { + uart.puts("[2][Error] MMU: "); + uart.puts(s); + uart.puts("\n"); + } + Ok(()) => uart.puts("[2] MMU online.\n"), + } } // After this closure, the UART instance is not valid anymore. - unsafe { mmu::init() }; - - // Instantiate a new UART using the virtual mapping in the second 2 MiB - // block. No need to init() again, though. - const UART_VIRT_BASE: u32 = 2 * 1024 * 1024 + 0x1000; - let uart = uart::Uart::new(UART_VIRT_BASE); + // Instantiate a new UART using the remapped address. No need to init() + // again, though. + let uart = uart::Uart::new(memory::map::virt::REMAPPED_UART_BASE); - uart.puts("MMU is live \\o/\n\nWriting through the virtual mapping at 0x"); - uart.hex(u64::from(UART_VIRT_BASE)); + uart.puts("\nWriting through the virtual mapping at 0x"); + uart.hex(memory::map::virt::REMAPPED_UART_BASE as u64); uart.puts(".\n"); // echo everything back diff --git a/0C_virtual_memory/src/mbox.rs b/0C_virtual_memory/src/mbox.rs index aeae88bb..2e4bf0ad 100644 --- a/0C_virtual_memory/src/mbox.rs +++ b/0C_virtual_memory/src/mbox.rs @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Andre Richter + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use core::ops; use cortex_a::asm; use register::{ @@ -39,8 +38,6 @@ register_bitfields! { ] } -const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -89,6 +86,7 @@ pub struct Mbox { // The address for buffer needs to be 16-byte aligned so that the // Videcore can handle it properly. pub buffer: [u32; 36], + base_addr: usize, } /// Deref to RegisterBlock @@ -105,18 +103,21 @@ impl ops::Deref for Mbox { type Target = RegisterBlock; fn deref(&self) -> &Self::Target { - unsafe { &*Self::ptr() } + unsafe { &*self.ptr() } } } impl Mbox { - pub fn new() -> Mbox { - Mbox { buffer: [0; 36] } + pub fn new(base_addr: usize) -> Mbox { + Mbox { + buffer: [0; 36], + base_addr, + } } /// Returns a pointer to the register block - fn ptr() -> *const RegisterBlock { - VIDEOCORE_MBOX as *const _ + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ } /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success diff --git a/0C_virtual_memory/src/memory.rs b/0C_virtual_memory/src/memory.rs new file mode 100644 index 00000000..c2135539 --- /dev/null +++ b/0C_virtual_memory/src/memory.rs @@ -0,0 +1,221 @@ +/* + * MIT License + * + * Copyright (c) 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 core::ops::RangeInclusive; + +pub mod mmu; + +/// System memory map. +#[rustfmt::skip] +pub mod map { + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x3FFF_FFFF; + + pub mod physical { + pub const MMIO_BASE: usize = 0x3F00_0000; + pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880; + pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000; + pub const UART_BASE: usize = MMIO_BASE + 0x0020_1000; + pub const MMIO_END: usize = super::END; + } + + pub mod virt { + pub const KERN_STACK_START: usize = super::START; + pub const KERN_STACK_END: usize = 0x0007_FFFF; + + // The last 4 KiB slot in the first 2 MiB + pub const REMAPPED_UART_BASE: usize = 0x001F_F000; + pub const REMAPPED_UART_END: usize = 0x001F_FFFF; + } +} + +/// Types used for compiling the virtual memory layout of the kernel using +/// address ranges. +pub mod kernel_mem_range { + use core::ops::RangeInclusive; + + #[derive(Copy, Clone)] + pub enum MemAttributes { + CacheableDRAM, + Device, + } + + #[derive(Copy, Clone)] + pub enum AccessPermissions { + ReadOnly, + ReadWrite, + } + + #[derive(Copy, Clone)] + pub enum Translation { + Identity, + Offset(usize), + } + + #[derive(Copy, Clone)] + pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, + } + + impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } + } + + pub struct Descriptor { + pub virtual_range: fn() -> RangeInclusive, + pub translation: Translation, + pub attribute_fields: AttributeFields, + } +} + +use kernel_mem_range::*; + +/// A virtual memory layout that is agnostic of the paging granularity that the +/// hardware MMU will use. +/// +/// Contains only special ranges, aka anything that is _not_ normal cacheable +/// DRAM. +static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [ + // Kernel stack + Descriptor { + virtual_range: || { + RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END) + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Kernel code and RO data + Descriptor { + virtual_range: || { + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols: + // + // [__ro_start, __ro_end) + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static __ro_start: u64; + + // The exclusive end of the read-only area, aka the address of + // the first byte _after_ the RO area. + static __ro_end: u64; + } + + unsafe { + // Notice the subtraction to turn the exclusive end into an + // inclusive end + RangeInclusive::new( + &__ro_start as *const _ as usize, + &__ro_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + // Kernel data and BSS + Descriptor { + virtual_range: || { + extern "C" { + static __ro_end: u64; + static __bss_end: u64; + } + + unsafe { + RangeInclusive::new( + &__ro_end as *const _ as usize, + &__bss_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Remapped UART + Descriptor { + virtual_range: || { + RangeInclusive::new(map::virt::REMAPPED_UART_BASE, map::virt::REMAPPED_UART_END) + }, + translation: Translation::Offset(map::physical::UART_BASE), + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Device MMIO + Descriptor { + virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, +]; + +/// For a given virtual address, find and return the output address and +/// according attributes. +/// +/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal +/// cacheable DRAM. +fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > map::END { + return Err("Address out of range."); + } + + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + if (i.virtual_range)().contains(&virt_addr) { + let output_addr = match i.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())) +} diff --git a/0C_virtual_memory/src/memory/mmu.rs b/0C_virtual_memory/src/memory/mmu.rs new file mode 100644 index 00000000..e6628990 --- /dev/null +++ b/0C_virtual_memory/src/memory/mmu.rs @@ -0,0 +1,355 @@ +/* + * 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 crate::memory::{get_virt_addr_properties, AttributeFields}; +use crate::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 [ + /// Privileged execute-never + PXN OFFSET(53) 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 + ] + ] +} + +const FOUR_KIB: usize = 4 * 1024; +const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024) + +const TWO_MIB: usize = 2 * 1024 * 1024; +const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024) + +/// A descriptor pointing to the next page table. +struct TableDescriptor(register::FieldValue); + +impl TableDescriptor { + fn new(next_lvl_table_addr: usize) -> Result { + if next_lvl_table_addr % FOUR_KIB != 0 { + return Err("TableDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT; + + Ok(TableDescriptor( + STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// A function that maps the generic memory range attributes to HW-specific +/// attributes of the MMU. +fn into_mmu_attributes( + attribute_fields: AttributeFields, +) -> register::FieldValue { + use crate::memory::{AccessPermissions, MemAttributes}; + + // Memory attributes + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::Device => { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // 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::True + } else { + STAGE1_DESCRIPTOR::PXN::False + }; + + desc +} + +/// A Level2 block descriptor with 2 MiB aperture. +/// +/// The output points to physical memory. +struct Lvl2BlockDescriptor(register::FieldValue); + +impl Lvl2BlockDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % TWO_MIB != 0 { + return Err("BlockDescriptor: Address is not 2 MiB aligned."); + } + + let shifted = output_addr >> TWO_MIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// A page descriptor with 4 KiB aperture. +/// +/// The output points to physical memory. +struct PageDescriptor(register::FieldValue); + +impl PageDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % FOUR_KIB != 0 { + return Err("PageDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = output_addr >> FOUR_KIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// Constants for indexing the MAIR_EL1. +#[allow(dead_code)] +mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; +} + +/// Setup function for the MAIR_EL1 register. +fn set_up_mair() { + // Define the three memory types that we will map. Cacheable and + // non-cacheable normal DRAM, and device. + MAIR_EL1.write( + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc + + // Attribute 0 + + MAIR_EL1::Attr0_HIGH::Device + + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, + ); +} + +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 + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// A wrapper struct is needed here so that the align attribute can be used. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +/// The LVL2 page table containng the 2 MiB entries. +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// The LVL3 page table containing the 4 KiB entries. +/// +/// The first entry of the LVL2_TABLE will forward to this table. +static mut LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 GiB of address space. +/// +/// The first 2 MiB are 4 KiB granule, the rest 2 MiB. +pub unsafe fn init() -> Result<(), &'static str> { + // Prepare the memory attribute indirection register. + set_up_mair(); + + // Point the first 2 MiB of virtual addresses to the follow-up LVL3 + // page-table. + LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) { + Err(s) => return Err(s), + Ok(d) => d.value(), + }; + + // 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 << TWO_MIB_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 Lvl2BlockDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; + + *entry = block_desc.value(); + } + + // 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 << FOUR_KIB_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 PageDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; + + *entry = page_desc.value(); + } + + // Point to the LVL2 table base address in TTBR0. + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); + + // 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); + + Ok(()) +} diff --git a/0C_virtual_memory/src/mmu.rs b/0C_virtual_memory/src/mmu.rs deleted file mode 100644 index ef352461..00000000 --- a/0C_virtual_memory/src/mmu.rs +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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 GiB of address space. -pub unsafe fn init() { - // First, define the two memory types that we will map. Cacheable 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, - ); - - // Descriptive consts for indexing into the correct MAIR_EL1 attributes. - mod mair { - pub const NORMAL: u64 = 0; - pub const DEVICE: u64 = 1; - } - - // The first 2 MiB. - // - // Set up the first LVL2 entry, pointing to the base address of a follow-up - // table containing 4 KiB pages. - // - // 0x0000_0000_0000_0000 | - // |> 2 MiB - // 0x0000_0000_001F_FFFF | - 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. - // - // 0x0000_0000_0020_0000 | - // |> 2 MiB - // 0x0000_0000_003F_FFFF | - 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 (2 MiB) entries as block descriptors. - // - // Differentiate between - // - cacheable DRAM - // - device memory - // - // Ranges are stored in memory.rs. - // - // 0x0000_0000_0040_0000 | - // |> 1004 MiB cacheable DRAM - // 0x0000_0000_3EFF_FFFF | - // 0x0000_0000_3F00_0000 | - // |> 16 MiB device (MMIO) - // 0x0000_0000_4000_0000 | - let mmio_first_block_index: u64 = (super::MMIO_BASE >> 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) which makes the iteration start at the third 2 MiB - // block (0x40_0000). - for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) { - let j: u64 = i as u64; - - let mem_attr = if j >= mmio_first_block_index { - 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 (ro_first_page_index..=ro_last_page_index).contains(&j) { - STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False - } else { - STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True - }; - - *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/0C_virtual_memory/src/uart.rs b/0C_virtual_memory/src/uart.rs index 2c0e8a03..9505be3a 100644 --- a/0C_virtual_memory/src/uart.rs +++ b/0C_virtual_memory/src/uart.rs @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Andre Richter + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use crate::delays; use crate::gpio; use crate::mbox; @@ -120,8 +119,6 @@ register_bitfields! { ] } -pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -143,7 +140,7 @@ pub enum UartError { pub type Result = ::core::result::Result; pub struct Uart { - uart_base: u32, + base_addr: usize, } impl ops::Deref for Uart { @@ -155,13 +152,13 @@ impl ops::Deref for Uart { } impl Uart { - pub fn new(uart_base: u32) -> Uart { - Uart { uart_base } + pub fn new(base_addr: usize) -> Uart { + Uart { base_addr } } /// Returns a pointer to the register block fn ptr(&self) -> *const RegisterBlock { - self.uart_base as *const _ + self.base_addr as *const _ } ///Set baud rate and characteristics (115200 8N1) and map to GPIO diff --git a/0D_cache_performance/README.md b/0D_cache_performance/README.md index 6248a0a7..bcb7fd90 100644 --- a/0D_cache_performance/README.md +++ b/0D_cache_performance/README.md @@ -18,13 +18,12 @@ operating with data on the same DRAM with caching enabled and disabled. ### mmu.rs Therefore, we will map the same physical memory via two different virtual -addresses. We set up our pagetables such that the virtual address `0x200000` -points to the physical DRAM at `0x400000`, and we configure it as +addresses. We set up our pagetables such that the virtual address `0x400000` +points to the physical DRAM at `0x200000`, and we configure it as `non-cacheable` in the page tables. -We are still using a `2 MiB` granule, and set up the next block, which starts at -virtual `0x400000`, to point at physical `0x400000` (this is an identity mapped -block). This time, the block is configured as cacheable. +There is also an identity mapped block, which starts at virtual `0x200000` and +points at physical `0x200000`. This time, the block is configured as cacheable. ### benchmark.rs @@ -38,15 +37,20 @@ non-cacheable virtual addresses. Remember that both virtual addresses point to the _same_ physical DRAM, so the difference in time that we will see will showcase how much faster it is to operate on DRAM with caching enabled. -## Results +## Output On my Raspberry, I get the following results: -```text -Benchmarking non-cacheable DRAM modifications at virtual 0x0000000000200000, physical 0x0000000000400000: +```console +ferris@box:~$ make raspboot + +[0] UART is live! +[1] Press a key to continue booting... Greetings fellow Rustacean! +[2] MMU online. +Benchmarking non-cacheable DRAM modifications at virtual 0x0000000000400000, physical 0x0000000000200000: 1040 miliseconds. -Benchmarking cacheable DRAM modifications at virtual 0x0000000000400000, physical 0x0000000000400000: +Benchmarking cacheable DRAM modifications at virtual 0x0000000000200000, physical 0x0000000000200000: 53 miliseconds. With caching, the function is 1800% faster! diff --git a/0D_cache_performance/kernel8 b/0D_cache_performance/kernel8 index 673897fdfa11c1a14a717a3e5d7cb9a9b90fae88..a19e827b4c403f18c6bdc4f0d16a70b57ef12229 100755 GIT binary patch literal 73968 zcmeI0dvH@%e#g(%wXq~ai~;j7CcfTy6XOs?x_Vs`63Yey=CMg-v1CJ8p~saiY)c@? zV3tj>*+~lpwurK2$)?LAJF{G8(n*uu?rPc&q?=AdXs02>X(#PY47=O0G1DoJfCXXI z-?_R6?2DC6W;^}scW18d)%X0)@BGf^cV5!2mNm-_97ot?AU`AdZ$?$}Xa&~l`!*gT z5uSKR2`N@>i&Yy|p07uUR=+&&z@t5h=ABwkf!kBA)f2RO?3u4dh`?U*c7O1jRG8Mo z3ke_rB!C2v01`j~NB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5$cfY_7=5o0?2Y7N=Ai{@tI{#MT2sxMyaY~J6ar)K`QwC3Vn6Iw?wCrK=CT|uMvvKvpX%EksQ%uU=ee~?hdn-K| z<2279^DjwxX>pZU{Z(@H`Tl*C4Hd<0A!w_SC!Iskel@he2W;nozERTyxtwp9pHu&R z1Igr?$<~^?IUkLwX3`lVuME)c_#-xb5Ar_z`BYLUiJ;pz8Q z6dg~5A(j!odZ2FTslfrm%5>>Fd%ahH$pOxjeZ82Z54=Ri{M^*he(2Xi_}<8sj&ksR ztdSqAhdL&>Tg!-JC1*TmezDIx!kg-iFPeWq+n8lK`|?G8R{dMhhRUD=+Akw&`^%a3 zBhaSgxio>cP0P(K$jumEDvoHmnFqNsK6duyvyxp^0C=YxJz z)APBRSN}2Org7=yxeQbhH<&ck^4C@Lq0}xP4^8*2Qir_({` z5A}Hh%2n;m)9Fh^dF>U@uKPV%1H?w>dk2iES>;7-0m$bu1L>q=iq_d>@MLQK@EJ9> zOQjEs--UVypx=Ul{Nx7D4Nx1p{?vFH^EP~y_!8LAb}zzQxx~*J=x6QsF&iQlGtPu* zRpNH7-aEg|9k~IqwEzJ(~^sLQSXr`yR{}T7LxA+VAIbN9bBRR#r8(k{tZt(>8-rK|I+Ga{QW!>-*r= z22L>x{od<4dF3yF;U4bHwbLTWw30K|8U>OWC^?>R2?W++Zr}!Q7+6@xDeje?>=?u~ z722%+_Vb+t#-Pf2l_xuAo+mR!)z_=6w}aMUSRf96g=AQtJ3upn-JBf{x7`8eIp|Ll z#I5R_Cu5pMW*EDxW;DXSLAbVW_5T*irg$ zJxVf4J@nl|GOqy(8ut)S#Ophk!aQq+dDf(il`I>hbL@86{4u(J(Y=f9TPI8NV~CC) zCo$~%8}Pjz?qL`+riwF?1bJ;WjJa>{#)0a`$=1GN;J0+y)*%z|?SOtUPIdD=nM%m* zo508q>m<#s#FNZ1L&fL`*r%4#G0s(tZZZImDV~PIW|B5n0K1uFM)i02txG|twgmj| zo$ZkpLRo7?rM>zW{8k<`TDA$Ee+p%*pll)dy9``z@D&5hhXL4|%PZ|;yt$sn`u6Rd zuXkLGMu?H_r+Xnry3f)WXG4sv53Ia@T=^WW5B;}tz8TOa>}P75EVkx5kh}6((56Xk zvj*&b1!I}UW;8w2R`*1ueJ+=er|D+S_n%OvU!QK5w5a*XE#=NDM~|v!3dr>UJy+~K znn}>LW)3UQ`vehTZRS%5iMc-C_&{#{h?!OzlaNs5L{EPh5`q$xH(H*V( zxv`w2-vuT!VGiAR(sQq$be*XH4lAIaY#yiRJbe5?n`iVkk3DGP)Z0Arpv@w^P4$B| zkLYb6Uo1B)=1RTIoCj^n^)@pfv@z;!${w_#d*uW^r4QQN)Y-7yLN3#E&W)@lB8&xk z&Qr`yq(Rl@7{$xmi8u6Bq$$7nv8Op)t;OE(lz!}SY z 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 diff --git a/0D_cache_performance/kernel8.img b/0D_cache_performance/kernel8.img index 06cee9298c40538b587c46a786465a9591182af7..149556d6bbeb2f3cc49fdda1fe846f4e109e1f40 100755 GIT binary patch literal 4416 zcmcgvZERE589w)(IKKCqEF{DUBmv*FYnvEII|FU_Xnm8=LSU$sh@sD=aaPf{*S#eQ;N)5)<;2ebPTjfyq#Av)o0lKGCzgbg9|U=!P#lCH{W+x4?fd zuybJFkg>aT-8n!@tgrGUrq_}7igDe^I8`iIt{&wgj($2T`txZVO#c;!y4!GA@ogMh zmaCVk#l|?8l^%yV=Wu8fxC~NDw6cGBJj$AgoNoukUX0(|DZG?MHA3S{vZJvZS%b~#+JlWqpNt=&= zErWeDW1rAwj`gq5#$JDGZRG!v!{2VhA^vR~CTXLSuQZXMM*?wZgB~v;{%z3XByXCe zhcr!()BIO@j6sj$j2?06FVMqmeKZ^UNqSs{&x?FXVk1YK!(A)2YmN2VSW}&ra@T4% z{C_ysN1HFCHIyGzkMa>mJ|RWv@5l;hIdmA;qI~R`uPYxc6hu8Fck{WCzsexvaY6LITjm6ozk>($MP~C|2usM&QKW*!K zP`#WpVS636;btYpgB#0tFZNVXK~AF!dLH75pY16&rkht{6+eC`&F!*jp6w-!+k zJb7<~a#4n1^`}CyPhe=d?FC?%-%H4q%d{xkll33TVu;O+<39XSe`N&R z8WGbhKUR;-;I|a~PGiA9ZvL-)%BwU~>JP(E5!$!QO)R#HjungO4dXP@vB?7?)b*C?ryIZ7-y zZC{^`^)}eN{9N0>9VC`rvlli2xRM?78gC%xI@VJQ-f26h#Kb&OC~Pe+wBy_$T;orA zzC>TH5*wtP&BBTPBa}E1qtxlbh(j_cv5$+37D)yYv%E}VsCn&-%PGt!9#CRdL!m8= zJMi_sew-&3*l@1cPCi0nK4@~qSZ15w5^)^6o5VCL)?Q6w#}Nz0yPp!+uX=&xq!}ni}ZZz$YIVp+eL$ zpR`8|h+qB6_I{B#cVJ!FI8~M@u~KOJE@IS-I?1%Hp(J*QHz$tZoT_JgoHHj{c*G-D zS-M{$s$@p&W|2b6^R&Glc7`RGe^;?mQ-!_;bLk?>2eh5S#`;?D{ssEhpsxz^T|rz% zs5zRQgGcP8g3?7vDp?uVQ`dFp_DLKCaAfD{E^uV$EaO-Vj@dP2`+Fw!mt@w^Gp0KW z;S=Xs+Gmzq-3@40Pz0Z1+GhoDpJ6XEZi4u|#>$1Ii)M2Zd}>E^=ikvcwLV=pKAhG^ zujkHc2M(m~6wo@#?iITZ#6qmrtbrzaH>V|7Ju;G%j?8@5?35HF@IhA5{>wk`s1|TElq}xNi7q=1LQNs3WXzoJvvFSCvoRAI7|8xC0ey z{`1{C`Kx`X+oHcCIe>hVIs1hHqmt^_NmL70oDfhq224tdwclX*MQ82y?tCg~F_5dc zXF7fSQ|WP?d~-58R^y9zvpBJM+>iUL?`U(4cW3kV%DGRUxad%ZNK$v^fZqV>8T*c) zj@*oUHC1uHD9Xlo1!9{5Ul!9Y#B=|@sQNnEYz+g5%?5B@YQ$Rp6#oCOrCJ$cd*<%! zTJY`4Ax;VUiqQAUmMu5+J@Zm{-+bunP3tQ`-?lR#(XH3$)6CU|^x!<@S198{0hcN=NNFd24W!Z?n(s z3j2bA4%ro!pYeslovt?dzU>R-r@cEm(8oTON%B@-o3F#;4hA-LR7=v=CjRb|uJ?t# zGJ}K90y*sU$eTL@ZsuL~b;x(gn_V4YPpCp_3~u#=8A#sd354Zsp?y}#rLkkBG=-hb72(p_(k!o^u2O9hNCyL@`?X_FeYTeJ#Hc>%lad zcJRKYW#1$jJxu_COHPn`x%BuwZc6+P;ubX9jDl$9X!GOcmvuvTIh$KI(QHIR52C*N zh)0N}XPsO%5=QY7OWMIsrzh4>K`lMfv8oZ-=`sgKnbCMZ~P>rSd5f^(DBh_}+0&pqy4FIg67V`^r+e3;U{b?A|(` z-OyCCny@~)fCSk|B0d+#MBRY%H6&(0lKA{MR*%@EF@gDr?LrgXoah?L$afEO0*lZ> zVr0!Un`(spLdED(5GIr0r!*GS&!Uhh(?m&aRmDty(&dBA|`Im@v62=gMSN} zZKM!duW122DS{n$E}tvwg}T!6-5-w^`~F{SKVlz@YA2E+bIRm`l<;1}^}H*%H-K7x z92E-eX=A!;70UdWl8K>&zRl->^#Ixs%OLzNjpfX<8~sZOTVr%Y79#y-ihi6zl70st z^^B&S&_Dd4>K_yyyy`MI+BxVaN)Vd*`fI5okZ7kP>x54%1(lcTd;=VbS4g1hLw(#3 z^&KE>eU;GGcgECI<4ByYDVqt3OCrj3v@{bn#l5eF+5jQ}y+t}GrocLEPmJcdF9`6Y zg%@x?dr!V!A3QB(_QL)(t!4{Y`7P6}6;24>!tYNcw$Mq8u}NO_4oBP~F^Ad>5a6?- zK7AH`wMKnH`zx(+hs`6jJ6gr|h}b&Y2w~ksI|`eLS9KY!?j(_bI&lvTVo}k(8OaSg-4Eh%;L)GWc=*L5X19$(-|9~SI zxx@N-p`R7w=fpWiO!G@YfWRJ<8y~A#xn4B~-{a3X3TwHsu`1_4ZKU>|laIB)Viq(# zLnJ1fvg|XA5YX``#!npH@=7~cT}(B(B2UWkSZ*4Y`xMJnq{=z4+juoet6 zfL+6o18l@xKr&gLFoW=Hp&-uF-<;lk9^e6w=Rf|~&gwt53c~lC!+*z<`wwAQ$Ijza zw2rcu(u--h=&MNjTvAx=enax_4({BzIk@#LX~l|{rEOcKt=o1N6%{4#f;*&lg1a_J KuWu_Bgns~a?ut?X diff --git a/0D_cache_performance/src/benchmark.rs b/0D_cache_performance/src/benchmark.rs index 86af6d62..346399f5 100644 --- a/0D_cache_performance/src/benchmark.rs +++ b/0D_cache_performance/src/benchmark.rs @@ -22,26 +22,26 @@ * SOFTWARE. */ -use super::uart; +use crate::uart; use core::sync::atomic::{compiler_fence, Ordering}; use cortex_a::{barrier, regs::*}; /// We assume that addr is cacheline aligned -fn batch_modify_time(addr: u64) -> Option { +fn batch_modify_time(addr: usize) -> Option { const CACHELINE_SIZE_BYTES: usize = 64; // TODO: retrieve this from a system register const NUM_CACHELINES_TOUCHED: usize = 5; const NUM_BENCH_ITERATIONS: usize = 20_000; const NUM_BYTES_TOUCHED: usize = CACHELINE_SIZE_BYTES * NUM_CACHELINES_TOUCHED; - let mem = unsafe { core::slice::from_raw_parts_mut(addr as *mut u64, NUM_BYTES_TOUCHED) }; + let mem = unsafe { core::slice::from_raw_parts_mut(addr as *mut usize, NUM_BYTES_TOUCHED) }; // Benchmark starts here let t1 = CNTPCT_EL0.get(); compiler_fence(Ordering::SeqCst); - let mut temp: u64; + let mut temp: usize; for _ in 0..NUM_BENCH_ITERATIONS { for qword in mem.iter_mut() { unsafe { @@ -65,24 +65,17 @@ fn batch_modify_time(addr: u64) -> Option { } pub fn run(uart: &uart::Uart) { - const SIZE_2MIB: u64 = 2 * 1024 * 1024; - const ERROR_STRING: &str = "Something went wrong!"; - - // Start of the __SECOND__ virtual 2 MiB block (counting starts at zero). - // NON-cacheable DRAM memory. - let non_cacheable_addr: u64 = SIZE_2MIB; + use crate::memory::map; - // Start of the __THIRD__ virtual 2 MiB block. - // Cacheable DRAM memory - let cacheable_addr: u64 = 2 * SIZE_2MIB; + const ERROR_STRING: &str = "Something went wrong!"; uart.puts("Benchmarking non-cacheable DRAM modifications at virtual 0x"); - uart.hex(non_cacheable_addr); + uart.hex(map::virt::NON_CACHEABLE_START as u64); uart.puts(", physical 0x"); - uart.hex(2 * SIZE_2MIB); + uart.hex(map::virt::CACHEABLE_START as u64); uart.puts(":\n"); - let result_nc = match batch_modify_time(non_cacheable_addr) { + let result_nc = match batch_modify_time(map::virt::NON_CACHEABLE_START) { Some(t) => { uart.dec(t as u32); uart.puts(" miliseconds.\n\n"); @@ -95,12 +88,12 @@ pub fn run(uart: &uart::Uart) { }; uart.puts("Benchmarking cacheable DRAM modifications at virtual 0x"); - uart.hex(cacheable_addr); + uart.hex(map::virt::CACHEABLE_START as u64); uart.puts(", physical 0x"); - uart.hex(2 * SIZE_2MIB); + uart.hex(map::virt::CACHEABLE_START as u64); uart.puts(":\n"); - let result_c = match batch_modify_time(cacheable_addr) { + let result_c = match batch_modify_time(map::virt::CACHEABLE_START) { Some(t) => { uart.dec(t as u32); uart.puts(" miliseconds.\n\n"); diff --git a/0D_cache_performance/src/gpio.rs b/0D_cache_performance/src/gpio.rs index 608ba532..7affea08 100644 --- a/0D_cache_performance/src/gpio.rs +++ b/0D_cache_performance/src/gpio.rs @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Andre Richter + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use core::ops; use register::{mmio::ReadWrite, register_bitfields}; @@ -67,8 +66,6 @@ register_bitfields! { ] } -const GPIO_BASE: u32 = MMIO_BASE + 0x200_000; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -99,23 +96,25 @@ pub struct RegisterBlock { } /// Public interface to the GPIO MMIO area -pub struct GPIO; +pub struct GPIO { + base_addr: usize, +} impl ops::Deref for GPIO { type Target = RegisterBlock; fn deref(&self) -> &Self::Target { - unsafe { &*Self::ptr() } + unsafe { &*self.ptr() } } } impl GPIO { - pub fn new() -> GPIO { - GPIO + pub fn new(base_addr: usize) -> GPIO { + GPIO { base_addr } } /// Returns a pointer to the register block - fn ptr() -> *const RegisterBlock { - GPIO_BASE as *const _ + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ } } diff --git a/0D_cache_performance/src/main.rs b/0D_cache_performance/src/main.rs index 0a02da69..45a8521f 100644 --- a/0D_cache_performance/src/main.rs +++ b/0D_cache_performance/src/main.rs @@ -26,19 +26,17 @@ #![no_main] #![feature(range_contains)] -const MMIO_BASE: u32 = 0x3F00_0000; - mod benchmark; mod delays; mod gpio; mod mbox; -mod mmu; +mod memory; mod uart; fn kernel_entry() -> ! { - let gpio = gpio::GPIO::new(); - let mut mbox = mbox::Mbox::new(); - let uart = uart::Uart::new(uart::UART_PHYS_BASE); + let gpio = gpio::GPIO::new(memory::map::physical::GPIO_BASE); + let mut mbox = mbox::Mbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE); + let uart = uart::Uart::new(memory::map::physical::UART_BASE); // set up serial console match uart.init(&mut mbox, &gpio) { @@ -52,11 +50,14 @@ fn kernel_entry() -> ! { uart.getc(); uart.puts("Greetings fellow Rustacean!\n"); - uart.puts("[2] Switching MMU on now... "); - - unsafe { mmu::init() }; - - uart.puts("MMU is live \\o/\n\n"); + match unsafe { memory::mmu::init() } { + Err(s) => { + uart.puts("[2][Error] MMU: "); + uart.puts(s); + uart.puts("\n"); + } + Ok(()) => uart.puts("[2] MMU online.\n"), + } benchmark::run(&uart); diff --git a/0D_cache_performance/src/mbox.rs b/0D_cache_performance/src/mbox.rs index aeae88bb..2e4bf0ad 100644 --- a/0D_cache_performance/src/mbox.rs +++ b/0D_cache_performance/src/mbox.rs @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Andre Richter + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use core::ops; use cortex_a::asm; use register::{ @@ -39,8 +38,6 @@ register_bitfields! { ] } -const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -89,6 +86,7 @@ pub struct Mbox { // The address for buffer needs to be 16-byte aligned so that the // Videcore can handle it properly. pub buffer: [u32; 36], + base_addr: usize, } /// Deref to RegisterBlock @@ -105,18 +103,21 @@ impl ops::Deref for Mbox { type Target = RegisterBlock; fn deref(&self) -> &Self::Target { - unsafe { &*Self::ptr() } + unsafe { &*self.ptr() } } } impl Mbox { - pub fn new() -> Mbox { - Mbox { buffer: [0; 36] } + pub fn new(base_addr: usize) -> Mbox { + Mbox { + buffer: [0; 36], + base_addr, + } } /// Returns a pointer to the register block - fn ptr() -> *const RegisterBlock { - VIDEOCORE_MBOX as *const _ + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ } /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success diff --git a/0D_cache_performance/src/memory.rs b/0D_cache_performance/src/memory.rs new file mode 100644 index 00000000..72db92dd --- /dev/null +++ b/0D_cache_performance/src/memory.rs @@ -0,0 +1,225 @@ +/* + * MIT License + * + * Copyright (c) 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 core::ops::RangeInclusive; + +pub mod mmu; + +/// System memory map. +#[rustfmt::skip] +pub mod map { + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x3FFF_FFFF; + + pub mod physical { + pub const MMIO_BASE: usize = 0x3F00_0000; + pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880; + pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000; + pub const UART_BASE: usize = MMIO_BASE + 0x0020_1000; + pub const MMIO_END: usize = super::END; + } + + pub mod virt { + pub const KERN_STACK_START: usize = super::START; + pub const KERN_STACK_END: usize = 0x0007_FFFF; + + // The second 2 MiB block + pub const CACHEABLE_START: usize = 0x0020_0000; + + // The third 2 MiB block + pub const NON_CACHEABLE_START: usize = 0x0040_0000; + pub const NON_CACHEABLE_END: usize = 0x005F_FFFF; + } +} + +/// Types used for compiling the virtual memory layout of the kernel using +/// address ranges. +pub mod kernel_mem_range { + use core::ops::RangeInclusive; + + #[derive(Copy, Clone)] + pub enum MemAttributes { + CacheableDRAM, + NonCacheableDRAM, + Device, + } + + #[derive(Copy, Clone)] + pub enum AccessPermissions { + ReadOnly, + ReadWrite, + } + + #[derive(Copy, Clone)] + pub enum Translation { + Identity, + Offset(usize), + } + + #[derive(Copy, Clone)] + pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, + } + + impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } + } + + pub struct Descriptor { + pub virtual_range: fn() -> RangeInclusive, + pub translation: Translation, + pub attribute_fields: AttributeFields, + } +} + +use kernel_mem_range::*; + +/// A virtual memory layout that is agnostic of the paging granularity that the +/// hardware MMU will use. +/// +/// Contains only special ranges, aka anything that is _not_ normal cacheable +/// DRAM. +static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [ + // Kernel stack + Descriptor { + virtual_range: || { + RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END) + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Kernel code and RO data + Descriptor { + virtual_range: || { + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols: + // + // [__ro_start, __ro_end) + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static __ro_start: u64; + + // The exclusive end of the read-only area, aka the address of + // the first byte _after_ the RO area. + static __ro_end: u64; + } + + unsafe { + // Notice the subtraction to turn the exclusive end into an + // inclusive end + RangeInclusive::new( + &__ro_start as *const _ as usize, + &__ro_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + // Kernel data and BSS + Descriptor { + virtual_range: || { + extern "C" { + static __ro_end: u64; + static __bss_end: u64; + } + + unsafe { + RangeInclusive::new( + &__ro_end as *const _ as usize, + &__bss_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Non-cacheable DRAM + Descriptor { + virtual_range: || { + RangeInclusive::new(map::virt::NON_CACHEABLE_START, map::virt::NON_CACHEABLE_END) + }, + translation: Translation::Offset(map::virt::CACHEABLE_START), + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::NonCacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + // Device MMIO + Descriptor { + virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, +]; + +/// For a given virtual address, find and return the output address and +/// according attributes. +/// +/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal +/// cacheable DRAM. +fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > map::END { + return Err("Address out of range."); + } + + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + if (i.virtual_range)().contains(&virt_addr) { + let output_addr = match i.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())) +} diff --git a/0D_cache_performance/src/memory/mmu.rs b/0D_cache_performance/src/memory/mmu.rs new file mode 100644 index 00000000..72405e74 --- /dev/null +++ b/0D_cache_performance/src/memory/mmu.rs @@ -0,0 +1,345 @@ +/* + * 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 crate::memory::{get_virt_addr_properties, AttributeFields}; +use cortex_a::{barrier, regs::*}; +use register::register_bitfields; + +register_bitfields! {u64, + // AArch64 Reference Manual page 2150 + STAGE1_DESCRIPTOR [ + /// Privileged execute-never + PXN OFFSET(53) 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 + ] + ] +} + +const FOUR_KIB: usize = 4 * 1024; +const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024) + +const TWO_MIB: usize = 2 * 1024 * 1024; +const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024) + +/// A descriptor pointing to the next page table. +struct TableDescriptor(register::FieldValue); + +impl TableDescriptor { + fn new(next_lvl_table_addr: usize) -> Result { + if next_lvl_table_addr % FOUR_KIB != 0 { + return Err("TableDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT; + + Ok(TableDescriptor( + STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// A function that maps the generic memory range attributes to HW-specific +/// attributes of the MMU. +fn into_mmu_attributes( + attribute_fields: AttributeFields, +) -> register::FieldValue { + use crate::memory::{AccessPermissions, MemAttributes}; + + // Memory attributes + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::NonCacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) + } + MemAttributes::Device => { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // 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::True + } else { + STAGE1_DESCRIPTOR::PXN::False + }; + + desc +} + +/// A Level2 block descriptor with 2 MiB aperture. +/// +/// The output points to physical memory. +struct Lvl2BlockDescriptor(register::FieldValue); + +impl Lvl2BlockDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % TWO_MIB != 0 { + return Err("BlockDescriptor: Address is not 2 MiB aligned."); + } + + let shifted = output_addr >> TWO_MIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// A page descriptor with 4 KiB aperture. +/// +/// The output points to physical memory. +struct PageDescriptor(register::FieldValue); + +impl PageDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % FOUR_KIB != 0 { + return Err("PageDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = output_addr >> FOUR_KIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value + } +} + +/// Constants for indexing the MAIR_EL1. +#[allow(dead_code)] +mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; + pub const NORMAL_NON_CACHEABLE: u64 = 2; +} + +/// Setup function for the MAIR_EL1 register. +fn set_up_mair() { + // Define the three memory types that we will map. Cacheable and + // non-cacheable normal DRAM, and device. + MAIR_EL1.write( + // Attribute 2 + MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable + + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable + + // Attribute 1 + + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc + + // Attribute 0 + + MAIR_EL1::Attr0_HIGH::Device + + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, + ); +} + +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 + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// A wrapper struct is needed here so that the align attribute can be used. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +/// The LVL2 page table containng the 2 MiB entries. +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// The LVL3 page table containing the 4 KiB entries. +/// +/// The first entry of the LVL2_TABLE will forward to this table. +static mut LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 GiB of address space. +/// +/// The first 2 MiB are 4 KiB granule, the rest 2 MiB. +pub unsafe fn init() -> Result<(), &'static str> { + // Prepare the memory attribute indirection register. + set_up_mair(); + + // Point the first 2 MiB of virtual addresses to the follow-up LVL3 + // page-table. + LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) { + Err(s) => return Err(s), + Ok(d) => d.value(), + }; + + // 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 << TWO_MIB_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 Lvl2BlockDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; + + *entry = block_desc.value(); + } + + // 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 << FOUR_KIB_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 PageDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; + + *entry = page_desc.value(); + } + + // Point to the LVL2 table base address in TTBR0. + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); + + // 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); + + Ok(()) +} diff --git a/0D_cache_performance/src/mmu.rs b/0D_cache_performance/src/mmu.rs deleted file mode 100644 index 6198bb02..00000000 --- a/0D_cache_performance/src/mmu.rs +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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 cortex_a::{barrier, regs::*}; -use register::register_bitfields; - -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 GiB of address space. -pub unsafe fn init() { - // First, define the three memory types that we will map. Cacheable and - // non-cacheable normal DRAM, and device. - MAIR_EL1.write( - // Attribute 2 - MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable - + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable - - // Attribute 1 - + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc - + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc - - // Attribute 0 - + MAIR_EL1::Attr0_HIGH::Device - + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, - ); - - // Descriptive consts for indexing into the correct MAIR_EL1 attributes. - mod mair { - pub const DEVICE: u64 = 0; - pub const NORMAL: u64 = 1; - pub const NORMAL_NON_CACHEABLE: u64 = 2; - } - - // The first 2 MiB. - // - // Set up the first LVL2 entry, pointing to the base address of a follow-up - // table containing 4 KiB pages. - // - // 0x0000_0000_0000_0000 | - // |> 2 MiB - // 0x0000_0000_001F_FFFF | - 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; - - // The second 2 MiB as block entry. - // - // Mapped as non-cacheable. - // - // 0x0000_0000_0020_0000 | - // |> 2 MiB - // 0x0000_0000_003F_FFFF | - LVL2_TABLE.entries[1] = (STAGE1_DESCRIPTOR::VALID::True - + STAGE1_DESCRIPTOR::TYPE::Block - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) - + STAGE1_DESCRIPTOR::AP::RW_EL1 - + STAGE1_DESCRIPTOR::SH::OuterShareable - + STAGE1_DESCRIPTOR::AF::True - // This translation is accessed for virtual 0x200000. Point to physical - // 0x400000, aka the third phyiscal 2 MiB DRAM block (third block == 2, - // because we start counting at 0). - // - // Here, we configure it non-cacheable. - + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(2) - + STAGE1_DESCRIPTOR::XN::True) - .value; - - // Fill the rest of the LVL2 (2 MiB) entries as block descriptors. - // - // Differentiate between - // - cacheable DRAM - // - device memory - // - // Ranges are stored in memory.rs. - // - // 0x0000_0000_0040_0000 | - // |> 1004 MiB cacheable DRAM - // 0x0000_0000_3EFF_FFFF | - // 0x0000_0000_3F00_0000 | - // |> 16 MiB device (MMIO) - // 0x0000_0000_4000_0000 | - let mmio_first_block_index: u64 = (super::MMIO_BASE >> 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). Start at the third 2 MiB DRAM block, which will point - // virtual 0x400000 to physical 0x400000, configured as cacheable memory. - for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) { - let j: u64 = i as u64; - - let mem_attr = if j >= mmio_first_block_index { - 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 (ro_first_page_index..=ro_last_page_index).contains(&j) { - STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False - } else { - STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True - }; - - *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/0D_cache_performance/src/uart.rs b/0D_cache_performance/src/uart.rs index 7287ec75..093ef6a7 100644 --- a/0D_cache_performance/src/uart.rs +++ b/0D_cache_performance/src/uart.rs @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2018 Andre Richter + * 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 @@ -22,7 +22,6 @@ * SOFTWARE. */ -use super::MMIO_BASE; use crate::delays; use crate::gpio; use crate::mbox; @@ -120,8 +119,6 @@ register_bitfields! { ] } -pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000; - #[allow(non_snake_case)] #[repr(C)] pub struct RegisterBlock { @@ -143,7 +140,7 @@ pub enum UartError { pub type Result = ::core::result::Result; pub struct Uart { - uart_base: u32, + base_addr: usize, } impl ops::Deref for Uart { @@ -155,13 +152,13 @@ impl ops::Deref for Uart { } impl Uart { - pub fn new(uart_base: u32) -> Uart { - Uart { uart_base } + pub fn new(base_addr: usize) -> Uart { + Uart { base_addr } } /// Returns a pointer to the register block fn ptr(&self) -> *const RegisterBlock { - self.uart_base as *const _ + self.base_addr as *const _ } ///Set baud rate and characteristics (115200 8N1) and map to GPIO diff --git a/0E_global_println/kernel8 b/0E_global_println/kernel8 index 1af89f942f7d2a8592b25cf351b6281d13548e1a..c078e488cfa2c1249ba39839bc02eff6e5c0369c 100755 GIT binary patch literal 90232 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 delta 10204 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 GPIO { + pub fn new(base_addr: usize) -> GPIO { GPIO { base_addr } } diff --git a/0E_global_println/src/devices/hw/pl011_uart.rs b/0E_global_println/src/devices/hw/uart.rs similarity index 95% rename from 0E_global_println/src/devices/hw/pl011_uart.rs rename to 0E_global_println/src/devices/hw/uart.rs index f98534e4..c27b62d6 100644 --- a/0E_global_println/src/devices/hw/pl011_uart.rs +++ b/0E_global_println/src/devices/hw/uart.rs @@ -135,16 +135,16 @@ pub struct RegisterBlock { ICR: WriteOnly, // 0x44 } -pub enum PL011UartError { +pub enum UartError { MailboxError, } -pub type Result = ::core::result::Result; +pub type Result = ::core::result::Result; -pub struct PL011Uart { - base_addr: u32, +pub struct Uart { + base_addr: usize, } -impl ops::Deref for PL011Uart { +impl ops::Deref for Uart { type Target = RegisterBlock; fn deref(&self) -> &Self::Target { @@ -152,9 +152,9 @@ impl ops::Deref for PL011Uart { } } -impl PL011Uart { - pub fn new(base_addr: u32) -> PL011Uart { - PL011Uart { base_addr } +impl Uart { + pub fn new(base_addr: usize) -> Uart { + Uart { base_addr } } /// Returns a pointer to the register block @@ -188,7 +188,7 @@ impl PL011Uart { compiler_fence(Ordering::Release); if v_mbox.call(videocore_mbox::channel::PROP).is_err() { - return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set + return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set }; // map UART0 to GPIO pins @@ -217,14 +217,14 @@ impl PL011Uart { } } -impl Drop for PL011Uart { +impl Drop for Uart { fn drop(&mut self) { self.CR .write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled); } } -impl ConsoleOps for PL011Uart { +impl ConsoleOps for Uart { /// Send a character fn putc(&self, c: char) { // wait until we can send diff --git a/0E_global_println/src/devices/hw/videocore_mbox.rs b/0E_global_println/src/devices/hw/videocore_mbox.rs index 1409f54a..0f6e294b 100644 --- a/0E_global_println/src/devices/hw/videocore_mbox.rs +++ b/0E_global_println/src/devices/hw/videocore_mbox.rs @@ -87,7 +87,7 @@ pub struct VideocoreMbox { // can handle it properly. Hence, put it at the start of the struct so that // the align attribute is effective. pub buffer: [u32; 36], - base_addr: u32, + base_addr: usize, } /// Deref to RegisterBlock @@ -109,7 +109,7 @@ impl ops::Deref for VideocoreMbox { } impl VideocoreMbox { - pub fn new(base_addr: u32) -> VideocoreMbox { + pub fn new(base_addr: usize) -> VideocoreMbox { VideocoreMbox { buffer: [0; 36], base_addr, diff --git a/0E_global_println/src/devices/virt/console.rs b/0E_global_println/src/devices/virt/console.rs index 088971c6..84244da8 100644 --- a/0E_global_println/src/devices/virt/console.rs +++ b/0E_global_println/src/devices/virt/console.rs @@ -47,12 +47,12 @@ impl ConsoleOps for NullConsole {} /// Possible outputs which the console can store. pub enum Output { None(NullConsole), - PL011Uart(hw::PL011Uart), + Uart(hw::Uart), } -impl From for Output { - fn from(instance: hw::PL011Uart) -> Self { - Output::PL011Uart(instance) +impl From for Output { + fn from(instance: hw::Uart) -> Self { + Output::Uart(instance) } } @@ -71,7 +71,7 @@ impl Console { fn current_ptr(&self) -> &dyn ConsoleOps { match &self.output { Output::None(i) => i, - Output::PL011Uart(i) => i, + Output::Uart(i) => i, } } diff --git a/0E_global_println/src/main.rs b/0E_global_println/src/main.rs index bd9fb8e3..58534756 100644 --- a/0E_global_println/src/main.rs +++ b/0E_global_println/src/main.rs @@ -50,17 +50,17 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Instantiate GPIO device //------------------------------------------------------------ - let gpio = hw::GPIO::new(memory::map::GPIO_BASE); + let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE); //------------------------------------------------------------ // Instantiate Videocore Mailbox //------------------------------------------------------------ - let mut v_mbox = hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE); + let mut v_mbox = hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE); //------------------------------------------------------------ // Instantiate PL011 UART and put it in CONSOLE //------------------------------------------------------------ - let uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); + let uart = hw::Uart::new(memory::map::physical::UART_BASE); match uart.init(&mut v_mbox, &gpio) { Ok(_) => { @@ -89,9 +89,11 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Bring up memory subsystem //------------------------------------------------------------ - print!("[2] Switching MMU on now... "); - unsafe { memory::mmu::init() }; - println!("MMU online."); + if unsafe { memory::mmu::init() }.is_err() { + println!("[2][Error] Could not set up MMU. Aborting."); + } else { + println!("[2] MMU online."); + } memory::print_layout(); diff --git a/0E_global_println/src/memory.rs b/0E_global_println/src/memory.rs index ee828c9d..dd00a8f4 100644 --- a/0E_global_println/src/memory.rs +++ b/0E_global_println/src/memory.rs @@ -23,91 +23,246 @@ */ use crate::println; +use core::fmt; +use core::ops::RangeInclusive; pub mod mmu; -/// The system memory map. +/// System memory map. #[rustfmt::skip] pub mod map { - pub const KERN_STACK_BOT: u32 = 0x0000_0000; - pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x3FFF_FFFF; - pub const MMIO_BASE: u32 = 0x3F00_0000; - pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; - pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; - pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000; + pub mod physical { + pub const MMIO_BASE: usize = 0x3F00_0000; + pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880; + pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000; + pub const UART_BASE: usize = MMIO_BASE + 0x0020_1000; + pub const MMIO_END: usize = super::END; + } - pub const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; + pub mod virt { + pub const KERN_STACK_START: usize = super::START; + pub const KERN_STACK_END: usize = 0x0007_FFFF; + } } -const PAGESIZE: u64 = 4096; +/// Types used for compiling the virtual memory layout of the kernel using +/// address ranges. +pub mod kernel_mem_range { + use core::ops::RangeInclusive; -fn get_ro_start_end() -> (u64, u64) { - // 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 __ro_start: u64; + #[allow(dead_code)] + #[derive(Copy, Clone)] + pub enum MemAttributes { + CacheableDRAM, + NonCacheableDRAM, + Device, + } - // The non-inclusive end of the read-only area, aka the address of the - // first byte _after_ the RO area. - static __ro_end: u64; + #[derive(Copy, Clone)] + pub enum AccessPermissions { + ReadOnly, + ReadWrite, } - unsafe { - // Notice the subtraction to calculate the last page index of the RO - // area and not the first page index after the RO area. - ( - &__ro_start as *const _ as u64, - &__ro_end as *const _ as u64 - 1, - ) + #[allow(dead_code)] + #[derive(Copy, Clone)] + pub enum Translation { + Identity, + Offset(usize), + } + + #[derive(Copy, Clone)] + pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, + } + + impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } + } + + pub struct Descriptor { + pub name: &'static str, + pub virtual_range: fn() -> RangeInclusive, + pub translation: Translation, + pub attribute_fields: AttributeFields, } } -pub fn print_layout() { - use crate::memory::map::*; +use kernel_mem_range::*; - // log2(1024) - const KIB_RSHIFT: u32 = 10; +/// A virtual memory layout that is agnostic of the paging granularity that the +/// hardware MMU will use. +/// +/// Contains only special ranges, aka anything that is _not_ normal cacheable +/// DRAM. +static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 4] = [ + Descriptor { + name: "Kernel stack", + virtual_range: || { + RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END) + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Kernel code and RO data", + virtual_range: || { + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols: + // + // [__ro_start, __ro_end) + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static __ro_start: u64; - // log2(1024 * 1024) - const MIB_RSHIFT: u32 = 20; + // The exclusive end of the read-only area, aka the address of + // the first byte _after_ the RO area. + static __ro_end: u64; + } - println!("[i] Memory layout:"); + unsafe { + // Notice the subtraction to turn the exclusive end into an + // inclusive end + RangeInclusive::new( + &__ro_start as *const _ as usize, + &__ro_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + Descriptor { + name: "Kernel data and BSS", + virtual_range: || { + extern "C" { + static __ro_end: u64; + static __bss_end: u64; + } - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", - KERN_STACK_BOT, - KERN_STACK_TOP, - (KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT - ); + unsafe { + RangeInclusive::new( + &__ro_end as *const _ as usize, + &__bss_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Device MMIO", + virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, +]; - let (ro_start, ro_end) = get_ro_start_end(); - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", - ro_start, - ro_end, - (ro_end - ro_start + 1) >> KIB_RSHIFT - ); +/// For a given virtual address, find and return the output address and +/// according attributes. +/// +/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal +/// cacheable DRAM. +fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > map::END { + return Err("Address out of range."); + } - extern "C" { - static __bss_end: u64; + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + if (i.virtual_range)().contains(&virt_addr) { + let output_addr = match i.translation { + Translation::Identity => virt_addr, + Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()), + }; + + return Ok((output_addr, i.attribute_fields)); + } } - let start = ro_end + 1; - let end = unsafe { &__bss_end as *const _ as u64 } - 1; - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", - start, - end, - (end - start + 1) >> KIB_RSHIFT - ); - - println!( - " {:#010X} - {:#010X} | {: >4} MiB | Device MMIO", - MMIO_BASE, - PHYS_ADDR_MAX, - (PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT - ); + Ok((virt_addr, AttributeFields::default())) +} + +/// Human-readable output of a Descriptor. +impl fmt::Display for Descriptor { + 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::NonCacheableDRAM => "NC", + 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 + ) + } +} + +/// Print the kernel memory layout. +pub fn print_layout() { + println!("[i] Kernel memory layout:"); + + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + println!("{}", i); + } } diff --git a/0E_global_println/src/memory/mmu.rs b/0E_global_println/src/memory/mmu.rs index e44283cc..72405e74 100644 --- a/0E_global_println/src/memory/mmu.rs +++ b/0E_global_println/src/memory/mmu.rs @@ -22,14 +22,15 @@ * SOFTWARE. */ +use crate::memory::{get_virt_addr_properties, AttributeFields}; use cortex_a::{barrier, regs::*}; use register::register_bitfields; register_bitfields! {u64, // AArch64 Reference Manual page 2150 STAGE1_DESCRIPTOR [ - /// Execute-never - XN OFFSET(54) NUMBITS(1) [ + /// Privileged execute-never + PXN OFFSET(53) NUMBITS(1) [ False = 0, True = 1 ], @@ -73,42 +74,150 @@ register_bitfields! {u64, ] } -trait BaseAddr { - fn base_addr(&self) -> u64; +const FOUR_KIB: usize = 4 * 1024; +const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024) + +const TWO_MIB: usize = 2 * 1024 * 1024; +const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024) + +/// A descriptor pointing to the next page table. +struct TableDescriptor(register::FieldValue); + +impl TableDescriptor { + fn new(next_lvl_table_addr: usize) -> Result { + if next_lvl_table_addr % FOUR_KIB != 0 { + return Err("TableDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT; + + Ok(TableDescriptor( + STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), + )) + } + + fn value(&self) -> u64 { + self.0.value + } } -impl BaseAddr for [u64; 512] { - fn base_addr(&self) -> u64 { - self as *const u64 as u64 +/// A function that maps the generic memory range attributes to HW-specific +/// attributes of the MMU. +fn into_mmu_attributes( + attribute_fields: AttributeFields, +) -> register::FieldValue { + use crate::memory::{AccessPermissions, MemAttributes}; + + // Memory attributes + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::NonCacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) + } + MemAttributes::Device => { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // 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::True + } else { + STAGE1_DESCRIPTOR::PXN::False + }; + + desc +} + +/// A Level2 block descriptor with 2 MiB aperture. +/// +/// The output points to physical memory. +struct Lvl2BlockDescriptor(register::FieldValue); + +impl Lvl2BlockDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % TWO_MIB != 0 { + return Err("BlockDescriptor: Address is not 2 MiB aligned."); + } + + let shifted = output_addr >> TWO_MIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value } } -const NUM_ENTRIES_4KIB: usize = 512; +/// A page descriptor with 4 KiB aperture. +/// +/// The output points to physical memory. +struct PageDescriptor(register::FieldValue); + +impl PageDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % FOUR_KIB != 0 { + return Err("PageDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = output_addr >> FOUR_KIB_SHIFT; + + Ok(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), + )) + } -// 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], + fn value(&self) -> u64 { + self.0.value + } } -static mut LVL2_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; -static mut SINGLE_LVL3_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; +/// Constants for indexing the MAIR_EL1. +#[allow(dead_code)] +mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; + pub const NORMAL_NON_CACHEABLE: u64 = 2; +} -/// Set up identity mapped page tables for the first 1 GiB of address space. -pub unsafe fn init() { - // First, define the three memory types that we will map. Cacheable and +/// Setup function for the MAIR_EL1 register. +fn set_up_mair() { + // Define the three memory types that we will map. Cacheable and // non-cacheable normal DRAM, and device. MAIR_EL1.write( // Attribute 2 MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable - // Attribute 1 + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc @@ -116,92 +225,97 @@ pub unsafe fn init() { + MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, ); +} + +trait BaseAddr { + fn base_addr_u64(&self) -> u64; + fn base_addr_usize(&self) -> usize; +} - // Descriptive consts for indexing into the correct MAIR_EL1 attributes. - #[allow(dead_code)] - mod mair { - pub const DEVICE: u64 = 0; - pub const NORMAL: u64 = 1; - pub const NORMAL_NON_CACHEABLE: u64 = 2; +impl BaseAddr for [u64; 512] { + fn base_addr_u64(&self) -> u64 { + self as *const u64 as u64 } - // The first 2 MiB. - // - // Set up the first LVL2 entry, pointing to the base address of a follow-up - // table containing 4 KiB pages. - // - // 0x0000_0000_0000_0000 | - // |> 2 MiB - // 0x0000_0000_001F_FFFF | - 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; + fn base_addr_usize(&self) -> usize { + self as *const u64 as usize + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// A wrapper struct is needed here so that the align attribute can be used. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +/// The LVL2 page table containng the 2 MiB entries. +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// The LVL3 page table containing the 4 KiB entries. +/// +/// The first entry of the LVL2_TABLE will forward to this table. +static mut LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 GiB of address space. +/// +/// The first 2 MiB are 4 KiB granule, the rest 2 MiB. +pub unsafe fn init() -> Result<(), &'static str> { + // Prepare the memory attribute indirection register. + set_up_mair(); + + // Point the first 2 MiB of virtual addresses to the follow-up LVL3 + // page-table. + LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) { + Err(s) => return Err(s), + Ok(d) => d.value(), + }; // Fill the rest of the LVL2 (2 MiB) entries as block descriptors. // - // Differentiate between - // - cacheable DRAM - // - device memory - // - // Ranges are stored in memory.rs. - // - // 0x0000_0000_0020_0000 | - // |> 1006 MiB cacheable DRAM - // 0x0000_0000_3EFF_FFFF | - // 0x0000_0000_3F00_0000 | - // |> 16 MiB device (MMIO) - // 0x0000_0000_4000_0000 | - let mmio_first_block_index: u64 = (crate::memory::map::MMIO_BASE >> 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(1) which makes the iteration start at the second 2 MiB // block (0x20_0000). - for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { - let j: u64 = i as u64; + for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { + let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT; - let mem_attr = if j >= mmio_first_block_index { - STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) - } else { - STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), }; - *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. - let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end(); + let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; - let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; - let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; + *entry = block_desc.value(); + } - let common = STAGE1_DESCRIPTOR::VALID::True - + STAGE1_DESCRIPTOR::TYPE::Table - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) - + STAGE1_DESCRIPTOR::SH::InnerShareable - + STAGE1_DESCRIPTOR::AF::True; + // 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 << FOUR_KIB_SHIFT; - for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { - let j: u64 = i as u64; + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), + }; - let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { - STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False - } else { - STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True + let page_desc = match PageDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, }; - *entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; + *entry = page_desc.value(); } // Point to the LVL2 table base address in TTBR0. - TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr()); + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Configure various settings of stage 1 of the EL1 translation regime. let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange); @@ -226,4 +340,6 @@ pub unsafe fn init() { // Force MMU init to complete before next instruction barrier::isb(barrier::SY); + + Ok(()) } diff --git a/0F_DMA_memory/README.md b/0F_DMA_memory/README.md index 6e01a2d7..4d20bbed 100644 --- a/0F_DMA_memory/README.md +++ b/0F_DMA_memory/README.md @@ -8,16 +8,21 @@ This lesson will teach about: Uart later (which now needs the memory allocator that theoretically could fail - which the MiniUart could then print). + +## Output + ```console +ferris@box:~$ make raspboot + [0] MiniUart online. [1] Press a key to continue booting... Greetings fellow Rustacean! -[2] Switching MMU on now... MMU online. -[i] Memory layout: - 0x00000000 - 0x0007FFFF | 512 KiB | Kernel stack - 0x00080000 - 0x00083FFF | 16 KiB | Kernel code and RO data - 0x00084000 - 0x00087007 | 12 KiB | Kernel data and BSS - 0x00200000 - 0x005FFFFF | 4 MiB | DMA heap pool - 0x3F000000 - 0x3FFFFFFF | 16 MiB | Device MMIO +[2] MMU online. +[i] Kernel memory layout: + 0x00000000 - 0x0007FFFF | 512 KiB | C RW PXN | Kernel stack + 0x00080000 - 0x00083FFF | 16 KiB | C RO PX | Kernel code and RO data + 0x00084000 - 0x0008700F | 12 KiB | C RW PXN | Kernel data and BSS + 0x00200000 - 0x005FFFFF | 4 MiB | NC RW PXN | DMA heap pool + 0x3F000000 - 0x3FFFFFFF | 16 MiB | Dev RW PXN | Device MMIO [i] Global DMA Allocator: Allocated Addr 0x00200000 Size 0x90 [3] Videocore Mailbox set up (DMA mem heap allocation successful). diff --git a/0F_DMA_memory/kernel8 b/0F_DMA_memory/kernel8 index 48adf027baf35cc912207f9f9383204a237fa975..45c05e25cdb4d6dd8f63efd11cb28afa1095e5ce 100755 GIT binary patch literal 97008 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 diff --git a/0F_DMA_memory/kernel8.img b/0F_DMA_memory/kernel8.img index 6b55311e52fbfdefaa2c4538a5e3786a567d2433..194369cea83861a3e3469d3336914fb92151af1a 100755 GIT binary patch delta 7209 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 delta 6585 zcmb_g3vg7|dH(OcyVAX@=Pr^~FSK_hjI@&VS|J{`u~y*JMxfZN6nh%#L9(nGMLaBp z#9(kybelybRb?AY!gnM~m>#zTYM={jvjm~=vM+eqfoOw&wO3eD_F#GuHswA8Tu z&b@bqL}Bun+?l)Qp8x#k|Nry9&-V9m{e9d)Iq;^94$CB8jK3@lOGHDHyVA6XK;HPc zsCn8elvsyNt0THTy&*;8Fiy3kYSEG;O$6>Yc~atmWKA5XP~2_lO86`(lLZtTeVJ1t zt0^{(d2%uxZgWsQ!=HSdVp&9|#KubH@Ty!Tn<#MV!_h(|(jw>t#lNpqm=GTK{eW2v z*g&O{0~li-dCCgmioBOcD|LoUkVrCl!yUZBn0`@`tWB4Q+E@}D9!#ft579x!6wD8L zI8sEs9xDx(bK)mWR#GCz0;EMsNNKv+bRiFt<>DPX%46C6U>Huit#Yx})N13B+nPR8snZKE8_lY^-B2q#K|}Ad4dTR6{p7A@ZK`FRHoal37VBuzZmjji z;H1i+u<R=jPCIA!es*vS9iSM66h|jL~ z`A^4@`WU;FXY+ja!6JRG1yegkF+enCwFn$ zq`;N$ebN`o&bGEZYEg?UIeeo`mF!t{GlEp^ zs-PZjI^H^}-n2Qb?l0o4KUeco7B84(xd~^_;>dAcP}7s@1M}f|NiF{fHb;2loRhm6 zwa=4R%_MZlbjx(;s-;kw#TPrCN*|PwFZNwapZ{}#4^9dMALfGD9EFOpzEPx!)v45q zeK8ko!82IuZP*$L9Q1clf3GB&iZ(WMP4b*glEyjuTl&`QDLE1vYYT+nm9WP zcX_336JAIYM@_gS@9}HN;E`vp$u!YnuDJpWP2i&2z!5NTxhb@jr^8|)Ib0I;j9933 zFO0%GOUucsW1e-+(dT~+u!iqaNVHN9^OiI44hAdxo-bsxI=l?0{Co4)8J(!$@`eRh zdBBCmO#ryQVvN##6zbyo{0t^}C=^XD@ao@{6!Ns0aHyY|W~$}maRi zJTM1FzK5~b<*lC!>jx}_yft%SRM!B43%wO4kU1E0u7tzQ3)*}mXzgmdM<aC=vLMbv_G1udA4hGflo<(Pjjz3!huF)X`s ztaqwK)FyXH+8CT|I)g&va2$z40_O-Lgggc(%702^fl z0aBu}2i0p-DD>XC6@P=!Dd_~4p;K#@lz1t{7#*ga4wy&XZC@BTV^d~nALVAXm(326 zLd%%;q|7(`QutW!WDAFTfX~i+D7t`RW7SA*^L=S(jxXVR)|c>YS46lG*5S;fkwS*1pjwYRO;>FMWiX1n8dK--uaYO~q8J=?C|*J_=A6uv!{%W= z!a<o6* zqYRv5H9L(Q;+8^31DDl3g67o4Wp@u?d?v|ght8sd-o*Q2(PR!adlhPUqc$dRUZy!0 zCv``YkL0^JTQ}1Taj)f1Z#b*hcy?-{biHBFzQ{o~186v&@oiT;c3+I_YH!XOtB$7*<}{dJ7H93#?3P*0 zYh!hsmJ7SwD!{c!;Ph(|YxX4MVaA%Nma%63VY1fK=G{2n%5cq?Tn{;G1L$xB-Bi;X zIB1F2{!nMlW)~!!VmmVMhleVaQ8X@Ll&BGuD3+0tRfn8-bMam-wky-cnYd+VD6kF! zV-?zHn99Raa+)v3HnL5|BK5SPAB+k)K~@tQak!|E8!STxoQtQvlKU4AB{qzpM~@25 z;0SI;Y`laKSB%5VN#Bnf&dM4>X4Cj;emI4hFf}i)^d(Enwlu2yqXtLAr zMn1Ef+kNtF_m`a)>WRWv`AZIUSN>8qbT2ICFFMr! z%zuXGa?}S4mKM8<^@eT)grX-b8e<-#Hp$Ui_^IrC6cjlMwbmce^T&g zY^Qs$NZ)OCa6=L7#4(1%#v;WtP@q;9zIaa={$}B;yij@CS=7fb8ltvE%KK(o)w5Bb zTX}QtoEDw_q3pJmpeYST^?x&X1e=H}h!p&%TXCl>uVO9CDfi$aby>wToLhaXVlnq$ z>Y0j~K&?k9^A%o7Z7jSL7LHtIQ2`#bq3db6xpT|5-ktL1_MO|lwfzB{4$ojYj(}%@ z{!1hqPo*sHmgTiwt1ztX?A_YAS^nns_Kt2_#KcF-razyVbF^*xvwq?Zz~|;1-lacG z5U?>OM7RJE#pCgYOpLZg56c9r$K%O(H|jW?+Fs*S!_@~li`rA;49pj*W81Txa{Jb2 zW&h*yv+dj4ZS=bYyi>mA_ZtdunG(&X&uHrOSGBb03Jd(f4Bj*u6eJil>so!Jxl!KK z+5Wuz{I+eI8R_3o@BmRl)iWkxq-Xt+O~Reu?&|22o151>ZkYJ@BYd0g6P5E_;sM#9 zf1JVbKh*4Vyi&DQ-dgxXm4Uy-T=*XdXiXrg3#&@Fu-aJV%nO@%h348RjEFo6t37vF z{lV<&z+F~9m|Y#d%j!SOu5#5*^<1s&eXPQek$D??epYSFQMUBwnmJFn^;ET*@BKAD zQ={^A5A#cE)m3#%`Lfb?@6&S23tihgHg#=%4toEZyltzzb=wP7 zRaG)0e(nx@17ma7*3K&3Kr`lgs+zatQ9jqJKC|RWev?=I2*aRPb<{sB<<=Q2tJKD2 z)qys1rc(!lckveC{jtNqGJc5?<2rV^!V^iXoAduR=Lw}G#`h!xf5sf&NipVEj4?p{ z=EAvq5V&YDHpo_E{Be>orY0NXpt=5mDTpo|evs4jr{9Es-yDyd<0*ZFZ(*Z)_Wl}m z$MSzKxRPfOHTTXEiR6jut`#dC{q@GWY$6QSpFY3h@3`Vk%M3WW7ybDwbDr?<9ZS_i z55Dg>XM!tCw8>?v|Dgl<2bUYWx0>KuD6b6P2bZ6|@sKE-u37mhkDqj|NY*hD=}b0_ Iok)lO2Z0KE#Q*>R diff --git a/0F_DMA_memory/src/devices/hw/gpio.rs b/0F_DMA_memory/src/devices/hw/gpio.rs index 0d06dfa3..7affea08 100644 --- a/0F_DMA_memory/src/devices/hw/gpio.rs +++ b/0F_DMA_memory/src/devices/hw/gpio.rs @@ -97,7 +97,7 @@ pub struct RegisterBlock { /// Public interface to the GPIO MMIO area pub struct GPIO { - base_addr: u32, + base_addr: usize, } impl ops::Deref for GPIO { @@ -109,7 +109,7 @@ impl ops::Deref for GPIO { } impl GPIO { - pub fn new(base_addr: u32) -> GPIO { + pub fn new(base_addr: usize) -> GPIO { GPIO { base_addr } } diff --git a/0F_DMA_memory/src/devices/hw/mini_uart.rs b/0F_DMA_memory/src/devices/hw/mini_uart.rs index 4f814268..a03ed8e4 100644 --- a/0F_DMA_memory/src/devices/hw/mini_uart.rs +++ b/0F_DMA_memory/src/devices/hw/mini_uart.rs @@ -123,7 +123,7 @@ pub struct RegisterBlock { } pub struct MiniUart { - base_addr: u32, + base_addr: usize, } /// Deref to RegisterBlock @@ -145,7 +145,7 @@ impl ops::Deref for MiniUart { } impl MiniUart { - pub fn new(base_addr: u32) -> MiniUart { + pub fn new(base_addr: usize) -> MiniUart { MiniUart { base_addr } } diff --git a/0F_DMA_memory/src/devices/hw/pl011_uart.rs b/0F_DMA_memory/src/devices/hw/pl011_uart.rs index f98534e4..44580d35 100644 --- a/0F_DMA_memory/src/devices/hw/pl011_uart.rs +++ b/0F_DMA_memory/src/devices/hw/pl011_uart.rs @@ -141,7 +141,7 @@ pub enum PL011UartError { pub type Result = ::core::result::Result; pub struct PL011Uart { - base_addr: u32, + base_addr: usize, } impl ops::Deref for PL011Uart { @@ -153,7 +153,7 @@ impl ops::Deref for PL011Uart { } impl PL011Uart { - pub fn new(base_addr: u32) -> PL011Uart { + pub fn new(base_addr: usize) -> PL011Uart { PL011Uart { base_addr } } diff --git a/0F_DMA_memory/src/devices/hw/videocore_mbox.rs b/0F_DMA_memory/src/devices/hw/videocore_mbox.rs index fad36461..729777aa 100644 --- a/0F_DMA_memory/src/devices/hw/videocore_mbox.rs +++ b/0F_DMA_memory/src/devices/hw/videocore_mbox.rs @@ -87,7 +87,7 @@ const MBOX_SIZE: usize = 36; // Public interface to the mailbox pub struct VideocoreMbox<'a> { pub buffer: &'a mut [u32], - base_addr: u32, + base_addr: usize, } /// Deref to RegisterBlock @@ -109,7 +109,7 @@ impl<'a> ops::Deref for VideocoreMbox<'a> { } impl<'a> VideocoreMbox<'a> { - pub fn new(base_addr: u32) -> ::core::result::Result, ()> { + pub fn new(base_addr: usize) -> ::core::result::Result, ()> { let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT)); if ret.is_err() { diff --git a/0F_DMA_memory/src/main.rs b/0F_DMA_memory/src/main.rs index a72c9a13..4f4b2405 100644 --- a/0F_DMA_memory/src/main.rs +++ b/0F_DMA_memory/src/main.rs @@ -45,8 +45,8 @@ static CONSOLE: sync::NullLock = /// non-cacheable in the page tables. static DMA_ALLOCATOR: sync::NullLock = sync::NullLock::new(memory::BumpAllocator::new( - memory::map::DMA_HEAP_START as usize, - memory::map::DMA_HEAP_END as usize, + memory::map::virt::DMA_HEAP_START as usize, + memory::map::virt::DMA_HEAP_END as usize, "Global DMA Allocator", // Try the following arguments instead to see the PL011 UART init // fail. It will cause the allocator to use memory that are marked @@ -66,12 +66,12 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Instantiate GPIO device //------------------------------------------------------------ - let gpio = hw::GPIO::new(memory::map::GPIO_BASE); + let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE); //------------------------------------------------------------ // Instantiate MiniUart //------------------------------------------------------------ - let mini_uart = hw::MiniUart::new(memory::map::MINI_UART_BASE); + let mini_uart = hw::MiniUart::new(memory::map::physical::MINI_UART_BASE); mini_uart.init(&gpio); CONSOLE.lock(|c| { @@ -90,24 +90,26 @@ fn kernel_entry() -> ! { }); println!("Greetings fellow Rustacean!"); - //------------------------------------------------------------ - // Bring up memory subsystem - //------------------------------------------------------------ - print!("[2] Switching MMU on now... "); - unsafe { memory::mmu::init() }; - println!("MMU online."); - - memory::print_layout(); - // We are now in a state where every next step can fail, but we can handle // the error with feedback for the user and fall through to our UART // loopback. 'init: { + //------------------------------------------------------------ + // Bring up memory subsystem + //------------------------------------------------------------ + if unsafe { memory::mmu::init() }.is_err() { + println!("[2][Error] Could not set up MMU. Aborting."); + break 'init; + }; + println!("[2] MMU online."); + + memory::print_layout(); + //------------------------------------------------------------ // Instantiate Videocore Mailbox //------------------------------------------------------------ let mut v_mbox; - match hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE) { + match hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE) { Ok(i) => { println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful)."); v_mbox = i; @@ -122,7 +124,7 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Instantiate PL011 UART and replace MiniUart with it in CONSOLE //------------------------------------------------------------ - let pl011_uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); + let pl011_uart = hw::PL011Uart::new(memory::map::physical::PL011_UART_BASE); // uart.init() will reconfigure the GPIO, which causes a race against // the MiniUart that is still putting out characters on the physical diff --git a/0F_DMA_memory/src/memory.rs b/0F_DMA_memory/src/memory.rs index 42535974..f1da0c87 100644 --- a/0F_DMA_memory/src/memory.rs +++ b/0F_DMA_memory/src/memory.rs @@ -23,111 +23,270 @@ */ use crate::println; +use core::fmt; +use core::ops::RangeInclusive; mod bump_allocator; pub use bump_allocator::BumpAllocator; pub mod mmu; -/// The system memory map. +/// System memory map. #[rustfmt::skip] pub mod map { - pub const KERN_STACK_BOT: u32 = 0x0000_0000; - pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x3FFF_FFFF; - /// The second 2 MiB block. - pub const DMA_HEAP_START: u32 = 0x0020_0000; - pub const DMA_HEAP_END: u32 = 0x005F_FFFF; + pub mod physical { + pub const MMIO_BASE: usize = 0x3F00_0000; + pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880; + pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000; + pub const PL011_UART_BASE: usize = MMIO_BASE + 0x0020_1000; + pub const MINI_UART_BASE: usize = MMIO_BASE + 0x0021_5000; + pub const MMIO_END: usize = super::END; + } - pub const MMIO_BASE: u32 = 0x3F00_0000; - pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; - pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; - pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000; - pub const MINI_UART_BASE: u32 = MMIO_BASE + 0x0021_5000; + pub mod virt { + pub const KERN_STACK_START: usize = super::START; + pub const KERN_STACK_END: usize = 0x0007_FFFF; - pub const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; + // The second 2 MiB block. + pub const DMA_HEAP_START: usize = 0x0020_0000; + pub const DMA_HEAP_END: usize = 0x005F_FFFF; + } } -const PAGESIZE: u64 = 4096; +/// Types used for compiling the virtual memory layout of the kernel using +/// address ranges. +pub mod kernel_mem_range { + use core::ops::RangeInclusive; -#[inline] -fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { - (addr + (alignment - 1)) & !(alignment - 1) + #[derive(Copy, Clone)] + pub enum MemAttributes { + CacheableDRAM, + NonCacheableDRAM, + Device, + } + + #[derive(Copy, Clone)] + pub enum AccessPermissions { + ReadOnly, + ReadWrite, + } + + #[allow(dead_code)] + #[derive(Copy, Clone)] + pub enum Translation { + Identity, + Offset(usize), + } + + #[derive(Copy, Clone)] + pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, + } + + impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } + } + + pub struct Descriptor { + pub name: &'static str, + pub virtual_range: fn() -> RangeInclusive, + pub translation: Translation, + pub attribute_fields: AttributeFields, + } } -fn get_ro_start_end() -> (u64, u64) { - // 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 __ro_start: u64; - - // The non-inclusive end of the read-only area, aka the address of the - // first byte _after_ the RO area. - static __ro_end: u64; +use kernel_mem_range::*; + +/// A virtual memory layout that is agnostic of the paging granularity that the +/// hardware MMU will use. +/// +/// Contains only special ranges, aka anything that is _not_ normal cacheable +/// DRAM. +static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [ + Descriptor { + name: "Kernel stack", + virtual_range: || { + RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END) + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Kernel code and RO data", + virtual_range: || { + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols: + // + // [__ro_start, __ro_end) + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static __ro_start: u64; + + // The exclusive end of the read-only area, aka the address of + // the first byte _after_ the RO area. + static __ro_end: u64; + } + + unsafe { + // Notice the subtraction to turn the exclusive end into an + // inclusive end + RangeInclusive::new( + &__ro_start as *const _ as usize, + &__ro_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + Descriptor { + name: "Kernel data and BSS", + virtual_range: || { + extern "C" { + static __ro_end: u64; + static __bss_end: u64; + } + + unsafe { + RangeInclusive::new( + &__ro_end as *const _ as usize, + &__bss_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "DMA heap pool", + virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::NonCacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Device MMIO", + virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, +]; + +/// For a given virtual address, find and return the output address and +/// according attributes. +/// +/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal +/// cacheable DRAM. +fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > map::END { + return Err("Address out of range."); } - unsafe { - // Notice the subtraction to calculate the last page index of the RO - // area and not the first page index after the RO area. - ( - &__ro_start as *const _ as u64, - &__ro_end as *const _ as u64 - 1, - ) + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + if (i.virtual_range)().contains(&virt_addr) { + let output_addr = match i.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())) } -pub fn print_layout() { - use crate::memory::map::*; +/// Human-readable output of a Descriptor. +impl fmt::Display for Descriptor { + 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; - // log2(1024) - const KIB_RSHIFT: u32 = 10; + 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") + }; - // log2(1024 * 1024) - const MIB_RSHIFT: u32 = 20; + let attr = match self.attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => "C", + MemAttributes::NonCacheableDRAM => "NC", + MemAttributes::Device => "Dev", + }; - println!("[i] Memory layout:"); + let acc_p = match self.attribute_fields.acc_perms { + AccessPermissions::ReadOnly => "RO", + AccessPermissions::ReadWrite => "RW", + }; - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", - KERN_STACK_BOT, - KERN_STACK_TOP, - (KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT - ); + let xn = if self.attribute_fields.execute_never { + "PXN" + } else { + "PX" + }; - let (ro_start, ro_end) = get_ro_start_end(); - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", - ro_start, - ro_end, - (ro_end - ro_start + 1) >> KIB_RSHIFT - ); + write!( + f, + " {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}", + start, end, size, unit, attr, acc_p, xn, self.name + ) + } +} + +/// Print the kernel memory layout. +pub fn print_layout() { + println!("[i] Kernel memory layout:"); - extern "C" { - static __bss_end: u64; + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + println!("{}", i); } +} - let start = ro_end + 1; - let end = unsafe { &__bss_end as *const _ as u64 } - 1; - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", - start, - end, - (end - start + 1) >> KIB_RSHIFT - ); - - println!( - " {:#010X} - {:#010X} | {: >4} MiB | DMA heap pool", - DMA_HEAP_START, - DMA_HEAP_END, - (DMA_HEAP_END - DMA_HEAP_START + 1) >> MIB_RSHIFT - ); - - println!( - " {:#010X} - {:#010X} | {: >4} MiB | Device MMIO", - MMIO_BASE, - PHYS_ADDR_MAX, - (PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT - ); +/// Calculate the next possible aligned address without sanity checking the +/// input parameters. +#[inline] +fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { + (addr + (alignment - 1)) & !(alignment - 1) } diff --git a/0F_DMA_memory/src/memory/mmu.rs b/0F_DMA_memory/src/memory/mmu.rs index 1ead372f..72405e74 100644 --- a/0F_DMA_memory/src/memory/mmu.rs +++ b/0F_DMA_memory/src/memory/mmu.rs @@ -22,14 +22,15 @@ * SOFTWARE. */ +use crate::memory::{get_virt_addr_properties, AttributeFields}; use cortex_a::{barrier, regs::*}; use register::register_bitfields; register_bitfields! {u64, // AArch64 Reference Manual page 2150 STAGE1_DESCRIPTOR [ - /// Execute-never - XN OFFSET(54) NUMBITS(1) [ + /// Privileged execute-never + PXN OFFSET(53) NUMBITS(1) [ False = 0, True = 1 ], @@ -73,42 +74,150 @@ register_bitfields! {u64, ] } -trait BaseAddr { - fn base_addr(&self) -> u64; +const FOUR_KIB: usize = 4 * 1024; +const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024) + +const TWO_MIB: usize = 2 * 1024 * 1024; +const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024) + +/// A descriptor pointing to the next page table. +struct TableDescriptor(register::FieldValue); + +impl TableDescriptor { + fn new(next_lvl_table_addr: usize) -> Result { + if next_lvl_table_addr % FOUR_KIB != 0 { + return Err("TableDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT; + + Ok(TableDescriptor( + STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), + )) + } + + fn value(&self) -> u64 { + self.0.value + } } -impl BaseAddr for [u64; 512] { - fn base_addr(&self) -> u64 { - self as *const u64 as u64 +/// A function that maps the generic memory range attributes to HW-specific +/// attributes of the MMU. +fn into_mmu_attributes( + attribute_fields: AttributeFields, +) -> register::FieldValue { + use crate::memory::{AccessPermissions, MemAttributes}; + + // Memory attributes + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::NonCacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) + } + MemAttributes::Device => { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // 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::True + } else { + STAGE1_DESCRIPTOR::PXN::False + }; + + desc +} + +/// A Level2 block descriptor with 2 MiB aperture. +/// +/// The output points to physical memory. +struct Lvl2BlockDescriptor(register::FieldValue); + +impl Lvl2BlockDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % TWO_MIB != 0 { + return Err("BlockDescriptor: Address is not 2 MiB aligned."); + } + + let shifted = output_addr >> TWO_MIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value } } -const NUM_ENTRIES_4KIB: usize = 512; +/// A page descriptor with 4 KiB aperture. +/// +/// The output points to physical memory. +struct PageDescriptor(register::FieldValue); + +impl PageDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % FOUR_KIB != 0 { + return Err("PageDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = output_addr >> FOUR_KIB_SHIFT; + + Ok(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), + )) + } -// 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], + fn value(&self) -> u64 { + self.0.value + } } -static mut LVL2_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; -static mut SINGLE_LVL3_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; +/// Constants for indexing the MAIR_EL1. +#[allow(dead_code)] +mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; + pub const NORMAL_NON_CACHEABLE: u64 = 2; +} -/// Set up identity mapped page tables for the first 1 GiB of address space. -pub unsafe fn init() { - // First, define the three memory types that we will map. Cacheable and +/// Setup function for the MAIR_EL1 register. +fn set_up_mair() { + // Define the three memory types that we will map. Cacheable and // non-cacheable normal DRAM, and device. MAIR_EL1.write( // Attribute 2 MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable - // Attribute 1 + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc @@ -116,101 +225,97 @@ pub unsafe fn init() { + MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, ); +} + +trait BaseAddr { + fn base_addr_u64(&self) -> u64; + fn base_addr_usize(&self) -> usize; +} - // Descriptive consts for indexing into the correct MAIR_EL1 attributes. - #[allow(dead_code)] - mod mair { - pub const DEVICE: u64 = 0; - pub const NORMAL: u64 = 1; - pub const NORMAL_NON_CACHEABLE: u64 = 2; +impl BaseAddr for [u64; 512] { + fn base_addr_u64(&self) -> u64 { + self as *const u64 as u64 } - // The first 2 MiB. - // - // Set up the first LVL2 entry, pointing to the base address of a follow-up - // table containing 4 KiB pages. - // - // 0x0000_0000_0000_0000 | - // |> 2 MiB - // 0x0000_0000_001F_FFFF | - 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; + fn base_addr_usize(&self) -> usize { + self as *const u64 as usize + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// A wrapper struct is needed here so that the align attribute can be used. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +/// The LVL2 page table containng the 2 MiB entries. +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// The LVL3 page table containing the 4 KiB entries. +/// +/// The first entry of the LVL2_TABLE will forward to this table. +static mut LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 GiB of address space. +/// +/// The first 2 MiB are 4 KiB granule, the rest 2 MiB. +pub unsafe fn init() -> Result<(), &'static str> { + // Prepare the memory attribute indirection register. + set_up_mair(); + + // Point the first 2 MiB of virtual addresses to the follow-up LVL3 + // page-table. + LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) { + Err(s) => return Err(s), + Ok(d) => d.value(), + }; // Fill the rest of the LVL2 (2 MiB) entries as block descriptors. // - // Differentiate between - // - non-cacheable DRAM - // - cacheable DRAM - // - device memory - // - // Ranges are stored in memory.rs. - // - // 0x0000_0000_0020_0000 | - // |> 4 MiB non-cacheable DRAM - // 0x0000_0000_005F_FFFF | - // 0x0000_0000_0060_0000 | - // |> 1002 MiB cacheable DRAM - // 0x0000_0000_3EFF_FFFF | - // 0x0000_0000_3F00_0000 | - // |> 16 MiB device (MMIO) - // 0x0000_0000_4000_0000 | - let dma_first_block_index: u64 = (crate::memory::map::DMA_HEAP_START >> 21).into(); - let dma_last_block_index: u64 = (crate::memory::map::DMA_HEAP_END >> 21).into(); - let mmio_first_block_index: u64 = (crate::memory::map::MMIO_BASE >> 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(1) which makes the iteration start at the second 2 MiB // block (0x20_0000). - for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { - let j: u64 = i as u64; + for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { + let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT; - let mem_attr = if (dma_first_block_index..=dma_last_block_index).contains(&j) { - STAGE1_DESCRIPTOR::SH::InnerShareable - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) - } else if j >= mmio_first_block_index { - STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) - } else { - STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), }; - *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. - let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end(); + let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; - let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; - let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; + *entry = block_desc.value(); + } - let common = STAGE1_DESCRIPTOR::VALID::True - + STAGE1_DESCRIPTOR::TYPE::Table - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) - + STAGE1_DESCRIPTOR::SH::InnerShareable - + STAGE1_DESCRIPTOR::AF::True; + // 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 << FOUR_KIB_SHIFT; - for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { - let j: u64 = i as u64; + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), + }; - let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { - STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False - } else { - STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True + let page_desc = match PageDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, }; - *entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; + *entry = page_desc.value(); } // Point to the LVL2 table base address in TTBR0. - TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr()); + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Configure various settings of stage 1 of the EL1 translation regime. let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange); @@ -235,4 +340,6 @@ pub unsafe fn init() { // Force MMU init to complete before next instruction barrier::isb(barrier::SY); + + Ok(()) } diff --git a/10_exceptions_groundwork/README.md b/10_exceptions_groundwork/README.md index 7edd68db..4f250146 100644 --- a/10_exceptions_groundwork/README.md +++ b/10_exceptions_groundwork/README.md @@ -258,29 +258,35 @@ unsafe { core::ptr::read_volatile(big_addr as *mut u64) }; ``` Finally, this triggers our exception code, because we try to read from a virtual address for which no address translations have been installed. Remember, we only installed identity-mapped page tables for the first 1 GiB of address space in lesson `0C`. -After the exception handler is finished, it returns to the first instruction after the memory read that caused the exception: +After the exception handler is finished, it returns to the first instruction +after the memory read that caused the exception. + +## Output ```console ferris@box:~$ make raspboot + [0] MiniUart online. [1] Press a key to continue booting... Greetings fellow Rustacean! -[2] Switching MMU on now... MMU online. -[i] Memory layout: - 0x00000000 - 0x0007FFFF | 512 KiB | Kernel stack - 0x00080000 - 0x00084FFF | 20 KiB | Kernel code and RO data - 0x00085000 - 0x00088007 | 12 KiB | Kernel data and BSS - 0x00200000 - 0x005FFFFF | 4 MiB | DMA heap pool - 0x3F000000 - 0x3FFFFFFF | 16 MiB | Device MMIO +[2] MMU online. +[i] Kernel memory layout: + 0x00000000 - 0x0007FFFF | 512 KiB | C RW PXN | Kernel stack + 0x00080000 - 0x00084FFF | 20 KiB | C RO PX | Kernel code and RO data + 0x00085000 - 0x0008800F | 12 KiB | C RW PXN | Kernel data and BSS + 0x00200000 - 0x005FFFFF | 4 MiB | NC RW PXN | DMA heap pool + 0x3F000000 - 0x3FFFFFFF | 16 MiB | Dev RW PXN | Device MMIO [i] Global DMA Allocator: Allocated Addr 0x00200000 Size 0x90 [3] Videocore Mailbox set up (DMA mem heap allocation successful). [4] PL011 UART online. Output switched to it. [5] Exception vectors are set up. [!] A synchronous exception happened. - ELR_EL1: 0x000809C0 + ELR_EL1: 0x00080C20 Incrementing ELR_EL1 by 4 now to continue with the first instruction after the exception! - ELR_EL1 modified: 0x000809C4 + ELR_EL1 modified: 0x00080C24 Returning from exception... [i] Whoa! We recovered from an exception. + +$> ``` diff --git a/10_exceptions_groundwork/kernel8 b/10_exceptions_groundwork/kernel8 index cb515e3ecfb0bbd85fe18e3548c951e4d8091c14..8210cc7c821c568175ae13464ad9670ca7f21921 100755 GIT binary patch literal 102544 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 Rqvd}|=6{YVSn#pu|GxyF79aos literal 101280 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 oEW_Vu+ni$l^Qw3e{LPJ^8@CyCv&ASy{MW`Fm+ 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 pA1+AQq=NTptVSt zCYdv1qU%@|ImskDC!pEY>ckl{#+me--2~&#S!Xh5qcQQHoU_|avb)`&D+CeRiS+(n zRW&vy%uG_}G_T%$_uYHn{d)HneSJ({A9GL;A4(^^0uermKhK>v5i&BiCsT=V#2p{z z6{pWG#kQm=&4e7B*p#l=F^)H9C{ahM!UOjymPm2YsPO%E$sWp*LT89HRzPBd&ofe_ zj>IM~|92zlZ8eihia&M_iCG9a!PhvX-r9wdl@Rg7slgH{(#)v@`L`Vs6~bv>09c9! z>vu?V0i(<#4;mm`k^ADHLuE(>i5Q7Hw1t%@(`yc+GU4Er0GJMBkxD0+QKn#i(8&;q z$IodX=Uoi{VUvMKk)t9}A`T)o-DtXy59E3H4IN<>Dg;g-EteNb^Bkp;(^o7}0b)lu zW#R}nLWs$u1|z<$SrW)f(4LO`nDaA&5wAG_7i%rdE1_gsLgw1jB1!FaZY5lmVB- z;69x}|KghrIyDBWRuZP{aYzYM>Gbb+-%5NHh!ciXj_ui~j5lvkCN`~CVq2P&>+3x+ zIH@$iZN5lg9-qPQC4F~DsYG*NRv(ucNU!`xs@>I@DcSqV!4L;5Hc($W$4eEOe}(Uq zA{DA%jPSlYBNq742=$)>q1U~xf7o>>67qk1 zfbZ+dIy~>w`wX66yC`iw^koxpN`X7f*#aX7qWc&U%g9eTpJ^iJuW)IB|G@e=hMa;w zTe@I%F>D;|FW>(vYa*hwIgN~Zm-M{a+TL-&%MuW<1v=moZkELEWym|{nd}d3S@#Ve zW=P9uX3m?f7_VgJ?7IR#N@vseU5dx^;!M_XA0aJ>7HO2r4qV}K0{;UzggTK4b5jAR zaF;lX`#9b|f)M{V;`biEv3|paD>pV=5Si@ZR%zq)PB31QnZEC1``%Zp+3aEA(8lYM z!E^pTHa9?FUN$4J-{g@xOhghHel)j`;`Qm8Tcr&kz?J+4s@ZyPV5*#+#+$P2ogpM(i;0wg>wbhWI3AnaLNHDFD z1pjCUY$FL$czQixZ6w&l`n`*>Z@tx0@kcX#C&6k7a03xLZ36e{>3FL^M zg^bu|CK6%9>Wjx%PjD|omV%f{q=6zCJV6tF&fPY%$v}cBAd-OF%5f@q&v-hC(To=t zkyNmUrsL=Xp4_I>Zs{Uhp=Q2gi-dB>!8J$C7LuDj5@RuA^?4RkO)H_w_ za;SG!UksfjQkeQd1(D`5s%opgcH;i!>WLw&<~Io;@8Oj9Af%8=mcEJXLIoWwn;A7? z_-_asDpbOOj(vw{q1QCkuCsrI6#q<%mUtPxF>Rtdib^C|y~n;soYd>1aZdReRq>U0 z3yFi9n@4bTPEPI%a=3Cm=9#1d09S5BQ3jO z8eFdyic|8SCpaH_MR|K>9&@++2Xiqa$fM>tR)>MKyv|yO{TN@I>$$7RO3W@fe~zna zdNYsB`Plpb7*}(oqZR3SZXF4BCoxFiY?RBh=G^I--Aa8Lo4| zpW`j2Tua3yXL8vVlTHSBgK}q9p22=C-tw7ze$FEEG~2B>uZ1Dz68ZO8RkwiiQ=8|? znb~2oY^%NN@qxH}hn)E`zjSUks5M zWGO7;CajSh^tO|}E|W=L%ORtjjvqA^xlNfm+(N|B&(k$RQSLUkDOX>ZM@G*~!*NMk zb$B5eJ)*-+LXTH5f=RxE#QF8FyS~I7K8adtn87 zHz{*174&X8iyQR?!008(Bkv?GR0=EkxhI%rFuN&^>*q7qDIM6$9fHklSlf!Z4HqjN zO}CR^JLC7N?@kho8VlU&yLcxFI$QMxsDErv4a&a{j!_3jt?5pls}bv8BZX1P4C41T z+=*s$6&|8+;(jX`=BcK*?xV@DKEO}`9M)l?pr`IIs_kFsKnm+eEkas}^hy$p3?O-; zY><;-0waafiTgk@%numU{eljo{zZK&DJl~|xxlq|z7~}`k%wqhh7wV!!`tO@^Q=)> zphab){I>aTj7o7jJa}xTQ7M2ijY=PlO751Z)N%5XaPojd1O8jZD6Wek_yup0pU%qq z#$d&9Ex9SpU&P?zA911?;8U2%h=WL>Vzsq8=#&;o5nQDaj**ZR1JMhdLg`1*|CL~* z`J6|M{5_|S)!JJJm>f;f`ZC&+`8bU+8bR8HMj=R~8U&A~Dzy;Ak7{My=!wDgT!Wki znZp|BO_g1F4g>lY{ww+8{JMsIqcnvYZsy^L*7=5UxX*Do4s=CUflYc_30bxoX*do& z5_BDjF6h7IN9HdQM6xz^Odm=Cj735Rw8%QCQ$O$njjI%#Oq72|OZYmh1 z!{_1viyqHxcQP-ZK0|Hpjd=)dj71HZxfsT zRhgUP367)uLMi7O+rz4*+oy2S8)!If&)nZJpvNhNP3o{7gwPL=?joGkwKt1YoLxYI zKQw!$L+&ha^L7_CUjlv{u}(woEKQG@#4PgL3(eJxr6Y`f%Enkb`Y}G5s@*nc(DYH= zM2!qmVl7f+4Rph}X+@|)S_dj1H(_3wKe6eInnK|#BgCJjrpEt7;-~2{ zIUCV;y)TjXaCgiLRwLn~3T~-J2js)386#VMpS#1ME;5ix18Rk)_MR&_b3HMVD`%xN z8dN;_;go9qtuLkg5&Ds&j8%pz7{vk;O%H9kvnzB zA^*xyH0N-xG;ugbns+1{t_D?uq#QC>?nq2pQ%Fmcn;W1FV+~}Ea`OTuR!=O1xTobd z#gm29hZ_7X*J+f&FP;xA6Ve?wTcRx948Ay8#~8^b7g{!{n-#n9dKNXM9ey452zp zz&N<-B?!~LnwYnNc@w_8IG=s%wfzv{GK6s6XuGiZct$}6K0S`?SIkn!YR{qlyF0o; zAn;bZt6RLCj7C7Xk$#lJhPxTlz9&wT+!qO1&VyMa2+}6K341SL{l{2u!hWpprh=eO z9;s(cAJ!hztU*5`!6Jc=p!sBHk8|>&!Ux%aSspI@39Fdp#lq5*kq*4PdD{Z^m|6bl zwny2pRjw$iV(q1JM^S+xPihM1$@_{HWreL$B>T2WRSw|1U$l0Cy;QOf6sect^c{sx z2qKU0HARxMzd$awJ^dH>{Z!bUJs;`*8if7r0<=Ti@{`&2=UMwg>6dzQfh2N{`0dgw z$#b&s+})Szl1RQI8(SSJ0EKkd^;W!y!Qx?`F8oz$hQ^rT^c}mY%}SL`spcAl|gND zv!=Ok%V6FLjX^cB-B6+KzhZ}4ue-%!3nNLx~?ZOYcytL2*7GC5T5XZvd9N7pVg=hSHk_)nJl zRM*O{uB~Dl>g12tmWqCTRnS)tV=Th&s#V*i{UszZKAfk$P3r6O^!X?HJaPKOxHMbC z_v_=rTy4Hh8%0v0FN9_x@Nk;8;g~*d$k67!`q-eu|4kP}KgV>=I-$eg(Z_f7aX=l7 zgs2;3>$(dCUleMjZzT~a-X`Fnki|F5V| z_gdNA^h@(u9lTfvkFArxY&tOiV1u@MhYl`>>Pql_u;JvZ>$j%hF7&f<$dft`*;z&L K>k*#F?*9)j4dt!? diff --git a/10_exceptions_groundwork/src/devices/hw/gpio.rs b/10_exceptions_groundwork/src/devices/hw/gpio.rs index 0d06dfa3..7affea08 100644 --- a/10_exceptions_groundwork/src/devices/hw/gpio.rs +++ b/10_exceptions_groundwork/src/devices/hw/gpio.rs @@ -97,7 +97,7 @@ pub struct RegisterBlock { /// Public interface to the GPIO MMIO area pub struct GPIO { - base_addr: u32, + base_addr: usize, } impl ops::Deref for GPIO { @@ -109,7 +109,7 @@ impl ops::Deref for GPIO { } impl GPIO { - pub fn new(base_addr: u32) -> GPIO { + pub fn new(base_addr: usize) -> GPIO { GPIO { base_addr } } diff --git a/10_exceptions_groundwork/src/devices/hw/mini_uart.rs b/10_exceptions_groundwork/src/devices/hw/mini_uart.rs index 4f814268..a03ed8e4 100644 --- a/10_exceptions_groundwork/src/devices/hw/mini_uart.rs +++ b/10_exceptions_groundwork/src/devices/hw/mini_uart.rs @@ -123,7 +123,7 @@ pub struct RegisterBlock { } pub struct MiniUart { - base_addr: u32, + base_addr: usize, } /// Deref to RegisterBlock @@ -145,7 +145,7 @@ impl ops::Deref for MiniUart { } impl MiniUart { - pub fn new(base_addr: u32) -> MiniUart { + pub fn new(base_addr: usize) -> MiniUart { MiniUart { base_addr } } diff --git a/10_exceptions_groundwork/src/devices/hw/pl011_uart.rs b/10_exceptions_groundwork/src/devices/hw/pl011_uart.rs index f98534e4..44580d35 100644 --- a/10_exceptions_groundwork/src/devices/hw/pl011_uart.rs +++ b/10_exceptions_groundwork/src/devices/hw/pl011_uart.rs @@ -141,7 +141,7 @@ pub enum PL011UartError { pub type Result = ::core::result::Result; pub struct PL011Uart { - base_addr: u32, + base_addr: usize, } impl ops::Deref for PL011Uart { @@ -153,7 +153,7 @@ impl ops::Deref for PL011Uart { } impl PL011Uart { - pub fn new(base_addr: u32) -> PL011Uart { + pub fn new(base_addr: usize) -> PL011Uart { PL011Uart { base_addr } } diff --git a/10_exceptions_groundwork/src/devices/hw/videocore_mbox.rs b/10_exceptions_groundwork/src/devices/hw/videocore_mbox.rs index fad36461..729777aa 100644 --- a/10_exceptions_groundwork/src/devices/hw/videocore_mbox.rs +++ b/10_exceptions_groundwork/src/devices/hw/videocore_mbox.rs @@ -87,7 +87,7 @@ const MBOX_SIZE: usize = 36; // Public interface to the mailbox pub struct VideocoreMbox<'a> { pub buffer: &'a mut [u32], - base_addr: u32, + base_addr: usize, } /// Deref to RegisterBlock @@ -109,7 +109,7 @@ impl<'a> ops::Deref for VideocoreMbox<'a> { } impl<'a> VideocoreMbox<'a> { - pub fn new(base_addr: u32) -> ::core::result::Result, ()> { + pub fn new(base_addr: usize) -> ::core::result::Result, ()> { let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT)); if ret.is_err() { diff --git a/10_exceptions_groundwork/src/main.rs b/10_exceptions_groundwork/src/main.rs index efcdd8ee..aa37ad13 100644 --- a/10_exceptions_groundwork/src/main.rs +++ b/10_exceptions_groundwork/src/main.rs @@ -47,8 +47,8 @@ static CONSOLE: sync::NullLock = /// non-cacheable in the page tables. static DMA_ALLOCATOR: sync::NullLock = sync::NullLock::new(memory::BumpAllocator::new( - memory::map::DMA_HEAP_START as usize, - memory::map::DMA_HEAP_END as usize, + memory::map::virt::DMA_HEAP_START as usize, + memory::map::virt::DMA_HEAP_END as usize, "Global DMA Allocator", )); @@ -63,12 +63,12 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Instantiate GPIO device //------------------------------------------------------------ - let gpio = hw::GPIO::new(memory::map::GPIO_BASE); + let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE); //------------------------------------------------------------ // Instantiate MiniUart //------------------------------------------------------------ - let mini_uart = hw::MiniUart::new(memory::map::MINI_UART_BASE); + let mini_uart = hw::MiniUart::new(memory::map::physical::MINI_UART_BASE); mini_uart.init(&gpio); CONSOLE.lock(|c| { @@ -87,24 +87,26 @@ fn kernel_entry() -> ! { }); println!("Greetings fellow Rustacean!"); - //------------------------------------------------------------ - // Bring up memory subsystem - //------------------------------------------------------------ - print!("[2] Switching MMU on now... "); - unsafe { memory::mmu::init() }; - println!("MMU online."); - - memory::print_layout(); - // We are now in a state where every next step can fail, but we can handle // the error with feedback for the user and fall through to our UART // loopback. 'init: { + //------------------------------------------------------------ + // Bring up memory subsystem + //------------------------------------------------------------ + if unsafe { memory::mmu::init() }.is_err() { + println!("[2][Error] Could not set up MMU. Aborting."); + break 'init; + }; + println!("[2] MMU online."); + + memory::print_layout(); + //------------------------------------------------------------ // Instantiate Videocore Mailbox //------------------------------------------------------------ let mut v_mbox; - match hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE) { + match hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE) { Ok(i) => { println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful)."); v_mbox = i; @@ -119,7 +121,7 @@ fn kernel_entry() -> ! { //------------------------------------------------------------ // Instantiate PL011 UART and replace MiniUart with it in CONSOLE //------------------------------------------------------------ - let pl011_uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); + let pl011_uart = hw::PL011Uart::new(memory::map::physical::PL011_UART_BASE); // uart.init() will reconfigure the GPIO, which causes a race against // the MiniUart that is still putting out characters on the physical diff --git a/10_exceptions_groundwork/src/memory.rs b/10_exceptions_groundwork/src/memory.rs index 42535974..f1da0c87 100644 --- a/10_exceptions_groundwork/src/memory.rs +++ b/10_exceptions_groundwork/src/memory.rs @@ -23,111 +23,270 @@ */ use crate::println; +use core::fmt; +use core::ops::RangeInclusive; mod bump_allocator; pub use bump_allocator::BumpAllocator; pub mod mmu; -/// The system memory map. +/// System memory map. #[rustfmt::skip] pub mod map { - pub const KERN_STACK_BOT: u32 = 0x0000_0000; - pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x3FFF_FFFF; - /// The second 2 MiB block. - pub const DMA_HEAP_START: u32 = 0x0020_0000; - pub const DMA_HEAP_END: u32 = 0x005F_FFFF; + pub mod physical { + pub const MMIO_BASE: usize = 0x3F00_0000; + pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880; + pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000; + pub const PL011_UART_BASE: usize = MMIO_BASE + 0x0020_1000; + pub const MINI_UART_BASE: usize = MMIO_BASE + 0x0021_5000; + pub const MMIO_END: usize = super::END; + } - pub const MMIO_BASE: u32 = 0x3F00_0000; - pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; - pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; - pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000; - pub const MINI_UART_BASE: u32 = MMIO_BASE + 0x0021_5000; + pub mod virt { + pub const KERN_STACK_START: usize = super::START; + pub const KERN_STACK_END: usize = 0x0007_FFFF; - pub const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; + // The second 2 MiB block. + pub const DMA_HEAP_START: usize = 0x0020_0000; + pub const DMA_HEAP_END: usize = 0x005F_FFFF; + } } -const PAGESIZE: u64 = 4096; +/// Types used for compiling the virtual memory layout of the kernel using +/// address ranges. +pub mod kernel_mem_range { + use core::ops::RangeInclusive; -#[inline] -fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { - (addr + (alignment - 1)) & !(alignment - 1) + #[derive(Copy, Clone)] + pub enum MemAttributes { + CacheableDRAM, + NonCacheableDRAM, + Device, + } + + #[derive(Copy, Clone)] + pub enum AccessPermissions { + ReadOnly, + ReadWrite, + } + + #[allow(dead_code)] + #[derive(Copy, Clone)] + pub enum Translation { + Identity, + Offset(usize), + } + + #[derive(Copy, Clone)] + pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, + } + + impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } + } + + pub struct Descriptor { + pub name: &'static str, + pub virtual_range: fn() -> RangeInclusive, + pub translation: Translation, + pub attribute_fields: AttributeFields, + } } -fn get_ro_start_end() -> (u64, u64) { - // 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 __ro_start: u64; - - // The non-inclusive end of the read-only area, aka the address of the - // first byte _after_ the RO area. - static __ro_end: u64; +use kernel_mem_range::*; + +/// A virtual memory layout that is agnostic of the paging granularity that the +/// hardware MMU will use. +/// +/// Contains only special ranges, aka anything that is _not_ normal cacheable +/// DRAM. +static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [ + Descriptor { + name: "Kernel stack", + virtual_range: || { + RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END) + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Kernel code and RO data", + virtual_range: || { + // Using the linker script, we ensure that the RO area is consecutive and 4 + // KiB aligned, and we export the boundaries via symbols: + // + // [__ro_start, __ro_end) + extern "C" { + // The inclusive start of the read-only area, aka the address of the + // first byte of the area. + static __ro_start: u64; + + // The exclusive end of the read-only area, aka the address of + // the first byte _after_ the RO area. + static __ro_end: u64; + } + + unsafe { + // Notice the subtraction to turn the exclusive end into an + // inclusive end + RangeInclusive::new( + &__ro_start as *const _ as usize, + &__ro_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadOnly, + execute_never: false, + }, + }, + Descriptor { + name: "Kernel data and BSS", + virtual_range: || { + extern "C" { + static __ro_end: u64; + static __bss_end: u64; + } + + unsafe { + RangeInclusive::new( + &__ro_end as *const _ as usize, + &__bss_end as *const _ as usize - 1, + ) + } + }, + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "DMA heap pool", + virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::NonCacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + Descriptor { + name: "Device MMIO", + virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END), + translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, +]; + +/// For a given virtual address, find and return the output address and +/// according attributes. +/// +/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal +/// cacheable DRAM. +fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > map::END { + return Err("Address out of range."); } - unsafe { - // Notice the subtraction to calculate the last page index of the RO - // area and not the first page index after the RO area. - ( - &__ro_start as *const _ as u64, - &__ro_end as *const _ as u64 - 1, - ) + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + if (i.virtual_range)().contains(&virt_addr) { + let output_addr = match i.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())) } -pub fn print_layout() { - use crate::memory::map::*; +/// Human-readable output of a Descriptor. +impl fmt::Display for Descriptor { + 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; - // log2(1024) - const KIB_RSHIFT: u32 = 10; + 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") + }; - // log2(1024 * 1024) - const MIB_RSHIFT: u32 = 20; + let attr = match self.attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => "C", + MemAttributes::NonCacheableDRAM => "NC", + MemAttributes::Device => "Dev", + }; - println!("[i] Memory layout:"); + let acc_p = match self.attribute_fields.acc_perms { + AccessPermissions::ReadOnly => "RO", + AccessPermissions::ReadWrite => "RW", + }; - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", - KERN_STACK_BOT, - KERN_STACK_TOP, - (KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT - ); + let xn = if self.attribute_fields.execute_never { + "PXN" + } else { + "PX" + }; - let (ro_start, ro_end) = get_ro_start_end(); - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", - ro_start, - ro_end, - (ro_end - ro_start + 1) >> KIB_RSHIFT - ); + write!( + f, + " {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}", + start, end, size, unit, attr, acc_p, xn, self.name + ) + } +} + +/// Print the kernel memory layout. +pub fn print_layout() { + println!("[i] Kernel memory layout:"); - extern "C" { - static __bss_end: u64; + for i in KERNEL_VIRTUAL_LAYOUT.iter() { + println!("{}", i); } +} - let start = ro_end + 1; - let end = unsafe { &__bss_end as *const _ as u64 } - 1; - println!( - " {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", - start, - end, - (end - start + 1) >> KIB_RSHIFT - ); - - println!( - " {:#010X} - {:#010X} | {: >4} MiB | DMA heap pool", - DMA_HEAP_START, - DMA_HEAP_END, - (DMA_HEAP_END - DMA_HEAP_START + 1) >> MIB_RSHIFT - ); - - println!( - " {:#010X} - {:#010X} | {: >4} MiB | Device MMIO", - MMIO_BASE, - PHYS_ADDR_MAX, - (PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT - ); +/// Calculate the next possible aligned address without sanity checking the +/// input parameters. +#[inline] +fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { + (addr + (alignment - 1)) & !(alignment - 1) } diff --git a/10_exceptions_groundwork/src/memory/mmu.rs b/10_exceptions_groundwork/src/memory/mmu.rs index 1ead372f..72405e74 100644 --- a/10_exceptions_groundwork/src/memory/mmu.rs +++ b/10_exceptions_groundwork/src/memory/mmu.rs @@ -22,14 +22,15 @@ * SOFTWARE. */ +use crate::memory::{get_virt_addr_properties, AttributeFields}; use cortex_a::{barrier, regs::*}; use register::register_bitfields; register_bitfields! {u64, // AArch64 Reference Manual page 2150 STAGE1_DESCRIPTOR [ - /// Execute-never - XN OFFSET(54) NUMBITS(1) [ + /// Privileged execute-never + PXN OFFSET(53) NUMBITS(1) [ False = 0, True = 1 ], @@ -73,42 +74,150 @@ register_bitfields! {u64, ] } -trait BaseAddr { - fn base_addr(&self) -> u64; +const FOUR_KIB: usize = 4 * 1024; +const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024) + +const TWO_MIB: usize = 2 * 1024 * 1024; +const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024) + +/// A descriptor pointing to the next page table. +struct TableDescriptor(register::FieldValue); + +impl TableDescriptor { + fn new(next_lvl_table_addr: usize) -> Result { + if next_lvl_table_addr % FOUR_KIB != 0 { + return Err("TableDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT; + + Ok(TableDescriptor( + STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64), + )) + } + + fn value(&self) -> u64 { + self.0.value + } } -impl BaseAddr for [u64; 512] { - fn base_addr(&self) -> u64 { - self as *const u64 as u64 +/// A function that maps the generic memory range attributes to HW-specific +/// attributes of the MMU. +fn into_mmu_attributes( + attribute_fields: AttributeFields, +) -> register::FieldValue { + use crate::memory::{AccessPermissions, MemAttributes}; + + // Memory attributes + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::NonCacheableDRAM => { + STAGE1_DESCRIPTOR::SH::InnerShareable + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) + } + MemAttributes::Device => { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // 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::True + } else { + STAGE1_DESCRIPTOR::PXN::False + }; + + desc +} + +/// A Level2 block descriptor with 2 MiB aperture. +/// +/// The output points to physical memory. +struct Lvl2BlockDescriptor(register::FieldValue); + +impl Lvl2BlockDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % TWO_MIB != 0 { + return Err("BlockDescriptor: Address is not 2 MiB aligned."); + } + + let shifted = output_addr >> TWO_MIB_SHIFT; + + Ok(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), + )) + } + + fn value(&self) -> u64 { + self.0.value } } -const NUM_ENTRIES_4KIB: usize = 512; +/// A page descriptor with 4 KiB aperture. +/// +/// The output points to physical memory. +struct PageDescriptor(register::FieldValue); + +impl PageDescriptor { + fn new( + output_addr: usize, + attribute_fields: AttributeFields, + ) -> Result { + if output_addr % FOUR_KIB != 0 { + return Err("PageDescriptor: Address is not 4 KiB aligned."); + } + + let shifted = output_addr >> FOUR_KIB_SHIFT; + + Ok(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), + )) + } -// 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], + fn value(&self) -> u64 { + self.0.value + } } -static mut LVL2_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; -static mut SINGLE_LVL3_TABLE: PageTable = PageTable { - entries: [0; NUM_ENTRIES_4KIB], -}; +/// Constants for indexing the MAIR_EL1. +#[allow(dead_code)] +mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; + pub const NORMAL_NON_CACHEABLE: u64 = 2; +} -/// Set up identity mapped page tables for the first 1 GiB of address space. -pub unsafe fn init() { - // First, define the three memory types that we will map. Cacheable and +/// Setup function for the MAIR_EL1 register. +fn set_up_mair() { + // Define the three memory types that we will map. Cacheable and // non-cacheable normal DRAM, and device. MAIR_EL1.write( // Attribute 2 MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable - // Attribute 1 + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc @@ -116,101 +225,97 @@ pub unsafe fn init() { + MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, ); +} + +trait BaseAddr { + fn base_addr_u64(&self) -> u64; + fn base_addr_usize(&self) -> usize; +} - // Descriptive consts for indexing into the correct MAIR_EL1 attributes. - #[allow(dead_code)] - mod mair { - pub const DEVICE: u64 = 0; - pub const NORMAL: u64 = 1; - pub const NORMAL_NON_CACHEABLE: u64 = 2; +impl BaseAddr for [u64; 512] { + fn base_addr_u64(&self) -> u64 { + self as *const u64 as u64 } - // The first 2 MiB. - // - // Set up the first LVL2 entry, pointing to the base address of a follow-up - // table containing 4 KiB pages. - // - // 0x0000_0000_0000_0000 | - // |> 2 MiB - // 0x0000_0000_001F_FFFF | - 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; + fn base_addr_usize(&self) -> usize { + self as *const u64 as usize + } +} + +const NUM_ENTRIES_4KIB: usize = 512; + +// A wrapper struct is needed here so that the align attribute can be used. +#[repr(C)] +#[repr(align(4096))] +struct PageTable { + entries: [u64; NUM_ENTRIES_4KIB], +} + +/// The LVL2 page table containng the 2 MiB entries. +static mut LVL2_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// The LVL3 page table containing the 4 KiB entries. +/// +/// The first entry of the LVL2_TABLE will forward to this table. +static mut LVL3_TABLE: PageTable = PageTable { + entries: [0; NUM_ENTRIES_4KIB], +}; + +/// Set up identity mapped page tables for the first 1 GiB of address space. +/// +/// The first 2 MiB are 4 KiB granule, the rest 2 MiB. +pub unsafe fn init() -> Result<(), &'static str> { + // Prepare the memory attribute indirection register. + set_up_mair(); + + // Point the first 2 MiB of virtual addresses to the follow-up LVL3 + // page-table. + LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) { + Err(s) => return Err(s), + Ok(d) => d.value(), + }; // Fill the rest of the LVL2 (2 MiB) entries as block descriptors. // - // Differentiate between - // - non-cacheable DRAM - // - cacheable DRAM - // - device memory - // - // Ranges are stored in memory.rs. - // - // 0x0000_0000_0020_0000 | - // |> 4 MiB non-cacheable DRAM - // 0x0000_0000_005F_FFFF | - // 0x0000_0000_0060_0000 | - // |> 1002 MiB cacheable DRAM - // 0x0000_0000_3EFF_FFFF | - // 0x0000_0000_3F00_0000 | - // |> 16 MiB device (MMIO) - // 0x0000_0000_4000_0000 | - let dma_first_block_index: u64 = (crate::memory::map::DMA_HEAP_START >> 21).into(); - let dma_last_block_index: u64 = (crate::memory::map::DMA_HEAP_END >> 21).into(); - let mmio_first_block_index: u64 = (crate::memory::map::MMIO_BASE >> 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(1) which makes the iteration start at the second 2 MiB // block (0x20_0000). - for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { - let j: u64 = i as u64; + for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { + let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT; - let mem_attr = if (dma_first_block_index..=dma_last_block_index).contains(&j) { - STAGE1_DESCRIPTOR::SH::InnerShareable - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) - } else if j >= mmio_first_block_index { - STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) - } else { - STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), }; - *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. - let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end(); + let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, + }; - let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; - let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; + *entry = block_desc.value(); + } - let common = STAGE1_DESCRIPTOR::VALID::True - + STAGE1_DESCRIPTOR::TYPE::Table - + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) - + STAGE1_DESCRIPTOR::SH::InnerShareable - + STAGE1_DESCRIPTOR::AF::True; + // 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 << FOUR_KIB_SHIFT; - for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { - let j: u64 = i as u64; + let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) { + Err(s) => return Err(s), + Ok((a, b)) => (a, b), + }; - let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { - STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False - } else { - STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True + let page_desc = match PageDescriptor::new(output_addr, attribute_fields) { + Err(s) => return Err(s), + Ok(desc) => desc, }; - *entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; + *entry = page_desc.value(); } // Point to the LVL2 table base address in TTBR0. - TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr()); + TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64()); // Configure various settings of stage 1 of the EL1 translation regime. let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange); @@ -235,4 +340,6 @@ pub unsafe fn init() { // Force MMU init to complete before next instruction barrier::isb(barrier::SY); + + Ok(()) }