Refactor tutorial 05

pull/51/head
Andre Richter 4 years ago
parent 91575eaa2c
commit 523af645cd
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -14,7 +14,7 @@ ifeq ($(BSP),rpi3)
QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/rpi/link.ld
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat
@ -22,7 +22,7 @@ else ifeq ($(BSP),rpi4)
# QEMU_BINARY = qemu-system-aarch64
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/rpi/link.ld
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif
@ -60,8 +60,7 @@ $(OUTPUT): $(CARGO_OUTPUT)
$(OBJCOPY_CMD) $< $(OUTPUT)
doc:
cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items
xdg-open target/$(TARGET)/doc/kernel/index.html
cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items --open
ifeq ($(QEMU_MACHINE_TYPE),)
qemu:

@ -1,37 +1,40 @@
# Tutorial 05 - Safe Globals
## A slightly longer tl;dr
## 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.
A pseudo-lock is introduced; It is a first showcase of OS synchronization primitives and enables
safe access to a global data structure.
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).
## Mutable globals in Rust
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).
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.
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 `arch` folder. For teaching purposes, to make the
code lean, it leaves out the actual architecture-specific logic for protection
against concurrent access, since we don't need it as long as the kernel only
executes on a single core with interrupts disabled.
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).
Instead, it focuses on showcasing the Rust 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].
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).
If you want to compare the `NullLock` to some real-world mutex implementations,
you can check out implemntations in the [spin crate] or the [parking lot crate].
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 `synchronization.rs`,
and implemented by the `NullLock` in the same file. In order to make the code lean for teaching
purposes, it leaves out the actual architecture-specific logic for protection against concurrent
access, since we don't need it as long as the kernel only executes on a single core with interrupts
disabled. That is also why it is implemented in the same file as the interface itself. In later
tutorials, an implementation might move to the `_arch` once it pulls in arch-specific code that can
not be further abstracted.
The `NullLock` focuses on showcasing the Rust 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 mutex 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
@ -39,321 +42,14 @@ you can check out implemntations in the [spin crate] or the [parking lot crate].
[spin crate]: https://github.com/mvdnes/spin-rs
[parking lot crate]: https://github.com/Amanieu/parking_lot
### Test it
## Test it
```console
» make qemu
$ make qemu
[...]
[0] Hello from pure Rust!
[1] Chars written: 26
[1] Chars written: 27
[2] Stopping here.
```
## Diff to previous
```diff
diff -uNr 04_zero_overhead_abstraction/src/arch/aarch64/sync.rs 05_safe_globals/src/arch/aarch64/sync.rs
--- 04_zero_overhead_abstraction/src/arch/aarch64/sync.rs
+++ 05_safe_globals/src/arch/aarch64/sync.rs
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Synchronization primitives.
+
+use crate::interface;
+use core::cell::UnsafeCell;
+
+//--------------------------------------------------------------------------------------------------
+// Arch-public
+//--------------------------------------------------------------------------------------------------
+
+/// 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<T: ?Sized> {
+ data: UnsafeCell<T>,
+}
+
+unsafe impl<T: ?Sized + Send> Send for NullLock<T> {}
+unsafe impl<T: ?Sized + Send> Sync for NullLock<T> {}
+
+impl<T> NullLock<T> {
+ /// Wraps `data` into a new `NullLock`.
+ pub const fn new(data: T) -> NullLock<T> {
+ NullLock {
+ data: UnsafeCell::new(data),
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+// OS interface implementations
+//--------------------------------------------------------------------------------------------------
+
+impl<T> interface::sync::Mutex for &NullLock<T> {
+ type Data = T;
+
+ fn lock<R>(&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/arch/aarch64.rs 05_safe_globals/src/arch/aarch64.rs
--- 04_zero_overhead_abstraction/src/arch/aarch64.rs
+++ 05_safe_globals/src/arch/aarch64.rs
@@ -4,6 +4,8 @@
//! AArch64.
+pub mod sync;
+
use crate::bsp;
use cortex_a::{asm, regs::*};
diff -uNr 04_zero_overhead_abstraction/src/bsp/rpi.rs 05_safe_globals/src/bsp/rpi.rs
--- 04_zero_overhead_abstraction/src/bsp/rpi.rs
+++ 05_safe_globals/src/bsp/rpi.rs
@@ -4,7 +4,7 @@
//! Board Support Package for the Raspberry Pi.
-use crate::interface;
+use crate::{arch::sync::NullLock, interface};
use core::fmt;
/// Used by `arch` code to find the early boot core.
@@ -14,31 +14,107 @@
pub const BOOT_CORE_STACK_START: u64 = 0x80_000;
/// A mystical, magical device for generating QEMU output out of the void.
-struct QEMUOutput;
+///
+/// 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(0x3F20_1000 as *mut u8, c as u8);
+ }
+ }
+}
-/// Implementing `console::Write` enables usage of the `format_args!` macros, which in turn are used
-/// to implement the `kernel`'s `print!` and `println!` macros.
+/// 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 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(0x3F20_1000 as *mut u8, c as u8);
+ // 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 main struct.
+pub struct QEMUOutput {
+ inner: NullLock<QEMUOutputInner>,
+}
+
+impl QEMUOutput {
+ pub const fn new() -> QEMUOutput {
+ QEMUOutput {
+ inner: NullLock::new(QEMUOutputInner::new()),
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+// OS interface implementations
+//--------------------------------------------------------------------------------------------------
+
+/// 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(|inner| fmt::Write::write_fmt(inner, 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(|inner| inner.chars_written)
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+// Global instances
+//--------------------------------------------------------------------------------------------------
+
+static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new();
+
+//--------------------------------------------------------------------------------------------------
// Implementation of the kernel's BSP calls
//--------------------------------------------------------------------------------------------------
-/// 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,12 +20,13 @@
/// 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 {
+ /// Write a Rust format string.
+ fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
+ }
/// Console read functions.
pub trait Read {
@@ -34,4 +35,53 @@
' '
}
}
+
+ /// 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<RefCell<i32>> = 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<R>(&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
@@ -21,6 +21,7 @@
#![feature(format_args_nl)]
#![feature(panic_info_message)]
+#![feature(trait_alias)]
#![no_main]
#![no_std]
@@ -45,8 +46,12 @@
///
/// - Only a single core must be active and running this function.
unsafe fn kernel_init() -> ! {
+ 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.");
arch::wait_forever()
}
```

