diff --git a/10_privilege_level/.vscode/settings.json b/10_privilege_level/.vscode/settings.json new file mode 100644 index 00000000..f2fa6961 --- /dev/null +++ b/10_privilege_level/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.formatOnSave": true, + "rust.features": [ + "bsp_rpi3" + ], + "rust.all_targets": false, + "editor.rulers": [ + 100 + ], +} \ No newline at end of file diff --git a/10_privilege_level/Cargo.lock b/10_privilege_level/Cargo.lock new file mode 100644 index 00000000..9a17339b --- /dev/null +++ b/10_privilege_level/Cargo.lock @@ -0,0 +1,42 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "cortex-a" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "register 0.3.3 (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 = "register" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tock-registers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbb16c411ab74044f174746a6cbae67bcdebea126e376b5441e5986e6a6aa950" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "469bb5ddde81d67fb8bba4e14d77689b8166cfd077abe7530591cefe29d05823" +"checksum tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c758f5195a2e0df9d9fecf6f506506b2766ff74cf64db1e995c87e2761a5c3e2" diff --git a/10_privilege_level/Cargo.toml b/10_privilege_level/Cargo.toml new file mode 100644 index 00000000..cf0f0636 --- /dev/null +++ b/10_privilege_level/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kernel" +version = "0.1.0" +authors = ["Andre Richter "] +edition = "2018" + +[package.metadata.cargo-xbuild] +sysroot_path = "../xbuild_sysroot" + +# The features section is used to select the target board. +[features] +default = [] +bsp_rpi3 = ["cortex-a", "register"] +bsp_rpi4 = ["cortex-a", "register"] + +[dependencies] +r0 = "0.2.*" + +# Optional dependencies +cortex-a = { version = "2.*", optional = true } +register = { version = "0.3.*", optional = true } diff --git a/10_privilege_level/Makefile b/10_privilege_level/Makefile new file mode 100644 index 00000000..4cf5e508 --- /dev/null +++ b/10_privilege_level/Makefile @@ -0,0 +1,127 @@ +## SPDX-License-Identifier: MIT +## +## Copyright (c) 2018-2019 Andre Richter + +# Default to the RPi3 +ifndef BSP + BSP = rpi3 +endif + +# BSP-specific arguments +ifeq ($(BSP),rpi3) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = raspi3 + QEMU_MISC_ARGS = -serial stdio + OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg + JTAG_BOOT_IMAGE = jtag_boot_rpi3.img + LINKER_FILE = src/bsp/rpi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 +else ifeq ($(BSP),rpi4) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img +# QEMU_BINARY = qemu-system-aarch64 +# QEMU_MACHINE_TYPE = +# QEMU_MISC_ARGS = -serial stdio + OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi4.cfg + JTAG_BOOT_IMAGE = jtag_boot_rpi4.img + LINKER_FILE = src/bsp/rpi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +endif + +SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) $(wildcard **/*.ld) + +XRUSTC_CMD = cargo xrustc \ + --target=$(TARGET) \ + --features bsp_$(BSP) \ + --release \ + -- \ + -C link-arg=-T$(LINKER_FILE) \ + $(RUSTC_MISC_ARGS) + +CARGO_OUTPUT = target/$(TARGET)/release/kernel + +OBJCOPY_CMD = cargo objcopy \ + -- \ + --strip-all \ + -O binary + +CONTAINER_UTILS = rustembedded/osdev-utils + +DOCKER_CMD = docker run -it --rm +DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work +DOCKER_ARG_TTY = --privileged -v /dev:/dev +DOCKER_ARG_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag +DOCKER_ARG_NET = --network host + +DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -kernel $(OUTPUT) +DOCKER_EXEC_RASPBOOT = raspbootcom +DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyUSB0 +# DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyACM0 + +.PHONY: all doc qemu chainboot clippy clean readelf objdump nm + +all: clean $(OUTPUT) + +$(CARGO_OUTPUT): $(SOURCES) + RUSTFLAGS="-D warnings -D missing_docs" $(XRUSTC_CMD) + +$(OUTPUT): $(CARGO_OUTPUT) + cp $< . + $(OBJCOPY_CMD) $< $(OUTPUT) + +doc: + cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items + xdg-open target/$(TARGET)/doc/kernel/index.html + +ifeq ($(QEMU_MACHINE_TYPE),) +qemu: + @echo "This board is not yet supported for QEMU." +else +qemu: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_QEMU) $(QEMU_MISC_ARGS) +endif + +chainboot: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_TTY) \ + $(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + $(OUTPUT) + +jtagboot: + $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_JTAG) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + /jtag/$(JTAG_BOOT_IMAGE) + +openocd: + $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ + openocd $(OPENOCD_ARG) + +define gen_gdb + RUSTFLAGS="-D warnings -D missing_docs" $(XRUSTC_CMD) $1 + cp $(CARGO_OUTPUT) kernel_for_jtag + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ + gdb-multiarch -q kernel_for_jtag +endef + +gdb: clean $(SOURCES) + $(call gen_gdb,-C debuginfo=2) + +gdb-opt0: clean $(SOURCES) + $(call gen_gdb,-C debuginfo=2 -C opt-level=0) + +clippy: + cargo xclippy --target=$(TARGET) --features bsp_$(BSP) + +clean: + cargo clean + +readelf: + readelf -a kernel + +objdump: + cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel + +nm: + cargo nm --target $(TARGET) -- kernel | sort diff --git a/10_privilege_level/README.md b/10_privilege_level/README.md new file mode 100644 index 00000000..68172fc3 --- /dev/null +++ b/10_privilege_level/README.md @@ -0,0 +1,406 @@ +# Tutorial 10 - Privilege Level + +## tl;dr + +In early boot code, we transition from the `Hypervisor` privilege level (`EL2` in AArch64) to the +`Kernel` (`EL1`) privilege level. + +## Introduction + +Application-grade CPUs have so-called `privilege levels`, which have different purposes: + +| Typically used for | AArch64 | RISC-V | x86 | +| ------------- | ------------- | ------------- | ------------- | +| Userspace applications | EL0 | U/VU | Ring 3 | +| OS Kernel | EL1 | S/VS | Ring 0 | +| Hypervisor | EL2 | HS | Ring -1 | +| Low-Level Firmware | EL3 | M | | + +`EL` in AArch64 stands for `Exception Level`. If you want more information regarding the other +architectures, please have a look at the following links: +- [x86 privilege rings](https://en.wikipedia.org/wiki/Protection_ring). +- [RISC-V privilege modes](https://content.riscv.org/wp-content/uploads/2017/12/Tue0942-riscv-hypervisor-waterman.pdf). + +At this point, I strongly recommend that you glimpse over `Chapter 3` of the [Programmer’s Guide for +ARMv8-A](http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf) +before you continue. It gives a concise overview about the topic. + +## Scope of this tutorial + +If you set up your SD Card exactly like mentioned in [tutorial 06], the Rpi will always start +executing in `EL2`. Since we are writing a traditional `Kernel`, we have to transition into the more +appropriate `EL1`. + +[tutorial 06]: https://github.com/rust-embedded/rust-raspi3-OS-tutorials/tree/master/06_drivers_gpio_uart#boot-it-from-sd-card + +## Checking for EL2 in the entrypoint + +First of all, we need to ensure that we actually execute in `EL2` before we can call respective code +to transition to `EL1`: + +```rust +pub unsafe extern "C" fn _start() -> ! { + const CORE_MASK: u64 = 0x3; + + // Expect the boot core to start in EL2. + if (bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK) + && (CurrentEL.get() == CurrentEL::EL::EL2.value) + { + el2_to_el1_transition() + } else { + // If not core0, infinitely wait for events. + wait_forever() + } +} +``` + +If this is the case, we continue with preparing the `EL2` -> `EL1` transition in +`el2_to_el1_transition()`. + +## Transition preparation + +Since `EL2` is more privileged than `EL1`, it has control over various processor features and can +allow or disallow `EL1` code to use them. One such example is access to timer and counter registers. +We are already using them since [tutorial 08](../08_timestamps/), so of course we want to keep them. +Therefore we set the respective flags in the [Counter-timer Hypervisor Control register] and +additionally set the virtual offset to zero so that we get the real physical value everytime: + +[Counter-timer Hypervisor Control register]: https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/cnthctl_el2.rs.html + +```rust +// 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); +``` + +Next, we configure the [Hypervisor Configuration Register] such that `EL1` should actually run in +`AArch64` mode, and not in `AArch32`, which would also be possible. + +[Hypervisor Configuration Register]: https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/hcr_el2.rs.html + +```rust +// Set EL1 execution state to AArch64. +HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); +``` + +## Returning from an exception that never happened + +There is actually only one way to transition from a higher EL to a lower EL, which is by way of +executing the [ERET] instruction. + +[ERET]: https://docs.rs/cortex-a/2.4.0/src/cortex_a/asm.rs.html#49-62 + +This instruction will copy the contents of the [Saved Program Status Register - EL2] to `Current +Program Status Register - EL1` and jump to the instruction address that is stored in the [Exception +Link Register - EL2]. + +This is basically the reverse of what is happening when an exception is taken. You'll learn about it +in an upcoming tutorial. + +[Saved Program Status Register - EL2]: https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/spsr_el2.rs.html +[Exception Link Register - EL2]: https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/elr_el2.rs.html + +```rust +// Set up a simulated exception return. +// +// First, fake a saved program status, where all interrupts were masked and SP_EL1 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::EL1h, +); + +// Second, let the link register point to init(). +ELR_EL2.set(crate::runtime_init::init as *const () as u64); +``` + +As you can see, we are populating `ELR_EL2` with the address of the [init()] function that we +earlier used to call directly from the entrypoint. + +Finally, we set the stack pointer for `SP_EL1` and call `ERET`: + +[init()]: src/runtime_init.rs + +```rust +// Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. +SP_EL1.set(bsp::BOOT_CORE_STACK_START); + +// Use `eret` to "return" to EL1. This will result in execution of `reset()` in EL1. +asm::eret() +``` + +## Are we stackless? + +We just wrote a big inline rust function, `el2_to_el1_transition()`, that is executed in a context +where we do not have a stack yet. We should double-check the generated machine code: + +```console +make objdump +[...] +Disassembly of section .text: + +0000000000080000 _start: + 80000: a8 00 38 d5 mrs x8, MPIDR_EL1 + 80004: 1f 05 40 f2 tst x8, #0x3 + 80008: 81 00 00 54 b.ne #0x10 <_start+0x18> + 8000c: 48 42 38 d5 mrs x8, CurrentEL + 80010: 1f 21 00 71 cmp w8, #0x8 + 80014: 60 00 00 54 b.eq #0xc <_start+0x20> + 80018: 5f 20 03 d5 wfe + 8001c: ff ff ff 17 b #-0x4 <_start+0x18> + 80020: e8 03 1f aa mov x8, xzr + 80024: 69 00 80 52 mov w9, #0x3 + 80028: 09 e1 1c d5 msr CNTHCTL_EL2, x9 + 8002c: 68 e0 1c d5 msr CNTVOFF_EL2, x8 + 80030: 08 00 00 90 adrp x8, #0x0 + 80034: 0a 00 b0 52 mov w10, #-0x80000000 + 80038: ab 78 80 52 mov w11, #0x3c5 + 8003c: 0c 01 a0 52 mov w12, #0x80000 + 80040: 0a 11 1c d5 msr HCR_EL2, x10 + 80044: 0b 40 1c d5 msr SPSR_EL2, x11 + 80048: 08 91 2f 91 add x8, x8, #0xbe4 + 8004c: 28 40 1c d5 msr ELR_EL2, x8 + 80050: 0c 41 1c d5 msr SP_EL1, x12 + 80054: e0 03 9f d6 eret +``` + +Looks good! Thanks zero-overhead abstractions in the +[cortex-a](https://github.com/rust-embedded/cortex-a) crate! :heart_eyes: + +## Testing + +In `main.rs`, we additionally inspect if the mask bits in `SPSR_EL2` made it to `EL1` as well: + +```console +make chainbot +[...] +### Listening on /dev/ttyUSB0 + __ __ _ _ _ _ +| \/ (_)_ _ (_) | ___ __ _ __| | +| |\/| | | ' \| | |__/ _ \/ _` / _` | +|_| |_|_|_||_|_|____\___/\__,_\__,_| + + Raspberry Pi 3 + +[ML] Requesting binary +### sending kernel kernel8.img [16480 byte] +### finished sending +[ML] Loaded! Executing the payload now + +[ 1.459973] Booting on: Raspberry Pi 3 +[ 1.462256] Current privilege level: EL1 +[ 1.466163] Exception handling state: +[ 1.469810] Debug: Masked +[ 1.473023] SError: Masked +[ 1.476235] IRQ: Masked +[ 1.479447] FIQ: Masked +[ 1.482661] Architectural timer resolution: 52 ns +[ 1.487349] Drivers loaded: +[ 1.490127] 1. GPIO +[ 1.492731] 2. PL011Uart +[ 1.495770] Timer test, spinning for 1 second +[ 2.500114] Echoing input now +``` + +## Diff to previous +```diff + +diff -uNr 09_hw_debug_JTAG/src/arch/aarch64/exception.rs 10_privilege_level/src/arch/aarch64/exception.rs +--- 09_hw_debug_JTAG/src/arch/aarch64/exception.rs ++++ 10_privilege_level/src/arch/aarch64/exception.rs +@@ -0,0 +1,44 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright (c) 2018-2019 Andre Richter ++ ++//! Exception handling. ++ ++use cortex_a::regs::*; ++ ++pub trait DaifField { ++ fn daif_field() -> register::Field; ++} ++ ++pub struct Debug; ++pub struct SError; ++pub struct IRQ; ++pub struct FIQ; ++ ++impl DaifField for Debug { ++ fn daif_field() -> register::Field { ++ DAIF::D ++ } ++} ++ ++impl DaifField for SError { ++ fn daif_field() -> register::Field { ++ DAIF::A ++ } ++} ++ ++impl DaifField for IRQ { ++ fn daif_field() -> register::Field { ++ DAIF::I ++ } ++} ++ ++impl DaifField for FIQ { ++ fn daif_field() -> register::Field { ++ DAIF::F ++ } ++} ++ ++pub fn is_masked() -> bool { ++ DAIF.is_set(T::daif_field()) ++} + +diff -uNr 09_hw_debug_JTAG/src/arch/aarch64.rs 10_privilege_level/src/arch/aarch64.rs +--- 09_hw_debug_JTAG/src/arch/aarch64.rs ++++ 10_privilege_level/src/arch/aarch64.rs +@@ -4,6 +4,7 @@ + + //! AArch64. + ++mod exception; + pub mod sync; + mod time; + +@@ -21,15 +22,51 @@ + pub unsafe extern "C" fn _start() -> ! { + const CORE_MASK: u64 = 0x3; + +- if bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK { +- SP.set(bsp::BOOT_CORE_STACK_START); +- crate::runtime_init::init() ++ // Expect the boot core to start in EL2. ++ if (bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK) ++ && (CurrentEL.get() == CurrentEL::EL::EL2.value) ++ { ++ el2_to_el1_transition() + } else { + // If not core0, infinitely wait for events. + wait_forever() + } + } + ++/// Transition from EL2 to EL1. ++#[inline(always)] ++fn el2_to_el1_transition() -> ! { ++ // 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. ++ HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); ++ ++ // Set up a simulated exception return. ++ // ++ // First, fake a saved program status, where all interrupts were masked and SP_EL1 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::EL1h, ++ ); ++ ++ // Second, let the link register point to init(). ++ ELR_EL2.set(crate::runtime_init::init as *const () as u64); ++ ++ // Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. ++ SP_EL1.set(bsp::BOOT_CORE_STACK_START); ++ ++ // Use `eret` to "return" to EL1. This will result in execution of `reset()` in EL1. ++ asm::eret() ++} ++ + //-------------------------------------------------------------------------------------------------- + // Global instances + //-------------------------------------------------------------------------------------------------- +@@ -61,3 +98,36 @@ + asm::wfe() + } + } ++ ++/// Information about the HW state. ++pub mod state { ++ use cortex_a::regs::*; ++ ++ /// The current privilege level. ++ pub fn current_privilege_level() -> &'static str { ++ let el = CurrentEL.read_as_enum(CurrentEL::EL); ++ match el { ++ Some(CurrentEL::EL::Value::EL2) => "EL2", ++ Some(CurrentEL::EL::Value::EL1) => "EL1", ++ _ => "Unknown", ++ } ++ } ++ ++ #[rustfmt::skip] ++ pub fn print_exception_state() { ++ use super::{ ++ exception, ++ exception::{Debug, SError, FIQ, IRQ}, ++ }; ++ use crate::println; ++ ++ let to_mask_str = |x: bool| -> &'static str { ++ if x { "Masked" } else { "Unmasked" } ++ }; ++ ++ println!(" Debug: {}", to_mask_str(exception::is_masked::())); ++ println!(" SError: {}", to_mask_str(exception::is_masked::())); ++ println!(" IRQ: {}", to_mask_str(exception::is_masked::())); ++ println!(" FIQ: {}", to_mask_str(exception::is_masked::())); ++ } ++} + +diff -uNr 09_hw_debug_JTAG/src/main.rs 10_privilege_level/src/main.rs +--- 09_hw_debug_JTAG/src/main.rs ++++ 10_privilege_level/src/main.rs +@@ -65,9 +65,17 @@ + /// The main function running after the early init. + fn kernel_main() -> ! { + use core::time::Duration; +- use interface::time::Timer; ++ use interface::{console::All, time::Timer}; + + println!("Booting on: {}", bsp::board_name()); ++ ++ println!( ++ "Current privilege level: {}", ++ arch::state::current_privilege_level() ++ ); ++ println!("Exception handling state:"); ++ arch::state::print_exception_state(); ++ + println!( + "Architectural timer resolution: {} ns", + arch::timer().resolution().as_nanos() +@@ -78,11 +86,12 @@ + println!(" {}. {}", i + 1, driver.compatible()); + } + +- // Test a failing timer case. +- arch::timer().spin_for(Duration::from_nanos(1)); ++ println!("Timer test, spinning for 1 second"); ++ arch::timer().spin_for(Duration::from_secs(1)); + ++ println!("Echoing input now"); + loop { +- println!("Spinning for 1 second"); +- arch::timer().spin_for(Duration::from_secs(1)); ++ let c = bsp::console().read_char(); ++ bsp::console().write_char(c); + } + } + +``` diff --git a/10_privilege_level/kernel b/10_privilege_level/kernel new file mode 100755 index 00000000..eb7c2e42 Binary files /dev/null and b/10_privilege_level/kernel differ diff --git a/10_privilege_level/kernel8.img b/10_privilege_level/kernel8.img new file mode 100755 index 00000000..0268a52c Binary files /dev/null and b/10_privilege_level/kernel8.img differ diff --git a/10_privilege_level/src/arch.rs b/10_privilege_level/src/arch.rs new file mode 100644 index 00000000..b1f035c5 --- /dev/null +++ b/10_privilege_level/src/arch.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of processor architecture code. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod aarch64; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use aarch64::*; diff --git a/10_privilege_level/src/arch/aarch64.rs b/10_privilege_level/src/arch/aarch64.rs new file mode 100644 index 00000000..7905a3ff --- /dev/null +++ b/10_privilege_level/src/arch/aarch64.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! AArch64. + +mod exception; +pub mod sync; +mod time; + +use crate::{bsp, interface}; +use cortex_a::{asm, regs::*}; + +/// The entry of the `kernel` binary. +/// +/// The function must be named `_start`, because the linker is looking for this exact name. +/// +/// # Safety +/// +/// - Linker script must ensure to place this function at `0x80_000`. +#[no_mangle] +pub unsafe extern "C" fn _start() -> ! { + const CORE_MASK: u64 = 0x3; + + // Expect the boot core to start in EL2. + if (bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK) + && (CurrentEL.get() == CurrentEL::EL::EL2.value) + { + el2_to_el1_transition() + } else { + // If not core0, infinitely wait for events. + wait_forever() + } +} + +/// Transition from EL2 to EL1. +#[inline(always)] +fn el2_to_el1_transition() -> ! { + // 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. + HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); + + // Set up a simulated exception return. + // + // First, fake a saved program status, where all interrupts were masked and SP_EL1 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::EL1h, + ); + + // Second, let the link register point to init(). + ELR_EL2.set(crate::runtime_init::init as *const () as u64); + + // Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it. + SP_EL1.set(bsp::BOOT_CORE_STACK_START); + + // Use `eret` to "return" to EL1. This will result in execution of `reset()` in EL1. + asm::eret() +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static TIMER: time::Timer = time::Timer; + +//-------------------------------------------------------------------------------------------------- +// Implementation of the kernel's architecture abstraction code +//-------------------------------------------------------------------------------------------------- + +pub use asm::nop; + +/// Spin for `n` cycles. +pub fn spin_for_cycles(n: usize) { + for _ in 0..n { + asm::nop(); + } +} + +/// Return a reference to a `interface::time::TimeKeeper` implementation. +pub fn timer() -> &'static impl interface::time::Timer { + &TIMER +} + +/// Pause execution on the calling CPU core. +#[inline(always)] +pub fn wait_forever() -> ! { + loop { + asm::wfe() + } +} + +/// Information about the HW state. +pub mod state { + use cortex_a::regs::*; + + /// The current privilege level. + pub fn current_privilege_level() -> &'static str { + let el = CurrentEL.read_as_enum(CurrentEL::EL); + match el { + Some(CurrentEL::EL::Value::EL2) => "EL2", + Some(CurrentEL::EL::Value::EL1) => "EL1", + _ => "Unknown", + } + } + + #[rustfmt::skip] + pub fn print_exception_state() { + use super::{ + exception, + exception::{Debug, SError, FIQ, IRQ}, + }; + use crate::println; + + let to_mask_str = |x: bool| -> &'static str { + if x { "Masked" } else { "Unmasked" } + }; + + println!(" Debug: {}", to_mask_str(exception::is_masked::())); + println!(" SError: {}", to_mask_str(exception::is_masked::())); + println!(" IRQ: {}", to_mask_str(exception::is_masked::())); + println!(" FIQ: {}", to_mask_str(exception::is_masked::())); + } +} diff --git a/10_privilege_level/src/arch/aarch64/exception.rs b/10_privilege_level/src/arch/aarch64/exception.rs new file mode 100644 index 00000000..27939b84 --- /dev/null +++ b/10_privilege_level/src/arch/aarch64/exception.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Exception handling. + +use cortex_a::regs::*; + +pub trait DaifField { + fn daif_field() -> register::Field; +} + +pub struct Debug; +pub struct SError; +pub struct IRQ; +pub struct FIQ; + +impl DaifField for Debug { + fn daif_field() -> register::Field { + DAIF::D + } +} + +impl DaifField for SError { + fn daif_field() -> register::Field { + DAIF::A + } +} + +impl DaifField for IRQ { + fn daif_field() -> register::Field { + DAIF::I + } +} + +impl DaifField for FIQ { + fn daif_field() -> register::Field { + DAIF::F + } +} + +pub fn is_masked() -> bool { + DAIF.is_set(T::daif_field()) +} diff --git a/10_privilege_level/src/arch/aarch64/sync.rs b/10_privilege_level/src/arch/aarch64/sync.rs new file mode 100644 index 00000000..dfebc0e1 --- /dev/null +++ b/10_privilege_level/src/arch/aarch64/sync.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Synchronization primitives. + +use crate::interface; +use core::cell::UnsafeCell; + +/// A pseudo-lock for teaching purposes. +/// +/// Used to introduce [interior mutability]. +/// +/// In contrast to a real Mutex implementation, does not protect against concurrent access to the +/// contained data. This part is preserved for later lessons. +/// +/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is +/// executing single-threaded, aka only running on a single core with interrupts disabled. +/// +/// [interior mutability]: https://doc.rust-lang.org/std/cell/index.html +pub struct NullLock { + data: UnsafeCell, +} + +unsafe impl Send for NullLock {} +unsafe impl Sync for NullLock {} + +impl NullLock { + pub const fn new(data: T) -> NullLock { + NullLock { + data: UnsafeCell::new(data), + } + } +} + +impl interface::sync::Mutex for &NullLock { + type Data = T; + + fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + f(unsafe { &mut *self.data.get() }) + } +} diff --git a/10_privilege_level/src/arch/aarch64/time.rs b/10_privilege_level/src/arch/aarch64/time.rs new file mode 100644 index 00000000..a6c9d50c --- /dev/null +++ b/10_privilege_level/src/arch/aarch64/time.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Timer primitives. + +use crate::{interface, warn}; +use core::time::Duration; +use cortex_a::regs::*; + +const NS_PER_S: u64 = 1_000_000_000; + +pub struct Timer; + +impl interface::time::Timer for Timer { + fn resolution(&self) -> Duration { + Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + } + + fn uptime(&self) -> Duration { + let frq: u64 = CNTFRQ_EL0.get() as u64; + let current_count: u64 = CNTPCT_EL0.get() * NS_PER_S; + + Duration::from_nanos(current_count / frq) + } + + fn spin_for(&self, duration: Duration) { + // Instantly return on zero. + if duration.as_nanos() == 0 { + return; + } + + // Calculate the register compare value. + let frq = CNTFRQ_EL0.get() as u64; + let x = match frq.checked_mul(duration.as_nanos() as u64) { + None => { + warn!("Spin duration too long, skipping"); + return; + } + Some(val) => val, + }; + let tval = x / NS_PER_S; + + // Check if it is within supported bounds. + let warn: Option<&str> = if tval == 0 { + Some("smaller") + } else if tval > u32::max_value().into() { + Some("bigger") + } else { + None + }; + + if let Some(w) = warn { + warn!( + "Spin duration {} than architecturally supported, skipping", + w + ); + return; + } + + // Set the compare value register. + CNTP_TVAL_EL0.set(tval as u32); + + // Kick off the counting. // Disable timer interrupt. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + + loop { + // ISTATUS will be '1' when cval ticks have passed. Busy-check it. + if CNTP_CTL_EL0.is_set(CNTP_CTL_EL0::ISTATUS) { + break; + } + } + + // Disable counting again. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + } +} diff --git a/10_privilege_level/src/bsp.rs b/10_privilege_level/src/bsp.rs new file mode 100644 index 00000000..3db8e14a --- /dev/null +++ b/10_privilege_level/src/bsp.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of Board Support Packages. + +mod driver; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod rpi; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use rpi::*; diff --git a/10_privilege_level/src/bsp/driver.rs b/10_privilege_level/src/bsp/driver.rs new file mode 100644 index 00000000..c910274e --- /dev/null +++ b/10_privilege_level/src/bsp/driver.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Drivers. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod bcm; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use bcm::*; diff --git a/10_privilege_level/src/bsp/driver/bcm.rs b/10_privilege_level/src/bsp/driver/bcm.rs new file mode 100644 index 00000000..15283aea --- /dev/null +++ b/10_privilege_level/src/bsp/driver/bcm.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! BCM driver top level. + +mod bcm2xxx_gpio; +mod bcm2xxx_pl011_uart; + +pub use bcm2xxx_gpio::GPIO; +pub use bcm2xxx_pl011_uart::PL011Uart; diff --git a/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_gpio.rs b/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_gpio.rs new file mode 100644 index 00000000..a9ceda61 --- /dev/null +++ b/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_gpio.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! GPIO driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::ops; +use register::{mmio::ReadWrite, register_bitfields}; + +// GPIO registers. +// +// 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, + AltFunc0 = 0b100 // PL011 UART RX + + ], + + /// Pin 14 + FSEL14 OFFSET(12) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100 // PL011 UART TX + ] + ], + + /// 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 + ] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + pub GPFSEL0: ReadWrite, // 0x00 + pub GPFSEL1: ReadWrite, // 0x04 + pub GPFSEL2: ReadWrite, // 0x08 + pub GPFSEL3: ReadWrite, // 0x0C + pub GPFSEL4: ReadWrite, // 0x10 + pub GPFSEL5: ReadWrite, // 0x14 + __reserved_0: u32, // 0x18 + GPSET0: ReadWrite, // 0x1C + GPSET1: ReadWrite, // 0x20 + __reserved_1: u32, // + GPCLR0: ReadWrite, // 0x28 + __reserved_2: [u32; 2], // + GPLEV0: ReadWrite, // 0x34 + GPLEV1: ReadWrite, // 0x38 + __reserved_3: u32, // + GPEDS0: ReadWrite, // 0x40 + GPEDS1: ReadWrite, // 0x44 + __reserved_4: [u32; 7], // + GPHEN0: ReadWrite, // 0x64 + GPHEN1: ReadWrite, // 0x68 + __reserved_5: [u32; 10], // + pub GPPUD: ReadWrite, // 0x94 + pub GPPUDCLK0: ReadWrite, // 0x98 + pub GPPUDCLK1: ReadWrite, // 0x9C +} + +/// The driver's private data. +struct GPIOInner { + base_addr: usize, +} + +/// Deref to RegisterBlock. +impl ops::Deref for GPIOInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl GPIOInner { + const fn new(base_addr: usize) -> GPIOInner { + GPIOInner { base_addr } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } +} + +//-------------------------------------------------------------------------------------------------- +// BSP-public +//-------------------------------------------------------------------------------------------------- +use interface::sync::Mutex; + +/// The driver's main struct. +pub struct GPIO { + inner: NullLock, +} + +impl GPIO { + pub const unsafe fn new(base_addr: usize) -> GPIO { + GPIO { + inner: NullLock::new(GPIOInner::new(base_addr)), + } + } + + /// Map PL011 UART as standard output. + /// + /// TX to pin 14 + /// RX to pin 15 + pub fn map_pl011_uart(&self) { + let mut r = &self.inner; + r.lock(|inner| { + // Map to pins. + inner + .GPFSEL1 + .modify(GPFSEL1::FSEL14::AltFunc0 + GPFSEL1::FSEL15::AltFunc0); + + // Enable pins 14 and 15. + inner.GPPUD.set(0); + arch::spin_for_cycles(150); + + inner + .GPPUDCLK0 + .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock); + arch::spin_for_cycles(150); + + inner.GPPUDCLK0.set(0); + }) + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- + +impl interface::driver::DeviceDriver for GPIO { + fn compatible(&self) -> &str { + "GPIO" + } +} diff --git a/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs b/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs new file mode 100644 index 00000000..78303c49 --- /dev/null +++ b/10_privilege_level/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! PL011 UART driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::{fmt, ops}; +use register::{mmio::*, register_bitfields}; + +// 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 empty. The meaning of this bit depends on the state of the FEN bit in the + /// Line Control Register, UARTLCR_ LCRH. + /// + /// If the FIFO is disabled, this bit is set when the transmit holding register is empty. If + /// the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty. This bit does + /// not indicate if there is data in the transmit shift register. + TXFE OFFSET(7) NUMBITS(1) [], + + /// 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 + ], + + /// Enable FIFOs: + /// + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding + /// registers + /// + /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). + FEN OFFSET(4) NUMBITS(1) [ + FifosDisabled = 0, + FifosEnabled = 1 + ] + ], + + /// 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 + ] + ], + + /// Interrupt Clear Register + ICR [ + /// Meta field for all pending interrupts + ALL OFFSET(0) NUMBITS(11) [] + ] +} + +#[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 +} + +/// The driver's mutex protected part. +struct PL011UartInner { + base_addr: usize, + chars_written: usize, +} + +/// Deref to RegisterBlock. +/// +/// Allows writing +/// ``` +/// self.DR.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*PL011UartInner::ptr()).DR.read() } +/// ``` +impl ops::Deref for PL011UartInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl PL011UartInner { + const fn new(base_addr: usize) -> PL011UartInner { + PL011UartInner { + base_addr, + chars_written: 0, + } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } + + /// Send a character. + fn write_char(&mut self, c: char) { + // Wait until we can send. + loop { + if !self.FR.is_set(FR::TXFF) { + break; + } + + arch::nop(); + } + + // Write the character to the buffer. + self.DR.set(c as u32); + } +} + +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl fmt::Write for PL011UartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + // Convert newline to carrige return + newline. + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + + self.chars_written += s.len(); + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// BSP-public +//-------------------------------------------------------------------------------------------------- + +/// The driver's main struct. +pub struct PL011Uart { + inner: NullLock, +} + +impl PL011Uart { + /// # Safety + /// + /// The user must ensure to provide the correct `base_addr`. + pub const unsafe fn new(base_addr: usize) -> PL011Uart { + PL011Uart { + inner: NullLock::new(PL011UartInner::new(base_addr)), + } + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- +use interface::sync::Mutex; + +impl interface::driver::DeviceDriver for PL011Uart { + fn compatible(&self) -> &str { + "PL011Uart" + } + + /// Set up baud rate and characteristics + /// + /// Results in 8N1 and 115200 baud (if the clk has been previously set to 4 MHz by the + /// firmware). + fn init(&self) -> interface::driver::Result { + let mut r = &self.inner; + r.lock(|inner| { + // Turn it off temporarily. + inner.CR.set(0); + + inner.ICR.write(ICR::ALL::CLEAR); + inner.IBRD.write(IBRD::IBRD.val(26)); // Results in 115200 baud for UART Clk of 48 MHz. + inner.FBRD.write(FBRD::FBRD.val(3)); + inner + .LCRH + .write(LCRH::WLEN::EightBit + LCRH::FEN::FifosEnabled); // 8N1 + Fifo on + inner + .CR + .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); + }); + + Ok(()) + } +} + +impl interface::console::Write for PL011Uart { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn write_char(&self, c: char) { + let mut r = &self.inner; + r.lock(|inner| inner.write_char(c)); + } + + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + // Fully qualified syntax for the call to `core::fmt::Write::write:fmt()` to increase + // readability. + let mut r = &self.inner; + r.lock(|inner| fmt::Write::write_fmt(inner, args)) + } + + fn flush(&self) { + let mut r = &self.inner; + // Spin until the TX FIFO empty flag is set. + r.lock(|inner| loop { + if inner.FR.is_set(FR::TXFE) { + break; + } + + arch::nop(); + }); + } +} + +impl interface::console::Read for PL011Uart { + fn read_char(&self) -> char { + let mut r = &self.inner; + r.lock(|inner| { + // Wait until buffer is filled. + loop { + if !inner.FR.is_set(FR::RXFE) { + break; + } + + arch::nop(); + } + + // Read one character. + let mut ret = inner.DR.get() as u8 as char; + + // Convert carrige return to newline. + if ret == '\r' { + ret = '\n' + } + + ret + }) + } + + fn clear(&self) { + let mut r = &self.inner; + r.lock(|inner| loop { + // Read from the RX FIFO until the empty bit is '1'. + if !inner.FR.is_set(FR::RXFE) { + inner.DR.get(); + } else { + break; + } + }) + } +} + +impl interface::console::Statistics for PL011Uart { + fn chars_written(&self) -> usize { + let mut r = &self.inner; + r.lock(|inner| inner.chars_written) + } +} diff --git a/10_privilege_level/src/bsp/rpi.rs b/10_privilege_level/src/bsp/rpi.rs new file mode 100644 index 00000000..c22c47bb --- /dev/null +++ b/10_privilege_level/src/bsp/rpi.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board Support Package for the Raspberry Pi. + +mod memory_map; + +use super::driver; +use crate::interface; + +pub const BOOT_CORE_ID: u64 = 0; +pub const BOOT_CORE_STACK_START: u64 = 0x80_000; + +//-------------------------------------------------------------------------------------------------- +// Global BSP driver instances +//-------------------------------------------------------------------------------------------------- + +static GPIO: driver::GPIO = unsafe { driver::GPIO::new(memory_map::mmio::GPIO_BASE) }; +static PL011_UART: driver::PL011Uart = + unsafe { driver::PL011Uart::new(memory_map::mmio::PL011_UART_BASE) }; + +//-------------------------------------------------------------------------------------------------- +// Implementation of the kernel's BSP calls +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + #[cfg(feature = "bsp_rpi3")] + { + "Raspberry Pi 3" + } + + #[cfg(feature = "bsp_rpi4")] + { + "Raspberry Pi 4" + } +} + +/// Return a reference to a `console::All` implementation. +pub fn console() -> &'static impl interface::console::All { + &PL011_UART +} + +/// Return an array of references to all `DeviceDriver` compatible `BSP` drivers. +/// +/// # Safety +/// +/// The order of devices is the order in which `DeviceDriver::init()` is called. +pub fn device_drivers() -> [&'static dyn interface::driver::DeviceDriver; 2] { + [&GPIO, &PL011_UART] +} + +/// BSP initialization code that runs after driver init. +pub fn post_driver_init() { + // Configure PL011Uart's output pins. + GPIO.map_pl011_uart(); +} diff --git a/10_privilege_level/src/bsp/rpi/link.ld b/10_privilege_level/src/bsp/rpi/link.ld new file mode 100644 index 00000000..53b65640 --- /dev/null +++ b/10_privilege_level/src/bsp/rpi/link.ld @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (c) 2018-2019 Andre Richter + */ + +SECTIONS +{ + /* Set current address to the value from which the RPi starts execution */ + . = 0x80000; + + .text : + { + *(.text._start) *(.text*) + } + + .rodata : + { + *(.rodata*) + } + + .data : + { + *(.data*) + } + + /* Align to 8 byte boundary */ + .bss ALIGN(8): + { + __bss_start = .; + *(.bss*); + __bss_end = .; + } + + /DISCARD/ : { *(.comment*) } +} diff --git a/10_privilege_level/src/bsp/rpi/memory_map.rs b/10_privilege_level/src/bsp/rpi/memory_map.rs new file mode 100644 index 00000000..ed617f5e --- /dev/null +++ b/10_privilege_level/src/bsp/rpi/memory_map.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! The board's memory map. + +/// Physical devices. +#[rustfmt::skip] +pub mod mmio { + #[cfg(feature = "bsp_rpi3")] + pub const BASE: usize = 0x3F00_0000; + + #[cfg(feature = "bsp_rpi4")] + pub const BASE: usize = 0xFE00_0000; + + pub const GPIO_BASE: usize = BASE + 0x0020_0000; + pub const PL011_UART_BASE: usize = BASE + 0x0020_1000; +} diff --git a/10_privilege_level/src/interface.rs b/10_privilege_level/src/interface.rs new file mode 100644 index 00000000..55df89cb --- /dev/null +++ b/10_privilege_level/src/interface.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Trait definitions for coupling `kernel` and `BSP` code. +//! +//! ``` +//! +-------------------+ +//! | Interface (Trait) | +//! | | +//! +--+-------------+--+ +//! ^ ^ +//! | | +//! | | +//! +----------+--+ +--+----------+ +//! | Kernel code | | BSP Code | +//! | | | | +//! +-------------+ +-------------+ +//! ``` + +/// System console operations. +pub mod console { + use core::fmt; + + /// Console write functions. + pub trait Write { + fn write_char(&self, c: char); + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; + + /// Block execution until the last character has been physically put on the TX wire + /// (draining TX buffers/FIFOs, if any). + fn flush(&self); + } + + /// Console read functions. + pub trait Read { + fn read_char(&self) -> char { + ' ' + } + + /// Clear RX buffers, if any. + fn clear(&self); + } + + /// Console statistics. + pub trait Statistics { + /// Return the number of characters written. + fn chars_written(&self) -> usize { + 0 + } + + /// Return the number of characters read. + fn chars_read(&self) -> usize { + 0 + } + } + + /// Trait alias for a full-fledged console. + pub trait All = Write + Read + Statistics; +} + +/// Synchronization primitives. +pub mod sync { + /// Any object implementing this trait guarantees exclusive access to the data contained within + /// the mutex for the duration of the lock. + /// + /// The trait follows the [Rust embedded WG's + /// proposal](https://github.com/korken89/wg/blob/master/rfcs/0377-mutex-trait.md) and therefore + /// provides some goodness such as [deadlock + /// prevention](https://github.com/korken89/wg/blob/master/rfcs/0377-mutex-trait.md#design-decisions-and-compatibility). + /// + /// # Example + /// + /// Since the lock function takes an `&mut self` to enable deadlock-prevention, the trait is + /// best implemented **for a reference to a container struct**, and has a usage pattern that + /// might feel strange at first: + /// + /// ``` + /// static MUT: Mutex> = Mutex::new(RefCell::new(0)); + /// + /// fn foo() { + /// let mut r = &MUT; // Note that r is mutable + /// r.lock(|data| *data += 1); + /// } + /// ``` + pub trait Mutex { + /// Type of data encapsulated by the mutex. + type Data; + + /// Creates a critical section and grants temporary mutable access to the encapsulated data. + fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R; + } +} + +/// Driver interfaces. +pub mod driver { + /// Driver result type, e.g. for indicating successful driver init. + pub type Result = core::result::Result<(), ()>; + + /// Device Driver functions. + pub trait DeviceDriver { + /// Return a compatibility string for identifying the driver. + fn compatible(&self) -> &str; + + /// Called by the kernel to bring up the device. + fn init(&self) -> Result { + Ok(()) + } + } +} + +/// Timekeeping interfaces. +pub mod time { + use core::time::Duration; + + /// Timer functions. + pub trait Timer { + /// The timer's resolution. + fn resolution(&self) -> Duration; + + /// The uptime since power-on of the device. + /// + /// This includes time consumed by firmware and bootloaders. + fn uptime(&self) -> Duration; + + /// Spin for a given duration. + fn spin_for(&self, duration: Duration); + } +} diff --git a/10_privilege_level/src/main.rs b/10_privilege_level/src/main.rs new file mode 100644 index 00000000..e89366c4 --- /dev/null +++ b/10_privilege_level/src/main.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +// Rust embedded logo for `make doc`. +#![doc(html_logo_url = "https://git.io/JeGIp")] + +//! The `kernel` +//! +//! The `kernel` is composed by glueing together code from +//! +//! - [Hardware-specific Board Support Packages] (`BSPs`). +//! - [Architecture-specific code]. +//! - HW- and architecture-agnostic `kernel` code. +//! +//! using the [`kernel::interface`] traits. +//! +//! [Hardware-specific Board Support Packages]: bsp/index.html +//! [Architecture-specific code]: arch/index.html +//! [`kernel::interface`]: interface/index.html + +#![feature(format_args_nl)] +#![feature(panic_info_message)] +#![feature(trait_alias)] +#![no_main] +#![no_std] + +// Conditionally includes the selected `architecture` code, which provides the `_start()` function, +// the first function to run. +mod arch; + +// `_start()` then calls `runtime_init::init()`, which on completion, jumps to `kernel_init()`. +mod runtime_init; + +// Conditionally includes the selected `BSP` code. +mod bsp; + +mod interface; +mod panic_wait; +mod print; + +/// Early init code. +/// +/// Concerned with with initializing `BSP` and `arch` parts. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +/// - The init calls in this function must appear in the correct order. +unsafe fn kernel_init() -> ! { + for i in bsp::device_drivers().iter() { + if let Err(()) = i.init() { + // This message will only be readable if, at the time of failure, the return value of + // `bsp::console()` is already in functioning state. + panic!("Error loading driver: {}", i.compatible()) + } + } + + bsp::post_driver_init(); + + // Transition from unsafe to safe. + kernel_main() +} + +/// The main function running after the early init. +fn kernel_main() -> ! { + use core::time::Duration; + use interface::{console::All, time::Timer}; + + println!("Booting on: {}", bsp::board_name()); + + println!( + "Current privilege level: {}", + arch::state::current_privilege_level() + ); + println!("Exception handling state:"); + arch::state::print_exception_state(); + + println!( + "Architectural timer resolution: {} ns", + arch::timer().resolution().as_nanos() + ); + + println!("Drivers loaded:"); + for (i, driver) in bsp::device_drivers().iter().enumerate() { + println!(" {}. {}", i + 1, driver.compatible()); + } + + println!("Timer test, spinning for 1 second"); + arch::timer().spin_for(Duration::from_secs(1)); + + println!("Echoing input now"); + loop { + let c = bsp::console().read_char(); + bsp::console().write_char(c); + } +} diff --git a/10_privilege_level/src/panic_wait.rs b/10_privilege_level/src/panic_wait.rs new file mode 100644 index 00000000..5e6d3fa5 --- /dev/null +++ b/10_privilege_level/src/panic_wait.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! A panic handler that infinitely waits. + +use crate::{arch, println}; +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + if let Some(args) = info.message() { + println!("Kernel panic: {}", args); + } else { + println!("Kernel panic!"); + } + + arch::wait_forever() +} diff --git a/10_privilege_level/src/print.rs b/10_privilege_level/src/print.rs new file mode 100644 index 00000000..86ab59b3 --- /dev/null +++ b/10_privilege_level/src/print.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Printing facilities. + +use crate::{bsp, interface}; +use core::fmt; + +/// Prints without a newline. +/// +/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// Prints with a newline. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +/// Prints a warning, with newline. +#[macro_export] +macro_rules! warn { + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +pub fn _print(args: fmt::Arguments) { + use interface::console::Write; + + bsp::console().write_fmt(args).unwrap(); +} diff --git a/10_privilege_level/src/runtime_init.rs b/10_privilege_level/src/runtime_init.rs new file mode 100644 index 00000000..4cd0415a --- /dev/null +++ b/10_privilege_level/src/runtime_init.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Rust runtime initialization code. + +/// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel +/// init code. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +pub unsafe fn init() -> ! { + extern "C" { + // Boundaries of the .bss section, provided by the linker script. + static mut __bss_start: u64; + static mut __bss_end: u64; + } + + // Zero out the .bss section. + r0::zero_bss(&mut __bss_start, &mut __bss_end); + + crate::kernel_init() +}