diff --git a/04_zero_overhead_abstraction/Cargo.lock b/04_zero_overhead_abstraction/Cargo.lock new file mode 100644 index 00000000..d9d11c3f --- /dev/null +++ b/04_zero_overhead_abstraction/Cargo.lock @@ -0,0 +1,41 @@ +# 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)", +] + +[[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/04_zero_overhead_abstraction/Cargo.toml b/04_zero_overhead_abstraction/Cargo.toml new file mode 100644 index 00000000..85b3a052 --- /dev/null +++ b/04_zero_overhead_abstraction/Cargo.toml @@ -0,0 +1,17 @@ +[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 = [] + +[dependencies] +r0 = "0.2.2" +cortex-a = "2.7.0" diff --git a/04_zero_overhead_abstraction/Makefile b/04_zero_overhead_abstraction/Makefile new file mode 100644 index 00000000..b3693e18 --- /dev/null +++ b/04_zero_overhead_abstraction/Makefile @@ -0,0 +1,76 @@ +## SPDX-License-Identifier: MIT +## +## Copyright (c) 2018-2019 Andre Richter + +# Default to the RPi3 +ifndef BSP + BSP = bsp_rpi3 +endif + +# BSP-specific arguments +ifeq ($(BSP),bsp_rpi3) + TARGET = aarch64-unknown-none + OUTPUT = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = raspi3 + QEMU_MISC_ARGS = -serial null -serial stdio + LINKER_FILE = src/bsp/rpi3/link.ld + RUSTC_MISC_ARGS = -C target-feature=-fp-armv8 -C target-cpu=cortex-a53 +endif + +SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) $(wildcard **/*.ld) + +XRUSTC_CMD = cargo xrustc \ + --target=$(TARGET) \ + --features $(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_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -kernel $(OUTPUT) + +.PHONY: all qemu 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) --document-private-items + xdg-open target/$(TARGET)/doc/kernel/index.html + +qemu: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_QEMU) $(QEMU_MISC_ARGS) + +clippy: + cargo xclippy --target=$(TARGET) --features $(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/04_zero_overhead_abstraction/README.md b/04_zero_overhead_abstraction/README.md new file mode 100644 index 00000000..b5dd1077 --- /dev/null +++ b/04_zero_overhead_abstraction/README.md @@ -0,0 +1,147 @@ +# Tutorial 04 - Zero Overhead Abstraction + +## tl;dr + +All hand-written assembly is replaced by Rust code from the [cortex-a] crate, +which provides zero-overhead abstractions and wraps the `unsafe` parts. + +[cortex-a]: https://github.com/rust-embedded/cortex-a + +## Diff to previous +```diff + +diff -uNr 03_hacky_hello_world/Cargo.toml 04_zero_overhead_abstraction/Cargo.toml +--- 03_hacky_hello_world/Cargo.toml 2019-09-25 14:41:51.089487788 +0200 ++++ 04_zero_overhead_abstraction/Cargo.toml 2019-09-25 13:59:33.588482692 +0200 +@@ -14,3 +14,4 @@ + + [dependencies] + r0 = "0.2.2" ++cortex-a = "2.7.0" + +diff -uNr 03_hacky_hello_world/src/bsp/rpi3/panic_wait.rs 04_zero_overhead_abstraction/src/bsp/rpi3/panic_wait.rs +--- 03_hacky_hello_world/src/bsp/rpi3/panic_wait.rs 2019-09-25 14:41:51.093487759 +0200 ++++ 04_zero_overhead_abstraction/src/bsp/rpi3/panic_wait.rs 2019-09-25 15:26:48.988205284 +0200 +@@ -6,6 +6,7 @@ + + use crate::println; + use core::panic::PanicInfo; ++use cortex_a::asm; + + #[panic_handler] + fn panic(info: &PanicInfo) -> ! { +@@ -15,9 +16,7 @@ + println!("Kernel panic!"); + } + +- unsafe { +- loop { +- asm!("wfe" :::: "volatile") +- } ++ loop { ++ asm::wfe(); + } + } + +diff -uNr 03_hacky_hello_world/src/bsp/rpi3/start.S 04_zero_overhead_abstraction/src/bsp/rpi3/start.S +--- 03_hacky_hello_world/src/bsp/rpi3/start.S 2019-09-25 15:07:28.593140386 +0200 ++++ 04_zero_overhead_abstraction/src/bsp/rpi3/start.S 1970-01-01 01:00:00.000000000 +0100 +@@ -1,21 +0,0 @@ +-// SPDX-License-Identifier: MIT +-// +-// Copyright (c) 2018-2019 Andre Richter +- +-.section ".text._start" +- +-.global _start +- +-_start: +- mrs x1, mpidr_el1 // Read Multiprocessor Affinity Register +- and x1, x1, #3 // Clear all bits except [1:0], which hold core id +- cbz x1, 2f // Jump to label 2 if we are core 0 +-1: wfe // Wait for event +- b 1b // In case an event happend, jump back to 1 +-2: // If we are here, we are core0 +- ldr x1, =_start // Load address of function "_start()" +- mov sp, x1 // Set start of stack to before our code, aka first +- // address before "_start()" +- bl init // Jump to the "init()" kernel function +- b 1b // We should never reach here. But just in case, +- // park this core aswell + +diff -uNr 03_hacky_hello_world/src/bsp/rpi3.rs 04_zero_overhead_abstraction/src/bsp/rpi3.rs +--- 03_hacky_hello_world/src/bsp/rpi3.rs 2019-09-25 14:41:51.093487759 +0200 ++++ 04_zero_overhead_abstraction/src/bsp/rpi3.rs 2019-09-25 15:19:14.474175689 +0200 +@@ -8,8 +8,34 @@ + + use crate::interface::console; + use core::fmt; ++use cortex_a::{asm, regs::*}; + +-global_asm!(include_str!("rpi3/start.S")); ++/// 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() -> ! { ++ use crate::runtime_init; ++ ++ const CORE_0: u64 = 0; ++ const CORE_MASK: u64 = 0x3; ++ const STACK_START: u64 = 0x80_000; ++ ++ if CORE_0 == MPIDR_EL1.get() & CORE_MASK { ++ SP.set(STACK_START); ++ runtime_init::init() ++ } else { ++ // if not core0, infinitely wait for events ++ loop { ++ asm::wfe(); ++ } ++ } ++} + + /// A mystical, magical device for generating QEMU output out of the void. + struct QEMUOutput; + +diff -uNr 03_hacky_hello_world/src/main.rs 04_zero_overhead_abstraction/src/main.rs +--- 03_hacky_hello_world/src/main.rs 2019-09-25 14:41:52.341478676 +0200 ++++ 04_zero_overhead_abstraction/src/main.rs 2019-09-25 15:22:45.433268740 +0200 +@@ -13,9 +13,7 @@ + //! + //! [`kernel::interface`]: interface/index.html + +-#![feature(asm)] + #![feature(format_args_nl)] +-#![feature(global_asm)] + #![feature(panic_info_message)] + #![no_main] + #![no_std] +@@ -33,7 +31,7 @@ + + /// Entrypoint of the `kernel`. + fn kernel_entry() -> ! { +- println!("Hello from Rust!"); ++ println!("Hello from pure Rust!"); + + panic!("Stopping here.") + } + +diff -uNr 03_hacky_hello_world/src/runtime_init.rs 04_zero_overhead_abstraction/src/runtime_init.rs +--- 03_hacky_hello_world/src/runtime_init.rs 2019-09-25 14:41:51.093487759 +0200 ++++ 04_zero_overhead_abstraction/src/runtime_init.rs 2019-09-25 14:00:32.560262587 +0200 +@@ -13,7 +13,7 @@ + /// + /// - Only a single core must be active and running this function. + #[no_mangle] +-pub unsafe extern "C" fn init() -> ! { ++pub unsafe fn init() -> ! { + extern "C" { + // Boundaries of the .bss section, provided by the linker script + static mut __bss_start: u64; +``` diff --git a/04_zero_overhead_abstraction/kernel b/04_zero_overhead_abstraction/kernel new file mode 100755 index 00000000..6bb49b1d Binary files /dev/null and b/04_zero_overhead_abstraction/kernel differ diff --git a/04_zero_overhead_abstraction/kernel8.img b/04_zero_overhead_abstraction/kernel8.img new file mode 100755 index 00000000..8198c546 Binary files /dev/null and b/04_zero_overhead_abstraction/kernel8.img differ diff --git a/04_zero_overhead_abstraction/src/bsp.rs b/04_zero_overhead_abstraction/src/bsp.rs new file mode 100644 index 00000000..7932435d --- /dev/null +++ b/04_zero_overhead_abstraction/src/bsp.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of Board Support Packages. + +#[cfg(feature = "bsp_rpi3")] +pub mod rpi3; + +#[cfg(feature = "bsp_rpi3")] +pub use rpi3::*; diff --git a/04_zero_overhead_abstraction/src/bsp/rpi3.rs b/04_zero_overhead_abstraction/src/bsp/rpi3.rs new file mode 100644 index 00000000..d9824e9f --- /dev/null +++ b/04_zero_overhead_abstraction/src/bsp/rpi3.rs @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board Support Package for the Raspberry Pi 3. + +mod panic_wait; + +use crate::interface::console; +use core::fmt; +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() -> ! { + use crate::runtime_init; + + const CORE_0: u64 = 0; + const CORE_MASK: u64 = 0x3; + const STACK_START: u64 = 0x80_000; + + if CORE_0 == MPIDR_EL1.get() & CORE_MASK { + SP.set(STACK_START); + runtime_init::init() + } else { + // if not core0, infinitely wait for events + loop { + asm::wfe(); + } + } +} + +/// A mystical, magical device for generating QEMU output out of the void. +struct QEMUOutput; + +/// Implementing `console::Write` enables usage of the `format_args!` macros, +/// which in turn are used to implement the `kernel`'s `print!` and `println!` +/// macros. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl console::Write for QEMUOutput { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + unsafe { + core::ptr::write_volatile(0x3F21_5040 as *mut u8, c as u8); + } + } + + Ok(()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Implementation of the kernel's BSP calls +//////////////////////////////////////////////////////////////////////////////// + +/// Returns a ready-to-use `console::Write` implementation. +pub fn console() -> impl console::Write { + QEMUOutput {} +} diff --git a/04_zero_overhead_abstraction/src/bsp/rpi3/link.ld b/04_zero_overhead_abstraction/src/bsp/rpi3/link.ld new file mode 100644 index 00000000..235a0a0c --- /dev/null +++ b/04_zero_overhead_abstraction/src/bsp/rpi3/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 RPi3 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/04_zero_overhead_abstraction/src/bsp/rpi3/panic_wait.rs b/04_zero_overhead_abstraction/src/bsp/rpi3/panic_wait.rs new file mode 100644 index 00000000..06c8b7f4 --- /dev/null +++ b/04_zero_overhead_abstraction/src/bsp/rpi3/panic_wait.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! A panic handler that infinitely waits. + +use crate::println; +use core::panic::PanicInfo; +use cortex_a::asm; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + if let Some(args) = info.message() { + println!("{}", args); + } else { + println!("Kernel panic!"); + } + + loop { + asm::wfe(); + } +} diff --git a/04_zero_overhead_abstraction/src/interface.rs b/04_zero_overhead_abstraction/src/interface.rs new file mode 100644 index 00000000..56645366 --- /dev/null +++ b/04_zero_overhead_abstraction/src/interface.rs @@ -0,0 +1,36 @@ +// 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 { + /// Console write functions. + /// + /// `core::fmt::Write` is exactly what we need. Re-export it here because + /// implementing `console::Write` gives a better hint to the reader about + /// the intention. + pub use core::fmt::Write; + + /// Console read functions. + pub trait Read { + fn read_char(&mut self) -> char { + ' ' + } + } +} diff --git a/04_zero_overhead_abstraction/src/main.rs b/04_zero_overhead_abstraction/src/main.rs new file mode 100644 index 00000000..70c6a788 --- /dev/null +++ b/04_zero_overhead_abstraction/src/main.rs @@ -0,0 +1,37 @@ +// 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 hardware-specific Board Support +//! Package (`BSP`) code and hardware-agnostic `kernel` code through the +//! [`kernel::interface`] traits. +//! +//! [`kernel::interface`]: interface/index.html + +#![feature(format_args_nl)] +#![feature(panic_info_message)] +#![no_main] +#![no_std] + +// This module conditionally includes the correct `BSP` which provides the +// `_start()` function, the first function to run. +mod bsp; + +// Afterwards, `BSP`'s early init code calls `runtime_init::init()` of this +// module, which on completion, jumps to `kernel_entry()`. +mod runtime_init; + +mod interface; +mod print; + +/// Entrypoint of the `kernel`. +fn kernel_entry() -> ! { + println!("Hello from pure Rust!"); + + panic!("Stopping here.") +} diff --git a/04_zero_overhead_abstraction/src/print.rs b/04_zero_overhead_abstraction/src/print.rs new file mode 100644 index 00000000..9a7dc63c --- /dev/null +++ b/04_zero_overhead_abstraction/src/print.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Printing facilities. + +use crate::bsp; +use crate::interface::console::Write; +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. +/// +/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ({ + $crate::print::_print(format_args_nl!($($arg)*)); + }) +} + +pub fn _print(args: fmt::Arguments) { + bsp::console().write_fmt(args).unwrap(); +} diff --git a/04_zero_overhead_abstraction/src/runtime_init.rs b/04_zero_overhead_abstraction/src/runtime_init.rs new file mode 100644 index 00000000..ebe2ec9b --- /dev/null +++ b/04_zero_overhead_abstraction/src/runtime_init.rs @@ -0,0 +1,27 @@ +// 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 calls the kernel entry. +/// +/// Called from BSP code. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +#[no_mangle] +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_entry() +}