Binary file not shown.

Binary file not shown.

@ -2,13 +2,15 @@
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! AArch64.
//! Architectural processor code.
pub mod sync;
use crate::bsp;
use crate::{bsp, cpu};
use cortex_a::{asm, regs::*};
//--------------------------------------------------------------------------------------------------
// Boot Code
//--------------------------------------------------------------------------------------------------
/// The entry of the `kernel` binary.
///
/// The function must be named `_start`, because the linker is looking for this exact name.
@ -16,13 +18,15 @@ use cortex_a::{asm, regs::*};
/// # Safety
///
/// - Linker script must ensure to place this function at `0x80_000`.
#[naked]
#[no_mangle]
pub unsafe extern "C" fn _start() -> ! {
const CORE_MASK: u64 = 0x3;
use crate::runtime_init;
if bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK {
SP.set(bsp::BOOT_CORE_STACK_START);
crate::runtime_init::runtime_init()
// Expect the boot core to start in EL2.
if bsp::cpu::BOOT_CORE_ID == cpu::smp::core_id() {
SP.set(bsp::cpu::BOOT_CORE_STACK_START);
runtime_init::runtime_init()
} else {
// If not core0, infinitely wait for events.
wait_forever()
@ -30,10 +34,10 @@ pub unsafe extern "C" fn _start() -> ! {
}
//--------------------------------------------------------------------------------------------------
// Implementation of the kernel's architecture abstraction code
// Public Code
//--------------------------------------------------------------------------------------------------
/// Pause execution on the calling CPU core.
/// Pause execution on the core.
#[inline(always)]
pub fn wait_forever() -> ! {
loop {

@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Architectural symmetric multiprocessing.
use cortex_a::regs::*;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return the executing core's id.
#[inline(always)]
pub fn core_id<T>() -> T
where
T: From<u8>,
{
const CORE_MASK: u64 = 0b11;
T::from((MPIDR_EL1.get() & CORE_MASK) as u8)
}

@ -1,11 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! 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::*;

@ -1,53 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Synchronization primitives.
use crate::interface;
use core::cell::UnsafeCell;
//--------------------------------------------------------------------------------------------------
// Arch-public
//--------------------------------------------------------------------------------------------------
/// 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<T: ?Sized> {
data: UnsafeCell<T>,
}
unsafe impl<T: ?Sized + Send> Send for NullLock<T> {}
unsafe impl<T: ?Sized + Send> Sync for NullLock<T> {}
impl<T> NullLock<T> {
/// Wraps `data` into a new `NullLock`.
pub const fn new(data: T) -> NullLock<T> {
NullLock {
data: UnsafeCell::new(data),
}
}
}
//--------------------------------------------------------------------------------------------------
// OS interface implementations
//--------------------------------------------------------------------------------------------------
impl<T> interface::sync::Mutex for &NullLock<T> {
type Data = T;
fn lock<R>(&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() })
}
}

@ -2,10 +2,10 @@
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Conditional exporting of Board Support Packages.
//! Conditional re-exporting of Board Support Packages.
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod rpi;
mod raspberrypi;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
pub use rpi::*;
pub use raspberrypi::*;

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Top-level BSP file for the Raspberry Pi 3 and 4.
pub mod console;
pub mod cpu;

@ -2,16 +2,14 @@
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Board Support Package for the Raspberry Pi.
//! BSP console facilities.
use crate::{arch::sync::NullLock, interface};
use crate::{console, synchronization, synchronization::NullLock};
use core::fmt;
/// Used by `arch` code to find the early boot core.
pub const BOOT_CORE_ID: u64 = 0;
/// The early boot core's stack address.
pub const BOOT_CORE_STACK_START: u64 = 0x80_000;
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// A mystical, magical device for generating QEMU output out of the void.
///
@ -20,6 +18,25 @@ struct QEMUOutputInner {
chars_written: usize,
}
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// The main struct.
pub struct QEMUOutput {
inner: NullLock<QEMUOutputInner>,
}
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new();
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl QEMUOutputInner {
const fn new() -> QEMUOutputInner {
QEMUOutputInner { chars_written: 0 }
@ -30,6 +47,8 @@ impl QEMUOutputInner {
unsafe {
core::ptr::write_volatile(0x3F20_1000 as *mut u8, c as u8);
}
self.chars_written += 1;
}
}
@ -53,22 +72,16 @@ impl fmt::Write for QEMUOutputInner {
self.write_char(c);
}
self.chars_written += s.len();
Ok(())
}
}
//--------------------------------------------------------------------------------------------------
// BSP-public
// Public Code
//--------------------------------------------------------------------------------------------------
/// The main struct.
pub struct QEMUOutput {
inner: NullLock<QEMUOutputInner>,
}
impl QEMUOutput {
/// Create a new instance.
pub const fn new() -> QEMUOutput {
QEMUOutput {
inner: NullLock::new(QEMUOutputInner::new()),
@ -76,16 +89,20 @@ impl QEMUOutput {
}
}
//--------------------------------------------------------------------------------------------------
// OS interface implementations
//--------------------------------------------------------------------------------------------------
/// Return a reference to the console.
pub fn console() -> &'static impl console::interface::All {
&QEMU_OUTPUT
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use synchronization::interface::Mutex;
/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to
/// serialize access.
impl interface::console::Write for QEMUOutput {
impl console::interface::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;
@ -93,28 +110,9 @@ impl interface::console::Write for QEMUOutput {
}
}
impl interface::console::Read for QEMUOutput {}
impl interface::console::Statistics for QEMUOutput {
impl console::interface::Statistics for QEMUOutput {
fn chars_written(&self) -> usize {
use interface::sync::Mutex;
let mut r = &self.inner;
r.lock(|inner| inner.chars_written)
}
}
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new();
//--------------------------------------------------------------------------------------------------
// Implementation of the kernel's BSP calls
//--------------------------------------------------------------------------------------------------
/// Return a reference to a `console::All` implementation.
pub fn console() -> &'static impl interface::console::All {
&QEMU_OUTPUT
}

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP Processor code.
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Used by `arch` code to find the early boot core.
pub const BOOT_CORE_ID: usize = 0;
/// The early boot core's stack address.
pub const BOOT_CORE_STACK_START: u64 = 0x80_000;

@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! System console.
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Console interfaces.
pub mod interface {
use core::fmt;
/// Console write functions.
pub trait Write {
/// Write a Rust format string.
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
}
/// Console statistics.
pub trait Statistics {
/// Return the number of characters written.
fn chars_written(&self) -> usize {
0
}
}
/// Trait alias for a full-fledged console.
pub trait All = Write + Statistics;
}

@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Processor code.
#[cfg(target_arch = "aarch64")]
#[path = "_arch/aarch64/cpu.rs"]
mod arch_cpu;
pub use arch_cpu::*;
pub mod smp;

@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Symmetric multiprocessing.
#[cfg(target_arch = "aarch64")]
#[path = "../_arch/aarch64/cpu/smp.rs"]
mod arch_cpu_smp;
pub use arch_cpu_smp::*;

@ -1,87 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! 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 {
/// Write a Rust format string.
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
}
/// Console read functions.
pub trait Read {
/// Read a single character.
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<RefCell<i32>> = 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<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
}
}

@ -5,40 +5,111 @@
// Rust embedded logo for `make doc`.
#![doc(html_logo_url = "https://git.io/JeGIp")]
//! The `kernel`
//! The `kernel` binary.
//!
//! The `kernel` is composed by glueing together code from
//! # Code organization and architecture
//!
//! - [Hardware-specific Board Support Packages] (`BSPs`).
//! - [Architecture-specific code].
//! - HW- and architecture-agnostic `kernel` code.
//! The code is divided into different *modules*, each representing a typical **subsystem** of the
//! `kernel`. Top-level module files of subsystems reside directly in the `src` folder. For example,
//! `src/memory.rs` contains code that is concerned with all things memory management.
//!
//! using the [`kernel::interface`] traits.
//! ## Visibility of processor architecture code
//!
//! [Hardware-specific Board Support Packages]: bsp/index.html
//! [Architecture-specific code]: arch/index.html
//! [`kernel::interface`]: interface/index.html
//! Some of the `kernel`'s subsystems depend on low-level code that is specific to the target
//! processor architecture. For each supported processor architecture, there exists a subfolder in
//! `src/_arch`, for example, `src/_arch/aarch64`.
//!
//! The architecture folders mirror the subsystem modules laid out in `src`. For example,
//! architectural code that belongs to the `kernel`'s memory subsystem (`src/memory.rs`) would go
//! into `src/_arch/aarch64/memory.rs`. The latter file is directly included and re-exported in
//! `src/memory.rs`, so that the architectural code parts are transparent with respect to the code's
//! module organization. That means a public function `foo()` defined in
//! `src/_arch/aarch64/memory.rs` would be reachable as `crate::memory::foo()` only.
//!
//! The `_` in `_arch` denotes that this folder is not part of the standard module hierarchy.
//! Rather, it's contents are conditionally pulled into respective files using the `#[path =
//! "_arch/xxx/yyy.rs"]` attribute.
//!
//! ## BSP code
//!
//! `BSP` stands for Board Support Package. `BSP` code is organized under `src/bsp.rs` and contains
//! target board specific definitions and functions. These are things such as the board's memory map
//! or instances of drivers for devices that are featured on the respective board.
//!
//! Just like processor architecture code, the `BSP` code's module structure tries to mirror the
//! `kernel`'s subsystem modules, but there is no transparent re-exporting this time. That means
//! whatever is provided must be called starting from the `bsp` namespace, e.g.
//! `bsp::driver::driver_manager()`.
//!
//! ## Kernel interfaces
//!
//! Both `arch` and `bsp` contain code that is conditionally compiled depending on the actual target
//! and board for which the kernel is compiled. For example, the `interrupt controller` hardware of
//! the `Raspberry Pi 3` and the `Raspberry Pi 4` is different, but we want the rest of the `kernel`
//! code to play nicely with any of the two without much hassle.
//!
//! In order to provide a clean abstraction between `arch`, `bsp` and `generic kernel code`,
//! `interface` traits are provided *whenever possible* and *where it makes sense*. They are defined
//! in the respective subsystem module and help to enforce the idiom of *program to an interface,
//! not an implementation*. For example, there will be a common IRQ handling interface which the two
//! different interrupt controller `drivers` of both Raspberrys will implement, and only export the
//! interface to the rest of the `kernel`.
//!
//! ```
//! +-------------------+
//! | Interface (Trait) |
//! | |
//! +--+-------------+--+
//! ^ ^
//! | |
//! | |
//! +----------+--+ +--+----------+
//! | kernel code | | bsp code |
//! | | | arch code |
//! +-------------+ +-------------+
//! ```
//!
//! # Summary
//!
//! For a logical `kernel` subsystem, corresponding code can be distributed over several physical
//! locations. Here is an example for the **memory** subsystem:
//!
//! - `src/memory.rs` and `src/memory/**/*`
//! - Common code that is agnostic of target processor architecture and `BSP` characteristics.
//! - Example: A function to zero a chunk of memory.
//! - Interfaces for the memory subsystem that are implemented by `arch` or `BSP` code.
//! - Example: An `MMU` interface that defines `MMU` function prototypes.
//! - `src/bsp/__board_name__/memory.rs` and `src/bsp/__board_name__/memory/**/*`
//! - `BSP` specific code.
//! - Example: The board's memory map (physical addresses of DRAM and MMIO devices).
//! - `src/_arch/__arch_name__/memory.rs` and `src/_arch/__arch_name__/memory/**/*`
//! - Processor architecture specific code.
//! - Example: Implementation of the `MMU` interface for the `__arch_name__` processor
//! architecture.
//!
//! From a namespace perspective, **memory** subsystem code lives in:
//!
//! - `crate::memory::*`
//! - `crate::bsp::memory::*`
#![feature(format_args_nl)]
#![feature(naked_functions)]
#![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;
// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls
// `runtime_init()`, which jumps to `kernel_init()`.
// `_start()` then calls `runtime_init()`, which on completion, jumps to `kernel_init()`.
mod runtime_init;
// Conditionally includes the selected `BSP` code.
mod bsp;
mod interface;
mod console;
mod cpu;
mod memory;
mod panic_wait;
mod print;
mod runtime_init;
mod synchronization;
/// Early init code.
///
@ -46,12 +117,15 @@ mod print;
///
/// - Only a single core must be active and running this function.
unsafe fn kernel_init() -> ! {
use interface::console::Statistics;
use console::interface::Statistics;
println!("[0] Hello from pure Rust!");
println!("[1] Chars written: {}", bsp::console().chars_written());
println!(
"[1] Chars written: {}",
bsp::console::console().chars_written()
);
println!("[2] Stopping here.");
arch::wait_forever()
cpu::wait_forever()
}

@ -6,6 +6,10 @@
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Zero out a memory region.
///
/// # Safety

@ -4,7 +4,7 @@
//! A panic handler that infinitely waits.
use crate::{arch, println};
use crate::{cpu, println};
use core::panic::PanicInfo;
#[panic_handler]
@ -15,5 +15,5 @@ fn panic(info: &PanicInfo) -> ! {
println!("\nKernel panic!");
}
arch::wait_forever()
cpu::wait_forever()
}

@ -4,16 +4,24 @@
//! Printing facilities.
use crate::{bsp, interface};
use crate::{bsp, console};
use core::fmt;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use interface::console::Write;
use console::interface::Write;
bsp::console().write_fmt(args).unwrap();
bsp::console::console().write_fmt(args).unwrap();
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Prints without a newline.
///
/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html

@ -7,6 +7,10 @@
use crate::memory;
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
/// Return the range spanning the .bss section.
///
/// # Safety
@ -36,6 +40,10 @@ unsafe fn zero_bss() {
memory::zero_volatile(bss_range());
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel
/// init code.
///

@ -0,0 +1,91 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Synchronization primitives.
use core::cell::UnsafeCell;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Synchronization interfaces.
pub mod interface {
/// Any object implementing this trait guarantees exclusive access to the data contained within
/// the Mutex for the duration of the provided closure.
///
/// 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<RefCell<i32>> = Mutex::new(RefCell::new(0));
///
/// fn foo() {
/// let mut r = &MUT; // Note that r is mutable
/// r.lock(|data| *data += 1);
/// }
/// ```
pub trait Mutex {
/// The type of encapsulated data.
type Data;
/// Creates a critical section and grants temporary mutable access to the encapsulated data.
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
}
}
/// A pseudo-lock for teaching purposes.
///
/// Used to introduce [interior mutability].
///
/// In contrast to a real Mutex implementation, does not protect against concurrent access from
/// other cores 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<T: ?Sized> {
data: UnsafeCell<T>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
unsafe impl<T: ?Sized> Sync for NullLock<T> {}
impl<T> NullLock<T> {
/// Wraps `data` into a new `NullLock`.
pub const fn new(data: T) -> Self {
Self {
data: UnsafeCell::new(data),
}
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
impl<T> interface::Mutex for &NullLock<T> {
type Data = T;
fn lock<R>(&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.
let data = unsafe { &mut *self.data.get() };
f(data)
}
}
Loading…
Cancel
Save