diff --git a/05_safe_globals/.rustfmt.toml b/05_safe_globals/.rustfmt.toml new file mode 100644 index 00000000..f255bc13 --- /dev/null +++ b/05_safe_globals/.rustfmt.toml @@ -0,0 +1,3 @@ +newline_style = "Unix" +edition = "2018" +format_code_in_doc_comments = true diff --git a/05_safe_globals/.vscode/settings.json b/05_safe_globals/.vscode/settings.json new file mode 100644 index 00000000..5fc0875d --- /dev/null +++ b/05_safe_globals/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust.features": ["bsp_rpi3"], + "rust.all_targets": false, +} diff --git a/05_safe_globals/Cargo.lock b/05_safe_globals/Cargo.lock new file mode 100644 index 00000000..d9d11c3f --- /dev/null +++ b/05_safe_globals/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/05_safe_globals/Cargo.toml b/05_safe_globals/Cargo.toml new file mode 100644 index 00000000..a0734bd3 --- /dev/null +++ b/05_safe_globals/Cargo.toml @@ -0,0 +1,19 @@ +[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"] + +[dependencies] +r0 = "0.2.*" + +# Optional dependencies +cortex-a = { version = "2.*", optional = true } diff --git a/05_safe_globals/Makefile b/05_safe_globals/Makefile new file mode 100644 index 00000000..a811b598 --- /dev/null +++ b/05_safe_globals/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-softfloat + 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-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/05_safe_globals/README.md b/05_safe_globals/README.md new file mode 100644 index 00000000..6d131590 --- /dev/null +++ b/05_safe_globals/README.md @@ -0,0 +1,337 @@ +# Tutorial 05 - Safe Globals + +## A slightly longer tl;dr + +When we introduced the globally usable `print!` macros in [tutorial 03], we +cheated a bit. Calling `core::fmt`'s `write_fmt()` function, which takes an +`&mut self`, was only working because on each call, a new instance of +`QEMUOutput` was created. + +If we would want to preserve some state, e.g. statistics about the number of +characters written, we need to make a single global instance of `QEMUOutput` (in +Rust, using the `static` keyword). + +A `static QEMU_OUTPUT`, however, would not allow to call functions taking `&mut +self`. For that, we would need a `static mut`, but calling functions that mutate +state on `static mut`s is unsafe. The Rust compiler's reasoning for this is that +it can then not prevent anymore that multiple cores/threads are mutating the +data concurrently (it is a global, so everyone can reference it from anywhere. +The borrow checker can't help here). + +The solution to this problem is to wrap the global into a synchronization +primitive. In our case, a variant of a *MUTual EXclusion* primivite. `Mutex` is +introduced as a trait in `interfaces.rs`, and implemented by the name of +`NullLock` in `sync.rs` in the `bsp` folder. For teaching purposes, to make the +code lean, it leaves out the actual platform-specific logic for protection +against concurrent access, since we don't need it as long as the kernel only +exeuts on a single core with interrupts disabled. + +Instead, it focuses on showcasing the core concept of [interior mutability]. +Make sure to read up on it. I also recommend to read this article about an +[accurate mental model for Rust's reference types]. + +If you want to compare the `NullLock` to some real-world implementations, you +can check out implemntations in the [spin crate] or the [parking lot crate]. + +[tutorial 03]: ../03_hacky_hello_world +[interior mutability]: https://doc.rust-lang.org/std/cell/index.html +[accurate mental model for Rust's reference types]: https://docs.rs/dtolnay/0.0.6/dtolnay/macro._02__reference_types.html +[spin crate]: https://github.com/mvdnes/spin-rs +[parking lot crate]: https://github.com/Amanieu/parking_lot + +## Diff to previous +```diff + +diff -uNr 04_zero_overhead_abstraction/src/bsp/rpi3/sync.rs 05_safe_globals/src/bsp/rpi3/sync.rs +--- 04_zero_overhead_abstraction/src/bsp/rpi3/sync.rs ++++ 05_safe_globals/src/bsp/rpi3/sync.rs +@@ -0,0 +1,47 @@ ++// SPDX-License-Identifier: MIT ++// ++// Copyright (c) 2018-2019 Andre Richter ++ ++//! Board-specific 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 -uNr 04_zero_overhead_abstraction/src/bsp/rpi3.rs 05_safe_globals/src/bsp/rpi3.rs +--- 04_zero_overhead_abstraction/src/bsp/rpi3.rs ++++ 05_safe_globals/src/bsp/rpi3.rs +@@ -5,10 +5,12 @@ + //! Board Support Package for the Raspberry Pi 3. + + mod panic_wait; ++mod sync; + + use crate::interface; + use core::fmt; + use cortex_a::{asm, regs::*}; ++use sync::NullLock; + + /// The entry of the `kernel` binary. + /// +@@ -38,28 +40,100 @@ + } + + /// A mystical, magical device for generating QEMU output out of the void. +-struct QEMUOutput; ++/// ++/// The mutex protected part. ++struct QEMUOutputInner { ++ chars_written: usize, ++} + +-/// Implementing `console::Write` enables usage of the `format_args!` macros, ++impl QEMUOutputInner { ++ const fn new() -> QEMUOutputInner { ++ QEMUOutputInner { chars_written: 0 } ++ } ++ ++ /// Send a character. ++ fn write_char(&mut self, c: char) { ++ unsafe { ++ core::ptr::write_volatile(0x3F21_5040 as *mut u8, c as u8); ++ } ++ } ++} ++ ++/// 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. ++/// 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 interface::console::Write for QEMUOutput { ++impl fmt::Write for QEMUOutputInner { + 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); ++ // Convert newline to carrige return + newline. ++ if c == ' +' { ++ self.write_char(' ') + } ++ ++ self.write_char(c); + } + ++ self.chars_written += s.len(); ++ + Ok(()) + } + } + + //////////////////////////////////////////////////////////////////////////////// ++// OS interface implementations ++//////////////////////////////////////////////////////////////////////////////// ++ ++/// The main struct. ++pub struct QEMUOutput { ++ inner: NullLock, ++} ++ ++impl QEMUOutput { ++ pub const fn new() -> QEMUOutput { ++ QEMUOutput { ++ inner: NullLock::new(QEMUOutputInner::new()), ++ } ++ } ++} ++ ++/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded ++/// by a Mutex to serialize access. ++impl interface::console::Write for QEMUOutput { ++ fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { ++ use interface::sync::Mutex; ++ ++ // Fully qualified syntax for the call to ++ // `core::fmt::Write::write:fmt()` to increase readability. ++ let mut r = &self.inner; ++ r.lock(|i| fmt::Write::write_fmt(i, args)) ++ } ++} ++ ++impl interface::console::Read for QEMUOutput {} ++ ++impl interface::console::Statistics for QEMUOutput { ++ fn chars_written(&self) -> usize { ++ use interface::sync::Mutex; ++ ++ let mut r = &self.inner; ++ r.lock(|i| i.chars_written) ++ } ++} ++ ++//////////////////////////////////////////////////////////////////////////////// ++// Global instances ++//////////////////////////////////////////////////////////////////////////////// ++ ++static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new(); ++ ++//////////////////////////////////////////////////////////////////////////////// + // Implementation of the kernel's BSP calls + //////////////////////////////////////////////////////////////////////////////// + +@@ -70,7 +144,7 @@ + } + } + +-/// Returns a ready-to-use `console::Write` implementation. +-pub fn console() -> impl interface::console::Write { +- QEMUOutput {} ++/// Return a reference to a `console::All` implementation. ++pub fn console() -> &'static impl interface::console::All { ++ &QEMU_OUTPUT + } + +diff -uNr 04_zero_overhead_abstraction/src/interface.rs 05_safe_globals/src/interface.rs +--- 04_zero_overhead_abstraction/src/interface.rs ++++ 05_safe_globals/src/interface.rs +@@ -20,17 +20,68 @@ + + /// System console operations. + pub mod console { ++ use core::fmt; ++ + /// Console write functions. +- /// +- /// `core::fmt::Write` is exactly what we need for now. Re-export it here +- /// because implementing `console::Write` gives a better hint to the reader +- /// about the intention. +- pub use core::fmt::Write; ++ pub trait Write { ++ fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; ++ } + + /// Console read functions. + pub trait Read { +- fn read_char(&mut self) -> char { ++ fn read_char(&self) -> char { + ' ' + } + } ++ ++ /// 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; ++ } + } + +diff -uNr 04_zero_overhead_abstraction/src/main.rs 05_safe_globals/src/main.rs +--- 04_zero_overhead_abstraction/src/main.rs ++++ 05_safe_globals/src/main.rs +@@ -15,6 +15,7 @@ + + #![feature(format_args_nl)] + #![feature(panic_info_message)] ++#![feature(trait_alias)] + #![no_main] + #![no_std] + +@@ -31,8 +32,12 @@ + + /// Entrypoint of the `kernel`. + fn kernel_entry() -> ! { ++ use interface::console::Statistics; ++ + println!("[0] Hello from pure Rust!"); + +- println!("[1] Stopping here."); ++ println!("[1] Chars written: {}", bsp::console().chars_written()); ++ ++ println!("[2] Stopping here."); + bsp::wait_forever() + } +``` diff --git a/05_safe_globals/kernel b/05_safe_globals/kernel new file mode 100755 index 00000000..3b81d226 Binary files /dev/null and b/05_safe_globals/kernel differ diff --git a/05_safe_globals/kernel8.img b/05_safe_globals/kernel8.img new file mode 100755 index 00000000..e9e9964e Binary files /dev/null and b/05_safe_globals/kernel8.img differ diff --git a/05_safe_globals/src/bsp.rs b/05_safe_globals/src/bsp.rs new file mode 100644 index 00000000..7932435d --- /dev/null +++ b/05_safe_globals/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/05_safe_globals/src/bsp/rpi3.rs b/05_safe_globals/src/bsp/rpi3.rs new file mode 100644 index 00000000..24d42d80 --- /dev/null +++ b/05_safe_globals/src/bsp/rpi3.rs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board Support Package for the Raspberry Pi 3. + +mod panic_wait; +mod sync; + +use crate::interface; +use core::fmt; +use cortex_a::{asm, regs::*}; +use sync::NullLock; + +/// 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. +/// +/// The mutex protected part. +struct QEMUOutputInner { + chars_written: usize, +} + +impl QEMUOutputInner { + const fn new() -> QEMUOutputInner { + QEMUOutputInner { chars_written: 0 } + } + + /// Send a character. + fn write_char(&mut self, c: char) { + unsafe { + core::ptr::write_volatile(0x3F21_5040 as *mut u8, c as u8); + } + } +} + +/// 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 QEMUOutputInner { + 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(()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// OS interface implementations +//////////////////////////////////////////////////////////////////////////////// + +/// The main struct. +pub struct QEMUOutput { + inner: NullLock, +} + +impl QEMUOutput { + pub const fn new() -> QEMUOutput { + QEMUOutput { + inner: NullLock::new(QEMUOutputInner::new()), + } + } +} + +/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded +/// by a Mutex to serialize access. +impl interface::console::Write for QEMUOutput { + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + use interface::sync::Mutex; + + // Fully qualified syntax for the call to + // `core::fmt::Write::write:fmt()` to increase readability. + let mut r = &self.inner; + r.lock(|i| fmt::Write::write_fmt(i, args)) + } +} + +impl interface::console::Read for QEMUOutput {} + +impl interface::console::Statistics for QEMUOutput { + fn chars_written(&self) -> usize { + use interface::sync::Mutex; + + let mut r = &self.inner; + r.lock(|i| i.chars_written) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Global instances +//////////////////////////////////////////////////////////////////////////////// + +static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new(); + +//////////////////////////////////////////////////////////////////////////////// +// Implementation of the kernel's BSP calls +//////////////////////////////////////////////////////////////////////////////// + +/// Park execution on the calling CPU core. +pub fn wait_forever() -> ! { + loop { + asm::wfe() + } +} + +/// Return a reference to a `console::All` implementation. +pub fn console() -> &'static impl interface::console::All { + &QEMU_OUTPUT +} diff --git a/05_safe_globals/src/bsp/rpi3/link.ld b/05_safe_globals/src/bsp/rpi3/link.ld new file mode 100644 index 00000000..235a0a0c --- /dev/null +++ b/05_safe_globals/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/05_safe_globals/src/bsp/rpi3/panic_wait.rs b/05_safe_globals/src/bsp/rpi3/panic_wait.rs new file mode 100644 index 00000000..05581c29 --- /dev/null +++ b/05_safe_globals/src/bsp/rpi3/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::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!"); + } + + super::wait_forever() +} diff --git a/05_safe_globals/src/bsp/rpi3/sync.rs b/05_safe_globals/src/bsp/rpi3/sync.rs new file mode 100644 index 00000000..9d737e74 --- /dev/null +++ b/05_safe_globals/src/bsp/rpi3/sync.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board-specific 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/05_safe_globals/src/interface.rs b/05_safe_globals/src/interface.rs new file mode 100644 index 00000000..4878e6f3 --- /dev/null +++ b/05_safe_globals/src/interface.rs @@ -0,0 +1,87 @@ +// 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_fmt(&self, args: fmt::Arguments) -> fmt::Result; + } + + /// Console read functions. + pub trait Read { + fn read_char(&self) -> char { + ' ' + } + } + + /// 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; + } +} diff --git a/05_safe_globals/src/main.rs b/05_safe_globals/src/main.rs new file mode 100644 index 00000000..1c0564e0 --- /dev/null +++ b/05_safe_globals/src/main.rs @@ -0,0 +1,43 @@ +// 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)] +#![feature(trait_alias)] +#![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() -> ! { + use interface::console::Statistics; + + println!("[0] Hello from pure Rust!"); + + println!("[1] Chars written: {}", bsp::console().chars_written()); + + println!("[2] Stopping here."); + bsp::wait_forever() +} diff --git a/05_safe_globals/src/print.rs b/05_safe_globals/src/print.rs new file mode 100644 index 00000000..c85d37e7 --- /dev/null +++ b/05_safe_globals/src/print.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Printing facilities. + +use crate::bsp; +use crate::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. +/// +/// 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) { + use interface::console::Write; + + bsp::console().write_fmt(args).unwrap(); +} diff --git a/05_safe_globals/src/runtime_init.rs b/05_safe_globals/src/runtime_init.rs new file mode 100644 index 00000000..baac32b0 --- /dev/null +++ b/05_safe_globals/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() +}