diff --git a/0C_virtual_memory/.cargo/config b/0C_virtual_memory/.cargo/config new file mode 100644 index 00000000..d7fb2ba3 --- /dev/null +++ b/0C_virtual_memory/.cargo/config @@ -0,0 +1,6 @@ +[target.aarch64-unknown-none] +rustflags = [ + "-C", "link-arg=-Tlink.ld", + "-C", "target-feature=-fp-armv8", + "-C", "target-cpu=cortex-a53", +] diff --git a/0C_virtual_memory/Cargo.lock b/0C_virtual_memory/Cargo.lock new file mode 100644 index 00000000..5f120aff --- /dev/null +++ b/0C_virtual_memory/Cargo.lock @@ -0,0 +1,55 @@ +[[package]] +name = "cortex-a" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel8" +version = "0.1.0" +dependencies = [ + "cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "raspi3_boot 0.1.0", + "register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "panic-abort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "r0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "raspi3_boot" +version = "0.1.0" +dependencies = [ + "cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "panic-abort 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "register" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tock-registers 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tock-registers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e24491e2ca61ecf806cd212431910cd621277240b22a41a1a551c647cb9ca2c" +"checksum panic-abort 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6bc796c620f27056d4ffe7c558533fd67ae5af0fd8e919fbe38de803368af73e" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "157a11ac0b1882ff4a527a92f911dd288df17367faaaa0c36f188cd61ec36fc1" +"checksum tock-registers 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a385d94f3f62e60445a0adb9ff8d9621faa272234530d4c0f848ec98f88e316" diff --git a/0C_virtual_memory/Cargo.toml b/0C_virtual_memory/Cargo.toml new file mode 100644 index 00000000..a99c8d75 --- /dev/null +++ b/0C_virtual_memory/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "kernel8" +version = "0.1.0" +authors = ["Andre Richter "] + +[dependencies] +raspi3_boot = { path = "raspi3_boot" } +cortex-a = "2.2.0" +register = "0.2.0" + +[package.metadata.cargo-xbuild] +sysroot_path = "../xbuild_sysroot" diff --git a/0C_virtual_memory/Makefile b/0C_virtual_memory/Makefile new file mode 100644 index 00000000..26e15973 --- /dev/null +++ b/0C_virtual_memory/Makefile @@ -0,0 +1,66 @@ +# +# MIT License +# +# Copyright (c) 2018 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. +# + +TARGET = aarch64-unknown-none + +OBJCOPY = cargo objcopy -- +OBJCOPY_PARAMS = --strip-all -O binary + +UTILS_CONTAINER = andrerichter/raspi3-utils +DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work +DOCKER_TTY = --privileged -v /dev:/dev +QEMU_CMD = qemu-system-aarch64 -M raspi3 -kernel kernel8.img +RASPBOOT_CMD = raspbootcom /dev/ttyUSB0 kernel8.img + +all: clean kernel8.img + +target/$(TARGET)/debug/kernel8: src/main.rs + cargo xbuild --target=$(TARGET) + cp $@ . + +target/$(TARGET)/release/kernel8: src/main.rs + cargo xbuild --target=$(TARGET) --release + cp $@ . + +ifeq ($(DEBUG),1) +kernel8: target/$(TARGET)/debug/kernel8 +else +kernel8: target/$(TARGET)/release/kernel8 +endif + +kernel8.img: kernel8 + $(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img + +qemu: all + $(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio + +raspboot: all + $(DOCKER_CMD) $(DOCKER_TTY) $(UTILS_CONTAINER) $(RASPBOOT_CMD) + +clippy: + cargo xclippy --target=$(TARGET) + +clean: + cargo clean + rm -f kernel8 diff --git a/0C_virtual_memory/README.md b/0C_virtual_memory/README.md new file mode 100644 index 00000000..2c323e2a --- /dev/null +++ b/0C_virtual_memory/README.md @@ -0,0 +1,67 @@ +# Tutorial 0C - Virtual Memory + +**This is a stub** + +TODO: Write rest of tutorial. + +Virtual memory is an immensely complex, but exciting topic. In this first +lesson, we start slow and switch on the MMU using handcrafted page tables for +the first `1 GiB` of memory. That is the amount of `DRAM` the usual Raspberry Pi +3 has. As we already know, the upper `16 MiB` of this gigabyte-window are +occupied by the Raspberry's peripherals such as the UART. + +The page tables we install alternate between `2 MiB` blocks and `4 KiB` blocks. + +The first `2 MiB` of memory are identity mapped, and therefore contain our code +and the stack. We use a single table with a `4 KiB` granule to differentiate +between code, RO-data and RW-data. The linker script was adapted to adhere to +the pagetable sizes. + +Next, we map the UART into the second `2 MiB` block to show the effects of +virtual memory. + +Everyting else is, for reasons of convenience, again identity mapped using `2 +MiB` blocks. + +Hopefully, in a later tutorial, we will write or use (e.g. from the `cortex-a` +crate) proper modules for page table handling, that, among others, cover topics +such as using recursive mapping for maintenace. + +## Zero-cost abstraction + +The MMU init code is a good example to see the great potential of Rust's +zero-cost abstractions[[1]](https://blog.rust-lang.org/2015/05/11/traits.html)[[2]](https://ruudvanasseldonk.com/2016/11/30/zero-cost-abstractions) for embedded programming. + +Take this piece of code for setting up the `MAIR_EL1` register using the +[cortex-a](https://crates.io/crates/cortex-a) crate: + + + +```rust +// First, define the two memory types that we will map. Normal DRAM type 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, +); +``` + +This piece of code is super expressive, and it makes us of `traits`, different +`types` and `constants` to provide type-safe register manipulation. + +In the end, this code sets the first four bytes of the register to certain +values according to the data sheet. Looking at the generated code, we can see +that despite all the type-safety and abstractions, we get super lean code: + +```text +kernel8::mmu::init::h53df3fab6e51e098: + ... + 80768: ed 9f 80 52 mov w13, #0x4ff + ... + 80778: 0d a2 18 d5 msr MAIR_EL1, x13 + ... +``` diff --git a/0C_virtual_memory/kernel8.img b/0C_virtual_memory/kernel8.img new file mode 100755 index 00000000..d8e7e007 Binary files /dev/null and b/0C_virtual_memory/kernel8.img differ diff --git a/0C_virtual_memory/link.ld b/0C_virtual_memory/link.ld new file mode 100644 index 00000000..896153e9 --- /dev/null +++ b/0C_virtual_memory/link.ld @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2018 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. + */ + +ENTRY(_boot_cores); + +SECTIONS +{ + . = 0x80000; /* This is already 4KiB aligned */ + __ro_start = .; + .text : + { + KEEP(*(.text.boot)) *(.text .text.*) + } + + .rodata : + { + *(.rodata .rodata.*) + } + . = ALIGN(4096); /* Fill up to 4KiB */ + __ro_end = .; + + .data : + { + *(.data .data.*) + } + + .bss ALIGN(8): + { + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + } + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} diff --git a/0C_virtual_memory/raspi3_boot/Cargo.toml b/0C_virtual_memory/raspi3_boot/Cargo.toml new file mode 100644 index 00000000..a122e02b --- /dev/null +++ b/0C_virtual_memory/raspi3_boot/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "raspi3_boot" +version = "0.1.0" +authors = ["Andre Richter "] + +[dependencies] +cortex-a = "2.2.0" +panic-abort = "0.2.0" +r0 = "0.2.2" diff --git a/0C_virtual_memory/raspi3_boot/src/lib.rs b/0C_virtual_memory/raspi3_boot/src/lib.rs new file mode 100644 index 00000000..8a081b53 --- /dev/null +++ b/0C_virtual_memory/raspi3_boot/src/lib.rs @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2018 Jorge Aparicio + * Copyright (c) 2018 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. + */ + +#![deny(missing_docs)] +#![deny(warnings)] +#![no_std] + +//! Low-level boot of the Raspberry's processor + +extern crate cortex_a; +extern crate panic_abort; +extern crate r0; + +#[macro_export] +macro_rules! entry { + ($path:path) => { + #[export_name = "main"] + pub unsafe fn __main() -> ! { + // type check the given path + let f: fn() -> ! = $path; + + f() + } + }; +} + +/// Reset function. +/// +/// Initializes the bss section before calling into the user's `main()`. +unsafe fn reset() -> ! { + extern "C" { + // Boundaries of the .bss section, provided by the linker script + static mut __bss_start: u64; + static mut __bss_end: u64; + } + + // Zeroes the .bss section + r0::zero_bss(&mut __bss_start, &mut __bss_end); + + extern "Rust" { + fn main() -> !; + } + + main() +} + +/// Prepare and execute transition from EL2 to EL1. +#[inline] +fn setup_and_enter_el1_from_el2() -> ! { + use cortex_a::{asm, regs::*}; + + // Enable timer counter registers for EL1 + CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET); + + // No offset for reading the counters + CNTVOFF_EL2.set(0); + + // Set EL1 execution state to AArch64 + // TODO: Explain the SWIO bit + HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::SWIO::SET); + + // Set up a simulated exception return. + // + // First, fake a saved program status, where all interrupts were + // masked and SP_EL0 was used as a stack pointer. + SPSR_EL2.write( + SPSR_EL2::D::Masked + + SPSR_EL2::A::Masked + + SPSR_EL2::I::Masked + + SPSR_EL2::F::Masked + + SPSR_EL2::M::EL1t, + ); + + // Second, let the link register point to reset(). + ELR_EL2.set(reset as *const () as u64); + + // Set up SP_EL0 (stack pointer), which will be used by EL1 once + // we "return" to it. + SP_EL0.set(0x80_000); + + // Use `eret` to "return" to EL1. This will result in execution of + // `reset()` in EL1. + asm::eret() +} + +/// Entrypoint of the processor. +/// +/// Parks all cores except core0 and checks if we started in EL2. If +/// so, proceeds with setting up EL1. +#[link_section = ".text.boot"] +#[no_mangle] +pub unsafe extern "C" fn _boot_cores() -> ! { + use cortex_a::{asm, regs::*}; + + const CORE_0: u64 = 0; + const CORE_MASK: u64 = 0x3; + const EL2: u32 = CurrentEL::EL::EL2.value; + + if let CORE_0 = MPIDR_EL1.get() & CORE_MASK { + if let EL2 = CurrentEL.get() { + setup_and_enter_el1_from_el2() + } + } + + // if not core0 or EL != 2, infinitely wait for events + loop { + asm::wfe(); + } +} diff --git a/0C_virtual_memory/src/gpio.rs b/0C_virtual_memory/src/gpio.rs new file mode 100644 index 00000000..3ff0c1e9 --- /dev/null +++ b/0C_virtual_memory/src/gpio.rs @@ -0,0 +1,75 @@ +/* + * MIT License + * + * Copyright (c) 2018 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::MMIO_BASE; +use register::mmio::ReadWrite; + +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// GPIO Function Select 1 + GPFSEL1 [ + /// Pin 15 + FSEL15 OFFSET(15) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + RXD0 = 0b100, // UART0 - Alternate function 0 + RXD1 = 0b010 // Mini UART - Alternate function 5 + + ], + + /// Pin 14 + FSEL14 OFFSET(12) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + TXD0 = 0b100, // UART0 - Alternate function 0 + TXD1 = 0b010 // Mini UART - Alternate function 5 + ] + ], + + /// GPIO Pull-up/down Clock Register 0 + GPPUDCLK0 [ + /// Pin 15 + PUDCLK15 OFFSET(15) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 14 + PUDCLK14 OFFSET(14) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ] + ] +} + +pub const GPFSEL1: *const ReadWrite = + (MMIO_BASE + 0x0020_0004) as *const ReadWrite; + +pub const GPPUD: *const ReadWrite = (MMIO_BASE + 0x0020_0094) as *const ReadWrite; + +pub const GPPUDCLK0: *const ReadWrite = + (MMIO_BASE + 0x0020_0098) as *const ReadWrite; diff --git a/0C_virtual_memory/src/main.rs b/0C_virtual_memory/src/main.rs new file mode 100644 index 00000000..6378ebe7 --- /dev/null +++ b/0C_virtual_memory/src/main.rs @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2018 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. + */ + +#![no_std] +#![no_main] +#![feature(asm)] +#![feature(const_fn)] + +extern crate cortex_a; + +#[macro_use] +extern crate raspi3_boot; + +#[macro_use] +extern crate register; + +const MMIO_BASE: u32 = 0x3F00_0000; + +mod gpio; +mod mbox; +mod mmu; +mod uart; + +entry!(kernel_entry); + +fn kernel_entry() -> ! { + let mut mbox = mbox::Mbox::new(); + + { + // Before the MMU is live, instantiate a UART driver with the physical address + let uart = uart::Uart::new(uart::UART_PHYS_BASE); + + // set up serial console + if uart.init(&mut mbox).is_err() { + loop { + cortex_a::asm::wfe() // If UART fails, abort early + } + } + + uart.getc(); // Press a key first before being greeted + uart.puts("Hello Rustacean!\n\n"); + + mmu::print_features(&uart); + + uart.puts("\nSwitching MMU on now..."); + } // 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); + + uart.puts("MMU is live \\o/\n\nWriting through the virtual mapping at 0x"); + uart.hex(UART_VIRT_BASE); + uart.puts(".\n"); + + // echo everything back + loop { + uart.send(uart.getc()); + } +} diff --git a/0C_virtual_memory/src/mbox.rs b/0C_virtual_memory/src/mbox.rs new file mode 100644 index 00000000..18fe5787 --- /dev/null +++ b/0C_virtual_memory/src/mbox.rs @@ -0,0 +1,159 @@ +/* + * MIT License + * + * Copyright (c) 2018 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::MMIO_BASE; +use core::ops; +use cortex_a::asm; +use register::mmio::{ReadOnly, WriteOnly}; + +register_bitfields! { + u32, + + STATUS [ + FULL OFFSET(31) NUMBITS(1) [], + EMPTY OFFSET(30) NUMBITS(1) [] + ] +} + +const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880; + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + READ: ReadOnly, // 0x00 + __reserved_0: [u32; 5], // 0x04 + STATUS: ReadOnly, // 0x18 + __reserved_1: u32, // 0x1C + WRITE: WriteOnly, // 0x20 +} + +// Custom errors +pub enum MboxError { + ResponseError, + UnknownError, +} +pub type Result = ::core::result::Result; + +// Channels +pub mod channel { + pub const PROP: u32 = 8; +} + +// Tags +pub mod tag { + pub const SETCLKRATE: u32 = 0x38002; + pub const LAST: u32 = 0; +} + +// Clocks +pub mod clock { + pub const UART: u32 = 0x0_0000_0002; +} + +// Responses +mod response { + pub const SUCCESS: u32 = 0x8000_0000; + pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response) +} + +pub const REQUEST: u32 = 0; + +// Public interface to the mailbox +#[repr(C)] +#[repr(align(16))] +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], +} + +/// Deref to RegisterBlock +/// +/// Allows writing +/// ``` +/// self.STATUS.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*Mbox::ptr()).STATUS.read() } +/// ``` +impl ops::Deref for Mbox { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*Self::ptr() } + } +} + +impl Mbox { + pub fn new() -> Mbox { + Mbox { buffer: [0; 36] } + } + + /// Returns a pointer to the register block + fn ptr() -> *const RegisterBlock { + VIDEOCORE_MBOX as *const _ + } + + /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success + pub fn call(&self, channel: u32) -> Result<()> { + // wait until we can write to the mailbox + loop { + if !self.STATUS.is_set(STATUS::FULL) { + break; + } + + asm::nop(); + } + + let buf_ptr = self.buffer.as_ptr() as u32; + + // write the address of our message to the mailbox with channel identifier + self.WRITE.set((buf_ptr & !0xF) | (channel & 0xF)); + + // now wait for the response + loop { + // is there a response? + loop { + if !self.STATUS.is_set(STATUS::EMPTY) { + break; + } + + asm::nop(); + } + + let resp: u32 = self.READ.get(); + + // is it a response to our message? + if ((resp & 0xF) == channel) && ((resp & !0xF) == buf_ptr) { + // is it a valid successful response? + return match self.buffer[1] { + response::SUCCESS => Ok(()), + response::ERROR => Err(MboxError::ResponseError), + _ => Err(MboxError::UnknownError), + }; + } + } + } +} diff --git a/0C_virtual_memory/src/mmu.rs b/0C_virtual_memory/src/mmu.rs new file mode 100644 index 00000000..375e685d --- /dev/null +++ b/0C_virtual_memory/src/mmu.rs @@ -0,0 +1,227 @@ +/* + * MIT License + * + * Copyright (c) 2018 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::*}; + +/// 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("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("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; + +static mut LVL2_TABLE: [u64; NUM_ENTRIES_4KIB] = [0; NUM_ENTRIES_4KIB]; +static mut SINGLE_LVL3_TABLE: [u64; NUM_ENTRIES_4KIB] = [0; NUM_ENTRIES_4KIB]; + +/// Set up identity mapped page tables for the first 1 gigabyte of address +/// space. +pub unsafe fn init() { + // First, define the two memory types that we will map. Normal DRAM and + // device. + MAIR_EL1.write( + // Attribute 1 + MAIR_EL1::Attr1_HIGH::Device + + MAIR_EL1::Attr1_LOW_DEVICE::Device_nGnRE + // Attribute 0 + + MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + + MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc, + ); + + // Two descriptive consts for indexing into the correct MAIR_EL1 attributes. + mod mair { + pub const NORMAL: u64 = 0; + pub const DEVICE: u64 = 1; + } + + // Set up the first LVL2 entry, pointing to a 4KiB table base address. + let lvl3_base: u64 = SINGLE_LVL3_TABLE.base_addr() >> 12; + LVL2_TABLE[0] = (STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Table + + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base)).value; + + // For educational purposes and fun, let the start of the second 2 MiB block + // point to the 2 MiB aperture which contains the UART's base address. + let uart_phys_base: u64 = (uart::UART_PHYS_BASE >> 21).into(); + LVL2_TABLE[1] = (STAGE1_DESCRIPTOR::VALID::True + + STAGE1_DESCRIPTOR::TYPE::Block + + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + + STAGE1_DESCRIPTOR::AP::RW_EL1 + + STAGE1_DESCRIPTOR::SH::OuterShareable + + STAGE1_DESCRIPTOR::AF::True + + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(uart_phys_base) + + STAGE1_DESCRIPTOR::XN::True) + .value; + + // Fill the rest of the LVL2 (2MiB) entries as block + // descriptors. Differentiate between normal and device mem. + let mmio_base: u64 = (super::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) + for (i, entry) in LVL2_TABLE.iter_mut().enumerate().skip(2) { + let j: u64 = i as u64; + + let mem_attr = if j >= mmio_base { + STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } else { + STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + }; + + *entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value; + } + + // Finally, fill the single LVL3 table (4KiB granule). Differentiate between + // code/RO and RW sections. + // + // Using the linker script, we ensure that the RO sections are 4KiB aligned, + // and we export their boundaries via symbols. + extern "C" { + static mut __ro_start: u64; + static mut __ro_end: u64; + } + + const PAGESIZE: u64 = 4096; + let ro_start: u64 = &__ro_start as *const _ as u64 / PAGESIZE; + let ro_end: u64 = &__ro_end as *const _ as u64 / PAGESIZE; + 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.iter_mut().enumerate() { + let j: u64 = i as u64; + + let mem_attr = if j < ro_start || j > ro_end { + STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True + } else { + STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False + }; + + *entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; + } + + // Point to the LVL2 table base address in TTBR0. + TTBR0_EL1.set_baddr(LVL2_TABLE.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 + SCTLR_EL1.modify(SCTLR_EL1::M::Enable); + + // 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 new file mode 100644 index 00000000..cf8739ef --- /dev/null +++ b/0C_virtual_memory/src/uart.rs @@ -0,0 +1,288 @@ +/* + * MIT License + * + * Copyright (c) 2018 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::MMIO_BASE; +use core::{ + ops, + sync::atomic::{compiler_fence, Ordering}, +}; +use cortex_a::asm; +use gpio; +use mbox; +use register::mmio::*; + +// PL011 UART registers. +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// Flag Register + FR [ + /// Transmit FIFO full. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_ LCRH Register. If the + /// FIFO is disabled, this bit is set when the transmit + /// holding register is full. If the FIFO is enabled, the TXFF + /// bit is set when the transmit FIFO is full. + TXFF OFFSET(5) NUMBITS(1) [], + + /// Receive FIFO empty. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_H Register. If the + /// FIFO is disabled, this bit is set when the receive holding + /// register is empty. If the FIFO is enabled, the RXFE bit is + /// set when the receive FIFO is empty. + RXFE OFFSET(4) NUMBITS(1) [] + ], + + /// Integer Baud rate divisor + IBRD [ + /// Integer Baud rate divisor + IBRD OFFSET(0) NUMBITS(16) [] + ], + + /// Fractional Baud rate divisor + FBRD [ + /// Fractional Baud rate divisor + FBRD OFFSET(0) NUMBITS(6) [] + ], + + /// Line Control register + LCRH [ + /// Word length. These bits indicate the number of data bits + /// transmitted or received in a frame. + WLEN OFFSET(5) NUMBITS(2) [ + FiveBit = 0b00, + SixBit = 0b01, + SevenBit = 0b10, + EightBit = 0b11 + ] + ], + + /// Control Register + CR [ + /// Receive enable. If this bit is set to 1, the receive + /// section of the UART is enabled. Data reception occurs for + /// UART signals. When the UART is disabled in the middle of + /// reception, it completes the current character before + /// stopping. + RXE OFFSET(9) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// Transmit enable. If this bit is set to 1, the transmit + /// section of the UART is enabled. Data transmission occurs + /// for UART signals. When the UART is disabled in the middle + /// of transmission, it completes the current character before + /// stopping. + TXE OFFSET(8) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// UART enable + UARTEN OFFSET(0) NUMBITS(1) [ + /// If the UART is disabled in the middle of transmission + /// or reception, it completes the current character + /// before stopping. + Disabled = 0, + Enabled = 1 + ] + ], + + /// Interupt Clear Register + ICR [ + /// Meta field for all pending interrupts + ALL OFFSET(0) NUMBITS(11) [] + ] +} + +pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000; + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + DR: ReadWrite, // 0x00 + __reserved_0: [u32; 5], // 0x04 + FR: ReadOnly, // 0x18 + __reserved_1: [u32; 2], // 0x1c + IBRD: WriteOnly, // 0x24 + FBRD: WriteOnly, // 0x28 + LCRH: WriteOnly, // 0x2C + CR: WriteOnly, // 0x30 + __reserved_2: [u32; 4], // 0x34 + ICR: WriteOnly, // 0x44 +} + +pub enum UartError { + MailboxError, +} +pub type Result = ::core::result::Result; + +pub struct Uart { + uart_base: u32, +} + +impl ops::Deref for Uart { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl Uart { + pub fn new(uart_base: u32) -> Uart { + Uart { uart_base } + } + + /// Returns a pointer to the register block + fn ptr(&self) -> *const RegisterBlock { + self.uart_base as *const _ + } + + ///Set baud rate and characteristics (115200 8N1) and map to GPIO + pub fn init(&self, mbox: &mut mbox::Mbox) -> Result<()> { + // turn off UART0 + self.CR.set(0); + + // set up clock for consistent divisor values + mbox.buffer[0] = 9 * 4; + mbox.buffer[1] = mbox::REQUEST; + mbox.buffer[2] = mbox::tag::SETCLKRATE; + mbox.buffer[3] = 12; + mbox.buffer[4] = 8; + mbox.buffer[5] = mbox::clock::UART; // UART clock + mbox.buffer[6] = 4_000_000; // 4Mhz + mbox.buffer[7] = 0; // skip turbo setting + mbox.buffer[8] = mbox::tag::LAST; + + // Insert a compiler fence that ensures that all stores to the + // mbox buffer are finished before the GPU is signaled (which + // is done by a store operation as well). + compiler_fence(Ordering::Release); + + if mbox.call(mbox::channel::PROP).is_err() { + return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set + }; + + // map UART0 to GPIO pins + unsafe { + (*gpio::GPFSEL1).modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0); + + (*gpio::GPPUD).set(0); // enable pins 14 and 15 + for _ in 0..150 { + asm::nop(); + } + + (*gpio::GPPUDCLK0).modify( + gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock, + ); + for _ in 0..150 { + asm::nop(); + } + + (*gpio::GPPUDCLK0).set(0); + } + + self.ICR.write(ICR::ALL::CLEAR); + self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud + self.FBRD.write(FBRD::FBRD.val(0xB)); + self.LCRH.write(LCRH::WLEN::EightBit); // 8N1 + self.CR + .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); + + Ok(()) + } + + /// Send a character + pub fn send(&self, c: char) { + // wait until we can send + loop { + if !self.FR.is_set(FR::TXFF) { + break; + } + + asm::nop(); + } + + // write the character to the buffer + self.DR.set(c as u32); + } + + /// Receive a character + pub fn getc(&self) -> char { + // wait until something is in the buffer + loop { + if !self.FR.is_set(FR::RXFE) { + break; + } + + asm::nop(); + } + + // read it and return + let mut ret = self.DR.get() as u8 as char; + + // convert carrige return to newline + if ret == '\r' { + ret = '\n' + } + + ret + } + + /// Display a string + pub fn puts(&self, string: &str) { + for c in string.chars() { + // convert newline to carrige return + newline + if c == '\n' { + self.send('\r') + } + + self.send(c); + } + } + + /// Display a binary value in hexadecimal + pub fn hex(&self, d: u32) { + let mut n; + + for i in 0..8 { + // get highest tetrad + n = d.wrapping_shr(28 - i * 4) & 0xF; + + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + // Add proper offset for ASCII table + if n > 9 { + n += 0x37; + } else { + n += 0x30; + } + + self.send(n as u8 as char); + } + } +}