Add README diffs

pull/51/head
Andre Richter 4 years ago
parent 18a7f225e6
commit 474f58ced0
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -21,3 +21,181 @@ run.
[bss]: https://en.wikipedia.org/wiki/.bss
## Diff to previous
```diff
diff -uNr 01_wait_forever/src/_arch/aarch64/cpu.S 02_runtime_init/src/_arch/aarch64/cpu.S
--- 01_wait_forever/src/_arch/aarch64/cpu.S
+++ 02_runtime_init/src/_arch/aarch64/cpu.S
@@ -7,5 +7,15 @@
.global _start
_start:
-1: wfe // Wait for event
- b 1b // In case an event happened, jump back to 1
+ 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 happened, 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 runtime_init // Jump to the "runtime_init()" kernel function
+ b 1b // We should never reach here. But just in case,
+ // park this core aswell
diff -uNr 01_wait_forever/src/bsp/raspberrypi/link.ld 02_runtime_init/src/bsp/raspberrypi/link.ld
--- 01_wait_forever/src/bsp/raspberrypi/link.ld
+++ 02_runtime_init/src/bsp/raspberrypi/link.ld
@@ -13,5 +13,24 @@
*(.text._start) *(.text*)
}
+ .rodata :
+ {
+ *(.rodata*)
+ }
+
+ .data :
+ {
+ *(.data*)
+ }
+
+ /* Section is zeroed in u64 chunks, align start and end to 8 bytes */
+ .bss ALIGN(8):
+ {
+ __bss_start = .;
+ *(.bss*);
+ . = ALIGN(8);
+ __bss_end = .;
+ }
+
/DISCARD/ : { *(.comment*) }
}
diff -uNr 01_wait_forever/src/main.rs 02_runtime_init/src/main.rs
--- 01_wait_forever/src/main.rs
+++ 02_runtime_init/src/main.rs
@@ -97,10 +97,20 @@
#![no_main]
#![no_std]
-// `mod cpu` provides the `_start()` function, the first function to run.
+// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls
+// `runtime_init()`, which jumps to `kernel_init()`.
mod bsp;
mod cpu;
+mod memory;
mod panic_wait;
+mod runtime_init;
-// Kernel code coming next tutorial.
+/// Early init code.
+///
+/// # Safety
+///
+/// - Only a single core must be active and running this function.
+unsafe fn kernel_init() -> ! {
+ panic!()
+}
diff -uNr 01_wait_forever/src/memory.rs 02_runtime_init/src/memory.rs
--- 01_wait_forever/src/memory.rs
+++ 02_runtime_init/src/memory.rs
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Memory Management.
+
+use core::ops::Range;
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Zero out a memory region.
+///
+/// # Safety
+///
+/// - `range.start` and `range.end` must be valid.
+/// - `range.start` and `range.end` must be `T` aligned.
+pub unsafe fn zero_volatile<T>(range: Range<*mut T>)
+where
+ T: From<u8>,
+{
+ let mut ptr = range.start;
+
+ while ptr < range.end {
+ core::ptr::write_volatile(ptr, T::from(0));
+ ptr = ptr.offset(1);
+ }
+}
diff -uNr 01_wait_forever/src/runtime_init.rs 02_runtime_init/src/runtime_init.rs
--- 01_wait_forever/src/runtime_init.rs
+++ 02_runtime_init/src/runtime_init.rs
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Rust runtime initialization code.
+
+use crate::memory;
+use core::ops::Range;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+/// Return the range spanning the .bss section.
+///
+/// # Safety
+///
+/// - The symbol-provided addresses must be valid.
+/// - The symbol-provided addresses must be usize aligned.
+unsafe fn bss_range() -> Range<*mut usize> {
+ extern "C" {
+ // Boundaries of the .bss section, provided by linker script symbols.
+ static mut __bss_start: usize;
+ static mut __bss_end: usize;
+ }
+
+ Range {
+ start: &mut __bss_start,
+ end: &mut __bss_end,
+ }
+}
+
+/// Zero out the .bss section.
+///
+/// # Safety
+///
+/// - Must only be called pre `kernel_init()`.
+#[inline(always)]
+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.
+///
+/// # Safety
+///
+/// - Only a single core must be active and running this function.
+#[no_mangle]
+pub unsafe extern "C" fn runtime_init() -> ! {
+ zero_bss();
+
+ crate::kernel_init()
+}
```

@ -26,3 +26,219 @@ Kernel panic: Stopping here.
```
## Diff to previous
```diff
diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile
--- 02_runtime_init/Makefile
+++ 03_hacky_hello_world/Makefile
@@ -13,7 +13,7 @@
OUTPUT = kernel8.img
QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3
- QEMU_RELEASE_ARGS = -d in_asm -display none
+ QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4)
@@ -21,7 +21,7 @@
OUTPUT = kernel8.img
# QEMU_BINARY = qemu-system-aarch64
# QEMU_MACHINE_TYPE =
- # QEMU_RELEASE_ARGS = -d in_asm -display none
+ # QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif
diff -uNr 02_runtime_init/src/bsp/raspberrypi/console.rs 03_hacky_hello_world/src/bsp/raspberrypi/console.rs
--- 02_runtime_init/src/bsp/raspberrypi/console.rs
+++ 03_hacky_hello_world/src/bsp/raspberrypi/console.rs
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! BSP console facilities.
+
+use crate::console;
+use core::fmt;
+
+//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// A mystical, magical device for generating QEMU output out of the void.
+struct QEMUOutput;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+/// 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.
+///
+/// See [`src/print.rs`].
+///
+/// [`src/print.rs`]: ../../print/index.html
+impl fmt::Write for QEMUOutput {
+ 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);
+ }
+ }
+
+ Ok(())
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Return a reference to the console.
+pub fn console() -> impl console::interface::Write {
+ QEMUOutput {}
+}
diff -uNr 02_runtime_init/src/bsp/raspberrypi.rs 03_hacky_hello_world/src/bsp/raspberrypi.rs
--- 02_runtime_init/src/bsp/raspberrypi.rs
+++ 03_hacky_hello_world/src/bsp/raspberrypi.rs
@@ -4,4 +4,4 @@
//! Top-level BSP file for the Raspberry Pi 3 and 4.
-// Coming soon.
+pub mod console;
diff -uNr 02_runtime_init/src/console.rs 03_hacky_hello_world/src/console.rs
--- 02_runtime_init/src/console.rs
+++ 03_hacky_hello_world/src/console.rs
@@ -0,0 +1,19 @@
+// 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 {
+ /// 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;
+}
diff -uNr 02_runtime_init/src/main.rs 03_hacky_hello_world/src/main.rs
--- 02_runtime_init/src/main.rs
+++ 03_hacky_hello_world/src/main.rs
@@ -93,7 +93,9 @@
//! - `crate::bsp::memory::*`
#![feature(asm)]
+#![feature(format_args_nl)]
#![feature(global_asm)]
+#![feature(panic_info_message)]
#![no_main]
#![no_std]
@@ -101,9 +103,11 @@
// `runtime_init()`, which jumps to `kernel_init()`.
mod bsp;
+mod console;
mod cpu;
mod memory;
mod panic_wait;
+mod print;
mod runtime_init;
/// Early init code.
@@ -112,5 +116,7 @@
///
/// - Only a single core must be active and running this function.
unsafe fn kernel_init() -> ! {
- panic!()
+ println!("[0] Hello from Rust!");
+
+ panic!("Stopping here.")
}
diff -uNr 02_runtime_init/src/panic_wait.rs 03_hacky_hello_world/src/panic_wait.rs
--- 02_runtime_init/src/panic_wait.rs
+++ 03_hacky_hello_world/src/panic_wait.rs
@@ -4,10 +4,16 @@
//! A panic handler that infinitely waits.
-use crate::cpu;
+use crate::{cpu, println};
use core::panic::PanicInfo;
#[panic_handler]
-fn panic(_info: &PanicInfo) -> ! {
+fn panic(info: &PanicInfo) -> ! {
+ if let Some(args) = info.message() {
+ println!("\nKernel panic: {}", args);
+ } else {
+ println!("\nKernel panic!");
+ }
+
cpu::wait_forever()
}
diff -uNr 02_runtime_init/src/print.rs 03_hacky_hello_world/src/print.rs
--- 02_runtime_init/src/print.rs
+++ 03_hacky_hello_world/src/print.rs
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Printing facilities.
+
+use crate::{bsp, console};
+use core::fmt;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+#[doc(hidden)]
+pub fn _print(args: fmt::Arguments) {
+ use console::interface::Write;
+
+ 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
+#[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)*));
+ })
+}
```

@ -8,3 +8,223 @@ 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
+++ 04_zero_overhead_abstraction/Cargo.toml
@@ -10,7 +10,10 @@
# The features section is used to select the target board.
[features]
default = []
-bsp_rpi3 = []
-bsp_rpi4 = []
+bsp_rpi3 = ["cortex-a"]
+bsp_rpi4 = ["cortex-a"]
[dependencies]
+
+# Optional dependencies
+cortex-a = { version = "2.9.x", optional = true }
diff -uNr 03_hacky_hello_world/src/_arch/aarch64/cpu/smp.rs 04_zero_overhead_abstraction/src/_arch/aarch64/cpu/smp.rs
--- 03_hacky_hello_world/src/_arch/aarch64/cpu/smp.rs
+++ 04_zero_overhead_abstraction/src/_arch/aarch64/cpu/smp.rs
@@ -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)
+}
diff -uNr 03_hacky_hello_world/src/_arch/aarch64/cpu.rs 04_zero_overhead_abstraction/src/_arch/aarch64/cpu.rs
--- 03_hacky_hello_world/src/_arch/aarch64/cpu.rs
+++ 04_zero_overhead_abstraction/src/_arch/aarch64/cpu.rs
@@ -4,8 +4,34 @@
//! Architectural processor code.
-// Assembly counterpart to this file.
-global_asm!(include_str!("cpu.S"));
+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.
+///
+/// # Safety
+///
+/// - Linker script must ensure to place this function at `0x80_000`.
+#[naked]
+#[no_mangle]
+pub unsafe extern "C" fn _start() -> ! {
+ use crate::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()
+ }
+}
//--------------------------------------------------------------------------------------------------
// Public Code
@@ -14,9 +40,7 @@
/// Pause execution on the core.
#[inline(always)]
pub fn wait_forever() -> ! {
- unsafe {
- loop {
- asm!("wfe" :::: "volatile")
- }
+ loop {
+ asm::wfe()
}
}
diff -uNr 03_hacky_hello_world/src/_arch/aarch64/cpu.S 04_zero_overhead_abstraction/src/_arch/aarch64/cpu.S
--- 03_hacky_hello_world/src/_arch/aarch64/cpu.S
+++ 04_zero_overhead_abstraction/src/_arch/aarch64/cpu.S
@@ -1,21 +0,0 @@
-// SPDX-License-Identifier: MIT OR Apache-2.0
-//
-// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
-
-.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 happened, 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 runtime_init // Jump to the "runtime_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/raspberrypi/cpu.rs 04_zero_overhead_abstraction/src/bsp/raspberrypi/cpu.rs
--- 03_hacky_hello_world/src/bsp/raspberrypi/cpu.rs
+++ 04_zero_overhead_abstraction/src/bsp/raspberrypi/cpu.rs
@@ -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;
diff -uNr 03_hacky_hello_world/src/bsp/raspberrypi.rs 04_zero_overhead_abstraction/src/bsp/raspberrypi.rs
--- 03_hacky_hello_world/src/bsp/raspberrypi.rs
+++ 04_zero_overhead_abstraction/src/bsp/raspberrypi.rs
@@ -5,3 +5,4 @@
//! Top-level BSP file for the Raspberry Pi 3 and 4.
pub mod console;
+pub mod cpu;
diff -uNr 03_hacky_hello_world/src/cpu/smp.rs 04_zero_overhead_abstraction/src/cpu/smp.rs
--- 03_hacky_hello_world/src/cpu/smp.rs
+++ 04_zero_overhead_abstraction/src/cpu/smp.rs
@@ -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::*;
diff -uNr 03_hacky_hello_world/src/cpu.rs 04_zero_overhead_abstraction/src/cpu.rs
--- 03_hacky_hello_world/src/cpu.rs
+++ 04_zero_overhead_abstraction/src/cpu.rs
@@ -8,3 +8,5 @@
#[path = "_arch/aarch64/cpu.rs"]
mod arch_cpu;
pub use arch_cpu::*;
+
+pub mod smp;
diff -uNr 03_hacky_hello_world/src/main.rs 04_zero_overhead_abstraction/src/main.rs
--- 03_hacky_hello_world/src/main.rs
+++ 04_zero_overhead_abstraction/src/main.rs
@@ -92,9 +92,8 @@
//! - `crate::memory::*`
//! - `crate::bsp::memory::*`
-#![feature(asm)]
#![feature(format_args_nl)]
-#![feature(global_asm)]
+#![feature(naked_functions)]
#![feature(panic_info_message)]
#![no_main]
#![no_std]
@@ -116,7 +115,8 @@
///
/// - Only a single core must be active and running this function.
unsafe fn kernel_init() -> ! {
- println!("[0] Hello from Rust!");
+ println!("[0] Hello from pure Rust!");
- panic!("Stopping here.")
+ println!("[1] Stopping here.");
+ cpu::wait_forever()
}
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
+++ 04_zero_overhead_abstraction/src/runtime_init.rs
@@ -50,8 +50,7 @@
/// # Safety
///
/// - Only a single core must be active and running this function.
-#[no_mangle]
-pub unsafe extern "C" fn runtime_init() -> ! {
+pub unsafe fn runtime_init() -> ! {
zero_bss();
crate::kernel_init()
```

@ -53,3 +53,295 @@ $ make qemu
```
## Diff to previous
```diff
diff -uNr 04_zero_overhead_abstraction/src/bsp/raspberrypi/console.rs 05_safe_globals/src/bsp/raspberrypi/console.rs
--- 04_zero_overhead_abstraction/src/bsp/raspberrypi/console.rs
+++ 05_safe_globals/src/bsp/raspberrypi/console.rs
@@ -4,7 +4,7 @@
//! BSP console facilities.
-use crate::console;
+use crate::{console, synchronization, synchronization::NullLock};
use core::fmt;
//--------------------------------------------------------------------------------------------------
@@ -12,25 +12,64 @@
//--------------------------------------------------------------------------------------------------
/// A mystical, magical device for generating QEMU output out of the void.
-struct QEMUOutput;
+///
+/// The mutex protected part.
+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 }
+ }
+
+ /// Send a character.
+ fn write_char(&mut self, c: char) {
+ unsafe {
+ core::ptr::write_volatile(0x3F20_1000 as *mut u8, c as u8);
+ }
+
+ self.chars_written += 1;
+ }
+}
+
/// 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 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);
}
Ok(())
@@ -41,7 +80,39 @@
// Public Code
//--------------------------------------------------------------------------------------------------
+impl QEMUOutput {
+ /// Create a new instance.
+ pub const fn new() -> QEMUOutput {
+ QEMUOutput {
+ inner: NullLock::new(QEMUOutputInner::new()),
+ }
+ }
+}
+
/// Return a reference to the console.
-pub fn console() -> impl console::interface::Write {
- QEMUOutput {}
+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 console::interface::Write for QEMUOutput {
+ 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))
+ }
+}
+
+impl console::interface::Statistics for QEMUOutput {
+ fn chars_written(&self) -> usize {
+ let mut r = &self.inner;
+ r.lock(|inner| inner.chars_written)
+ }
}
diff -uNr 04_zero_overhead_abstraction/src/console.rs 05_safe_globals/src/console.rs
--- 04_zero_overhead_abstraction/src/console.rs
+++ 05_safe_globals/src/console.rs
@@ -10,10 +10,22 @@
/// Console interfaces.
pub mod interface {
+ 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 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;
}
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
@@ -95,6 +95,7 @@
#![feature(format_args_nl)]
#![feature(naked_functions)]
#![feature(panic_info_message)]
+#![feature(trait_alias)]
#![no_main]
#![no_std]
@@ -108,6 +109,7 @@
mod panic_wait;
mod print;
mod runtime_init;
+mod synchronization;
/// Early init code.
///
@@ -115,8 +117,15 @@
///
/// - Only a single core must be active and running this function.
unsafe fn kernel_init() -> ! {
+ use console::interface::Statistics;
+
println!("[0] Hello from pure Rust!");
- println!("[1] Stopping here.");
+ println!(
+ "[1] Chars written: {}",
+ bsp::console::console().chars_written()
+ );
+
+ println!("[2] Stopping here.");
cpu::wait_forever()
}
diff -uNr 04_zero_overhead_abstraction/src/synchronization.rs 05_safe_globals/src/synchronization.rs
--- 04_zero_overhead_abstraction/src/synchronization.rs
+++ 05_safe_globals/src/synchronization.rs
@@ -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)
+ }
+}
```

File diff suppressed because it is too large Load Diff

@ -80,3 +80,446 @@ IN:
```
## Diff to previous
```diff
Binary files 06_drivers_gpio_uart/demo_payload_rpi3.img and 07_uart_chainloader/demo_payload_rpi3.img differ
Binary files 06_drivers_gpio_uart/demo_payload_rpi4.img and 07_uart_chainloader/demo_payload_rpi4.img differ
diff -uNr 06_drivers_gpio_uart/Makefile 07_uart_chainloader/Makefile
--- 06_drivers_gpio_uart/Makefile
+++ 07_uart_chainloader/Makefile
@@ -7,6 +7,11 @@
BSP = rpi3
endif
+# Default to /dev/ttyUSB0
+ifndef DEV_SERIAL
+ DEV_SERIAL = /dev/ttyUSB0
+endif
+
# BSP-specific arguments
ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat
@@ -15,7 +20,8 @@
QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 -C relocation-model=pic
+ CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img
else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat
OUTPUT = kernel8.img
@@ -23,7 +29,8 @@
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 -C relocation-model=pic
+ CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img
endif
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
@@ -46,9 +53,12 @@
DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_CMD = docker run -it --rm
DOCKER_ARG_DIR_TUT = -v $(shell pwd):/work -w /work
+DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/utils
+DOCKER_ARG_TTY = --privileged -v /dev:/dev
DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb
-.PHONY: all doc qemu clippy clean readelf objdump nm
+.PHONY: all doc qemu qemuasm chainboot clippy clean readelf objdump nm
all: clean $(OUTPUT)
@@ -65,13 +75,26 @@
ifeq ($(QEMU_MACHINE_TYPE),)
qemu:
@echo "This board is not yet supported for QEMU."
+
+qemuasm:
+ @echo "This board is not yet supported for QEMU."
else
qemu: all
@$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \
$(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \
-kernel $(OUTPUT)
+
+qemuasm: all
+ @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \
+ $(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \
+ -kernel $(OUTPUT) -d in_asm
endif
+chainboot:
+ @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_TTY) \
+ $(DOCKER_IMAGE) $(DOCKER_EXEC_MINIPUSH) $(DEV_SERIAL) \
+ $(CHAINBOOT_DEMO_PAYLOAD)
+
clippy:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" cargo xclippy --target=$(TARGET) --features bsp_$(BSP)
diff -uNr 06_drivers_gpio_uart/src/_arch/aarch64/cpu.rs 07_uart_chainloader/src/_arch/aarch64/cpu.rs
--- 06_drivers_gpio_uart/src/_arch/aarch64/cpu.rs
+++ 07_uart_chainloader/src/_arch/aarch64/cpu.rs
@@ -21,12 +21,12 @@
#[naked]
#[no_mangle]
pub unsafe extern "C" fn _start() -> ! {
- use crate::runtime_init;
+ use crate::relocate;
// 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()
+ relocate::relocate_self::<u64>()
} else {
// If not core0, infinitely wait for events.
wait_forever()
diff -uNr 06_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
--- 06_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
+++ 07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
@@ -282,6 +282,16 @@
let mut r = &self.inner;
r.lock(|inner| fmt::Write::write_fmt(inner, args))
}
+
+ fn flush(&self) {
+ // Spin until TX FIFO empty is set.
+ let mut r = &self.inner;
+ r.lock(|inner| {
+ while !inner.FR.matches_all(FR::TXFE::SET) {
+ cpu::nop();
+ }
+ });
+ }
}
impl console::interface::Read for PL011Uart {
@@ -293,18 +303,21 @@
cpu::nop();
}
- // Read one character.
- let mut ret = inner.DR.get() as u8 as char;
-
- // Convert carrige return to newline.
- if ret == '\r' {
- ret = '\n'
- }
-
// Update statistics.
inner.chars_read += 1;
- ret
+ // Read one character.
+ inner.DR.get() as u8 as char
+ })
+ }
+
+ fn clear(&self) {
+ let mut r = &self.inner;
+ r.lock(|inner| {
+ // Read from the RX FIFO until it is indicating empty.
+ while !inner.FR.matches_all(FR::RXFE::SET) {
+ inner.DR.get();
+ }
})
}
}
diff -uNr 06_drivers_gpio_uart/src/bsp/raspberrypi/cpu.rs 07_uart_chainloader/src/bsp/raspberrypi/cpu.rs
--- 06_drivers_gpio_uart/src/bsp/raspberrypi/cpu.rs
+++ 07_uart_chainloader/src/bsp/raspberrypi/cpu.rs
@@ -13,3 +13,6 @@
/// The early boot core's stack address.
pub const BOOT_CORE_STACK_START: u64 = 0x80_000;
+
+/// The address on which the Raspberry firmware loads every binary by default.
+pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x80_000;
diff -uNr 06_drivers_gpio_uart/src/bsp/raspberrypi/link.ld 07_uart_chainloader/src/bsp/raspberrypi/link.ld
--- 06_drivers_gpio_uart/src/bsp/raspberrypi/link.ld
+++ 07_uart_chainloader/src/bsp/raspberrypi/link.ld
@@ -5,9 +5,10 @@
SECTIONS
{
- /* Set current address to the value from which the RPi starts execution */
- . = 0x80000;
+ /* Set the link address to the top-most 40 KiB of DRAM (assuming 1GiB) */
+ . = 0x3F000000 - 0x10000;
+ __binary_start = .;
.text :
{
*(.text._start) *(.text*)
@@ -32,5 +33,14 @@
__bss_end = .;
}
+ .got :
+ {
+ *(.got*)
+ }
+
+ /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */
+ . = ALIGN(8);
+ __binary_end = .;
+
/DISCARD/ : { *(.comment*) }
}
diff -uNr 06_drivers_gpio_uart/src/console.rs 07_uart_chainloader/src/console.rs
--- 06_drivers_gpio_uart/src/console.rs
+++ 07_uart_chainloader/src/console.rs
@@ -19,6 +19,10 @@
/// Write a Rust format string.
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.
@@ -27,6 +31,9 @@
fn read_char(&self) -> char {
' '
}
+
+ /// Clear RX buffers, if any.
+ fn clear(&self);
}
/// Console statistics.
diff -uNr 06_drivers_gpio_uart/src/main.rs 07_uart_chainloader/src/main.rs
--- 06_drivers_gpio_uart/src/main.rs
+++ 07_uart_chainloader/src/main.rs
@@ -108,7 +108,8 @@
#![no_std]
// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls
-// `runtime_init()`, which jumps to `kernel_init()`.
+// `relocate::relocate_self()`. `relocate::relocate_self()` calls `runtime_init()`, which jumps to
+// `kernel_init()`.
mod bsp;
mod console;
@@ -117,6 +118,7 @@
mod memory;
mod panic_wait;
mod print;
+mod relocate;
mod runtime_init;
mod synchronization;
@@ -143,35 +145,52 @@
/// The main function running after the early init.
fn kernel_main() -> ! {
+ use bsp::console::console;
use console::interface::All;
- use driver::interface::DriverManager;
- // Wait for user to hit Enter.
- loop {
- if bsp::console::console().read_char() == '\n' {
- break;
+ println!(" __ __ _ _ _ _ ");
+ println!("| \\/ (_)_ _ (_) | ___ __ _ __| |");
+ println!("| |\\/| | | ' \\| | |__/ _ \\/ _` / _` |");
+ println!("|_| |_|_|_||_|_|____\\___/\\__,_\\__,_|");
+ println!();
+ println!("{:^37}", bsp::board_name());
+ println!();
+ println!("[ML] Requesting binary");
+ console().flush();
+
+ // Clear the RX FIFOs, if any, of spurious received characters before starting with the loader
+ // protocol.
+ console().clear();
+
+ // Notify `Minipush` to send the binary.
+ for _ in 0..3 {
+ console().write_char(3 as char);
+ }
+
+ // Read the binary's size.
+ let mut size: u32 = u32::from(console().read_char() as u8);
+ size |= u32::from(console().read_char() as u8) << 8;
+ size |= u32::from(console().read_char() as u8) << 16;
+ size |= u32::from(console().read_char() as u8) << 24;
+
+ // Trust it's not too big.
+ console().write_char('O');
+ console().write_char('K');
+
+ let kernel_addr: *mut u8 = bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS as *mut u8;
+ unsafe {
+ // Read the kernel byte by byte.
+ for i in 0..size {
+ *kernel_addr.offset(i as isize) = console().read_char() as u8;
}
}
- println!("[0] Booting on: {}", bsp::board_name());
+ println!("[ML] Loaded! Executing the payload now\n");
+ console().flush();
- println!("[1] Drivers loaded:");
- for (i, driver) in bsp::driver::driver_manager()
- .all_device_drivers()
- .iter()
- .enumerate()
- {
- println!(" {}. {}", i + 1, driver.compatible());
- }
+ // Use black magic to get a function pointer.
+ let kernel: extern "C" fn() -> ! = unsafe { core::mem::transmute(kernel_addr as *const ()) };
- println!(
- "[2] Chars written: {}",
- bsp::console::console().chars_written()
- );
- println!("[3] Echoing input now");
-
- loop {
- let c = bsp::console::console().read_char();
- bsp::console::console().write_char(c);
- }
+ // Jump to loaded kernel!
+ kernel()
}
diff -uNr 06_drivers_gpio_uart/src/relocate.rs 07_uart_chainloader/src/relocate.rs
--- 06_drivers_gpio_uart/src/relocate.rs
+++ 07_uart_chainloader/src/relocate.rs
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Relocation code.
+
+use crate::{bsp, runtime_init};
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Relocates the own binary from `bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS` to the `__binary_start`
+/// address from the linker script.
+///
+/// # Safety
+///
+/// - Only a single core must be active and running this function.
+/// - Function must not use the `bss` section.
+pub unsafe fn relocate_self<T>() -> ! {
+ extern "C" {
+ static __binary_start: usize;
+ static __binary_end: usize;
+ }
+
+ let binary_start_addr: usize = &__binary_start as *const _ as _;
+ let binary_end_addr: usize = &__binary_end as *const _ as _;
+ let binary_size_in_byte: usize = binary_end_addr - binary_start_addr;
+
+ // Get the relocation destination address from the linker symbol.
+ let mut reloc_dst_addr: *mut T = binary_start_addr as *mut T;
+
+ // The address of where the previous firmware loaded us.
+ let mut src_addr: *const T = bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS as *const _;
+
+ // Copy the whole binary.
+ //
+ // This is essentially a `memcpy()` optimized for throughput by transferring in chunks of T.
+ let n = binary_size_in_byte / core::mem::size_of::<T>();
+ for _ in 0..n {
+ use core::ptr;
+
+ ptr::write_volatile::<T>(reloc_dst_addr, ptr::read_volatile::<T>(src_addr));
+ reloc_dst_addr = reloc_dst_addr.offset(1);
+ src_addr = src_addr.offset(1);
+ }
+
+ // Call `runtime_init()` through a trait object, causing the jump to use an absolute address to
+ // reach the relocated binary. An elaborate explanation can be found in the `runtime_init.rs`
+ // source comments.
+ runtime_init::get().runtime_init()
+}
diff -uNr 06_drivers_gpio_uart/src/runtime_init.rs 07_uart_chainloader/src/runtime_init.rs
--- 06_drivers_gpio_uart/src/runtime_init.rs
+++ 07_uart_chainloader/src/runtime_init.rs
@@ -8,9 +8,43 @@
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+struct Traitor;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// We are outsmarting the compiler here by using a trait as a layer of indirection. Because we are
+/// generating PIC code, a static dispatch to `init()` would generate a relative jump from the
+/// callee to `init()`. However, when calling `init()`, code just finished copying the binary to the
+/// actual link-time address, and hence is still running at whatever location the previous loader
+/// has put it. So we do not want a relative jump, because it would not jump to the relocated code.
+///
+/// By indirecting through a trait object, we can make use of the property that vtables store
+/// absolute addresses. So calling `init()` this way will kick execution to the relocated binary.
+pub trait RunTimeInit {
+ /// 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.
+ unsafe fn runtime_init(&self) -> ! {
+ zero_bss();
+
+ crate::kernel_init()
+ }
+}
+
+//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
+impl RunTimeInit for Traitor {}
+
/// Return the range spanning the .bss section.
///
/// # Safety
@@ -44,14 +78,7 @@
// Public 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 runtime_init() -> ! {
- zero_bss();
-
- crate::kernel_init()
+/// Give the callee a `RunTimeInit` trait object.
+pub fn get() -> &'static dyn RunTimeInit {
+ &Traitor {}
}
```

@ -39,3 +39,613 @@ Minipush 1.0
```
## Diff to previous
```diff
Binary files 07_uart_chainloader/demo_payload_rpi3.img and 08_timestamps/demo_payload_rpi3.img differ
Binary files 07_uart_chainloader/demo_payload_rpi4.img and 08_timestamps/demo_payload_rpi4.img differ
diff -uNr 07_uart_chainloader/Makefile 08_timestamps/Makefile
--- 07_uart_chainloader/Makefile
+++ 08_timestamps/Makefile
@@ -20,8 +20,7 @@
QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 -C relocation-model=pic
- CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img
+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat
OUTPUT = kernel8.img
@@ -29,8 +28,7 @@
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
LINKER_FILE = src/bsp/raspberrypi/link.ld
- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 -C relocation-model=pic
- CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img
+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
@@ -58,7 +56,7 @@
DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb
-.PHONY: all doc qemu qemuasm chainboot clippy clean readelf objdump nm
+.PHONY: all doc qemu chainboot clippy clean readelf objdump nm
all: clean $(OUTPUT)
@@ -75,25 +73,17 @@
ifeq ($(QEMU_MACHINE_TYPE),)
qemu:
@echo "This board is not yet supported for QEMU."
-
-qemuasm:
- @echo "This board is not yet supported for QEMU."
else
qemu: all
@$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \
$(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \
-kernel $(OUTPUT)
-
-qemuasm: all
- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \
- $(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \
- -kernel $(OUTPUT) -d in_asm
endif
-chainboot:
+chainboot: all
@$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_TTY) \
$(DOCKER_IMAGE) $(DOCKER_EXEC_MINIPUSH) $(DEV_SERIAL) \
- $(CHAINBOOT_DEMO_PAYLOAD)
+ $(OUTPUT)
clippy:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" cargo xclippy --target=$(TARGET) --features bsp_$(BSP)
diff -uNr 07_uart_chainloader/src/_arch/aarch64/cpu.rs 08_timestamps/src/_arch/aarch64/cpu.rs
--- 07_uart_chainloader/src/_arch/aarch64/cpu.rs
+++ 08_timestamps/src/_arch/aarch64/cpu.rs
@@ -21,12 +21,12 @@
#[naked]
#[no_mangle]
pub unsafe extern "C" fn _start() -> ! {
- use crate::relocate;
+ use crate::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);
- relocate::relocate_self::<u64>()
+ runtime_init::runtime_init()
} else {
// If not core0, infinitely wait for events.
wait_forever()
diff -uNr 07_uart_chainloader/src/_arch/aarch64/time.rs 08_timestamps/src/_arch/aarch64/time.rs
--- 07_uart_chainloader/src/_arch/aarch64/time.rs
+++ 08_timestamps/src/_arch/aarch64/time.rs
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Architectural timer primitives.
+
+use crate::{time, warn};
+use core::time::Duration;
+use cortex_a::regs::*;
+
+//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+const NS_PER_S: u64 = 1_000_000_000;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// ARMv8 Generic Timer.
+pub struct GenericTimer;
+
+//--------------------------------------------------------------------------------------------------
+// Global instances
+//--------------------------------------------------------------------------------------------------
+
+static TIME_MANAGER: GenericTimer = GenericTimer;
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Return a reference to the time manager.
+pub fn time_manager() -> &'static impl time::interface::TimeManager {
+ &TIME_MANAGER
+}
+
+//------------------------------------------------------------------------------
+// OS Interface Code
+//------------------------------------------------------------------------------
+
+impl time::interface::TimeManager for GenericTimer {
+ 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);
+
+ // ISTATUS will be '1' when cval ticks have passed. Busy-check it.
+ while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {}
+
+ // Disable counting again.
+ CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR);
+ }
+}
diff -uNr 07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 08_timestamps/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
--- 07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
+++ 08_timestamps/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
@@ -303,11 +303,18 @@
cpu::nop();
}
+ // Read one character.
+ let mut ret = inner.DR.get() as u8 as char;
+
+ // Convert carrige return to newline.
+ if ret == '\r' {
+ ret = '\n'
+ }
+
// Update statistics.
inner.chars_read += 1;
- // Read one character.
- inner.DR.get() as u8 as char
+ ret
})
}
diff -uNr 07_uart_chainloader/src/bsp/raspberrypi/cpu.rs 08_timestamps/src/bsp/raspberrypi/cpu.rs
--- 07_uart_chainloader/src/bsp/raspberrypi/cpu.rs
+++ 08_timestamps/src/bsp/raspberrypi/cpu.rs
@@ -13,6 +13,3 @@
/// The early boot core's stack address.
pub const BOOT_CORE_STACK_START: u64 = 0x80_000;
-
-/// The address on which the Raspberry firmware loads every binary by default.
-pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x80_000;
diff -uNr 07_uart_chainloader/src/bsp/raspberrypi/link.ld 08_timestamps/src/bsp/raspberrypi/link.ld
--- 07_uart_chainloader/src/bsp/raspberrypi/link.ld
+++ 08_timestamps/src/bsp/raspberrypi/link.ld
@@ -5,10 +5,9 @@
SECTIONS
{
- /* Set the link address to the top-most 40 KiB of DRAM (assuming 1GiB) */
- . = 0x3F000000 - 0x10000;
+ /* Set current address to the value from which the RPi starts execution */
+ . = 0x80000;
- __binary_start = .;
.text :
{
*(.text._start) *(.text*)
@@ -33,14 +32,5 @@
__bss_end = .;
}
- .got :
- {
- *(.got*)
- }
-
- /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */
- . = ALIGN(8);
- __binary_end = .;
-
/DISCARD/ : { *(.comment*) }
}
diff -uNr 07_uart_chainloader/src/main.rs 08_timestamps/src/main.rs
--- 07_uart_chainloader/src/main.rs
+++ 08_timestamps/src/main.rs
@@ -11,9 +11,11 @@
//!
//! - [`bsp::console::console()`] - Returns a reference to the kernel's [console interface].
//! - [`bsp::driver::driver_manager()`] - Returns a reference to the kernel's [driver interface].
+//! - [`time::time_manager()`] - Returns a reference to the kernel's [timer interface].
//!
//! [console interface]: ../libkernel/console/interface/index.html
//! [driver interface]: ../libkernel/driver/interface/trait.DriverManager.html
+//! [timer interface]: ../libkernel/time/interface/trait.TimeManager.html
//!
//! # Code organization and architecture
//!
@@ -108,8 +110,7 @@
#![no_std]
// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls
-// `relocate::relocate_self()`. `relocate::relocate_self()` calls `runtime_init()`, which jumps to
-// `kernel_init()`.
+// `runtime_init()`, which jumps to `kernel_init()`.
mod bsp;
mod console;
@@ -118,9 +119,9 @@
mod memory;
mod panic_wait;
mod print;
-mod relocate;
mod runtime_init;
mod synchronization;
+mod time;
/// Early init code.
///
@@ -145,52 +146,31 @@
/// The main function running after the early init.
fn kernel_main() -> ! {
- use bsp::console::console;
- use console::interface::All;
+ use core::time::Duration;
+ use driver::interface::DriverManager;
+ use time::interface::TimeManager;
- println!(" __ __ _ _ _ _ ");
- println!("| \\/ (_)_ _ (_) | ___ __ _ __| |");
- println!("| |\\/| | | ' \\| | |__/ _ \\/ _` / _` |");
- println!("|_| |_|_|_||_|_|____\\___/\\__,_\\__,_|");
- println!();
- println!("{:^37}", bsp::board_name());
- println!();
- println!("[ML] Requesting binary");
- console().flush();
-
- // Clear the RX FIFOs, if any, of spurious received characters before starting with the loader
- // protocol.
- console().clear();
-
- // Notify `Minipush` to send the binary.
- for _ in 0..3 {
- console().write_char(3 as char);
- }
+ info!("Booting on: {}", bsp::board_name());
- // Read the binary's size.
- let mut size: u32 = u32::from(console().read_char() as u8);
- size |= u32::from(console().read_char() as u8) << 8;
- size |= u32::from(console().read_char() as u8) << 16;
- size |= u32::from(console().read_char() as u8) << 24;
-
- // Trust it's not too big.
- console().write_char('O');
- console().write_char('K');
-
- let kernel_addr: *mut u8 = bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS as *mut u8;
- unsafe {
- // Read the kernel byte by byte.
- for i in 0..size {
- *kernel_addr.offset(i as isize) = console().read_char() as u8;
- }
+ info!(
+ "Architectural timer resolution: {} ns",
+ time::time_manager().resolution().as_nanos()
+ );
+
+ info!("Drivers loaded:");
+ for (i, driver) in bsp::driver::driver_manager()
+ .all_device_drivers()
+ .iter()
+ .enumerate()
+ {
+ info!(" {}. {}", i + 1, driver.compatible());
}
- println!("[ML] Loaded! Executing the payload now\n");
- console().flush();
+ // Test a failing timer case.
+ time::time_manager().spin_for(Duration::from_nanos(1));
- // Use black magic to get a function pointer.
- let kernel: extern "C" fn() -> ! = unsafe { core::mem::transmute(kernel_addr as *const ()) };
-
- // Jump to loaded kernel!
- kernel()
+ loop {
+ info!("Spinning for 1 second");
+ time::time_manager().spin_for(Duration::from_secs(1));
+ }
}
diff -uNr 07_uart_chainloader/src/print.rs 08_timestamps/src/print.rs
--- 07_uart_chainloader/src/print.rs
+++ 08_timestamps/src/print.rs
@@ -40,3 +40,71 @@
$crate::print::_print(format_args_nl!($($arg)*));
})
}
+
+/// Prints an info, with a newline.
+#[macro_export]
+macro_rules! info {
+ ($string:expr) => ({
+ #[allow(unused_imports)]
+ use crate::time::interface::TimeManager;
+
+ let timestamp = $crate::time::time_manager().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 modulo 1_000
+ ));
+ });
+ ($format_string:expr, $($arg:tt)*) => ({
+ #[allow(unused_imports)]
+ use crate::time::interface::TimeManager;
+
+ let timestamp = $crate::time::time_manager().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 modulo 1_000,
+ $($arg)*
+ ));
+ })
+}
+
+/// Prints a warning, with a newline.
+#[macro_export]
+macro_rules! warn {
+ ($string:expr) => ({
+ #[allow(unused_imports)]
+ use crate::time::interface::TimeManager;
+
+ let timestamp = $crate::time::time_manager().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 modulo 1_000
+ ));
+ });
+ ($format_string:expr, $($arg:tt)*) => ({
+ #[allow(unused_imports)]
+ use crate::time::interface::TimeManager;
+
+ let timestamp = $crate::time::time_manager().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 modulo 1_000,
+ $($arg)*
+ ));
+ })
+}
diff -uNr 07_uart_chainloader/src/relocate.rs 08_timestamps/src/relocate.rs
--- 07_uart_chainloader/src/relocate.rs
+++ 08_timestamps/src/relocate.rs
@@ -1,52 +0,0 @@
-// SPDX-License-Identifier: MIT OR Apache-2.0
-//
-// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
-
-//! Relocation code.
-
-use crate::{bsp, runtime_init};
-
-//--------------------------------------------------------------------------------------------------
-// Public Code
-//--------------------------------------------------------------------------------------------------
-
-/// Relocates the own binary from `bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS` to the `__binary_start`
-/// address from the linker script.
-///
-/// # Safety
-///
-/// - Only a single core must be active and running this function.
-/// - Function must not use the `bss` section.
-pub unsafe fn relocate_self<T>() -> ! {
- extern "C" {
- static __binary_start: usize;
- static __binary_end: usize;
- }
-
- let binary_start_addr: usize = &__binary_start as *const _ as _;
- let binary_end_addr: usize = &__binary_end as *const _ as _;
- let binary_size_in_byte: usize = binary_end_addr - binary_start_addr;
-
- // Get the relocation destination address from the linker symbol.
- let mut reloc_dst_addr: *mut T = binary_start_addr as *mut T;
-
- // The address of where the previous firmware loaded us.
- let mut src_addr: *const T = bsp::cpu::BOARD_DEFAULT_LOAD_ADDRESS as *const _;
-
- // Copy the whole binary.
- //
- // This is essentially a `memcpy()` optimized for throughput by transferring in chunks of T.
- let n = binary_size_in_byte / core::mem::size_of::<T>();
- for _ in 0..n {
- use core::ptr;
-
- ptr::write_volatile::<T>(reloc_dst_addr, ptr::read_volatile::<T>(src_addr));
- reloc_dst_addr = reloc_dst_addr.offset(1);
- src_addr = src_addr.offset(1);
- }
-
- // Call `runtime_init()` through a trait object, causing the jump to use an absolute address to
- // reach the relocated binary. An elaborate explanation can be found in the `runtime_init.rs`
- // source comments.
- runtime_init::get().runtime_init()
-}
diff -uNr 07_uart_chainloader/src/runtime_init.rs 08_timestamps/src/runtime_init.rs
--- 07_uart_chainloader/src/runtime_init.rs
+++ 08_timestamps/src/runtime_init.rs
@@ -8,43 +8,9 @@
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
-// Private Definitions
-//--------------------------------------------------------------------------------------------------
-
-struct Traitor;
-
-//--------------------------------------------------------------------------------------------------
-// Public Definitions
-//--------------------------------------------------------------------------------------------------
-
-/// We are outsmarting the compiler here by using a trait as a layer of indirection. Because we are
-/// generating PIC code, a static dispatch to `init()` would generate a relative jump from the
-/// callee to `init()`. However, when calling `init()`, code just finished copying the binary to the
-/// actual link-time address, and hence is still running at whatever location the previous loader
-/// has put it. So we do not want a relative jump, because it would not jump to the relocated code.
-///
-/// By indirecting through a trait object, we can make use of the property that vtables store
-/// absolute addresses. So calling `init()` this way will kick execution to the relocated binary.
-pub trait RunTimeInit {
- /// 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.
- unsafe fn runtime_init(&self) -> ! {
- zero_bss();
-
- crate::kernel_init()
- }
-}
-
-//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
-impl RunTimeInit for Traitor {}
-
/// Return the range spanning the .bss section.
///
/// # Safety
@@ -78,7 +44,14 @@
// Public Code
//--------------------------------------------------------------------------------------------------
-/// Give the callee a `RunTimeInit` trait object.
-pub fn get() -> &'static dyn RunTimeInit {
- &Traitor {}
+/// 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 runtime_init() -> ! {
+ zero_bss();
+
+ crate::kernel_init()
}
diff -uNr 07_uart_chainloader/src/time.rs 08_timestamps/src/time.rs
--- 07_uart_chainloader/src/time.rs
+++ 08_timestamps/src/time.rs
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Timer primitives.
+
+#[cfg(target_arch = "aarch64")]
+#[path = "_arch/aarch64/time.rs"]
+mod arch_time;
+pub use arch_time::*;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Timekeeping interfaces.
+pub mod interface {
+ use core::time::Duration;
+
+ /// Time management functions.
+ ///
+ /// The `BSP` is supposed to supply one global instance.
+ pub trait TimeManager {
+ /// 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);
+ }
+}
```

@ -302,3 +302,71 @@ runs, while keeping the debugger connected.
Thanks to [@naotaco](https://github.com/naotaco) for laying the groundwork for this tutorial.
## Diff to previous
```diff
diff -uNr 08_timestamps/Makefile 09_hw_debug_JTAG/Makefile
--- 08_timestamps/Makefile
+++ 09_hw_debug_JTAG/Makefile
@@ -19,6 +19,8 @@
QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none
+ 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/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4)
@@ -27,6 +29,8 @@
# QEMU_BINARY = qemu-system-aarch64
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
+ 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/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif
@@ -52,11 +56,13 @@
DOCKER_CMD = docker run -it --rm
DOCKER_ARG_DIR_TUT = -v $(shell pwd):/work -w /work
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/utils
+DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag
DOCKER_ARG_TTY = --privileged -v /dev:/dev
+DOCKER_ARG_NET = --network host
DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb
-.PHONY: all doc qemu chainboot clippy clean readelf objdump nm
+.PHONY: all doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy clean readelf objdump nm
all: clean $(OUTPUT)
@@ -85,6 +91,28 @@
$(DOCKER_IMAGE) $(DOCKER_EXEC_MINIPUSH) $(DEV_SERIAL) \
$(OUTPUT)
+jtagboot:
+ @$(DOCKER_CMD) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_TTY) \
+ $(DOCKER_IMAGE) $(DOCKER_EXEC_MINIPUSH) $(DEV_SERIAL) \
+ /jtag/$(JTAG_BOOT_IMAGE)
+
+openocd:
+ @$(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
+ openocd $(OPENOCD_ARG)
+
+define gen_gdb
+ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(XRUSTC_CMD)
+ cp $(CARGO_OUTPUT) kernel_for_jtag
+ @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
+ 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:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" cargo xclippy --target=$(TARGET) --features bsp_$(BSP)
```

@ -220,3 +220,274 @@ Minipush 1.0
```
## Diff to previous
```diff
diff -uNr 09_hw_debug_JTAG/src/_arch/aarch64/cpu.rs 10_privilege_level/src/_arch/aarch64/cpu.rs
--- 09_hw_debug_JTAG/src/_arch/aarch64/cpu.rs
+++ 10_privilege_level/src/_arch/aarch64/cpu.rs
@@ -21,18 +21,59 @@
#[naked]
#[no_mangle]
pub unsafe extern "C" fn _start() -> ! {
- use crate::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()
+ if (bsp::cpu::BOOT_CORE_ID == cpu::smp::core_id())
+ && (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.
+///
+/// # Safety
+///
+/// - The HW state of EL1 must be prepared in a sound way.
+/// - Exception return from EL2 must must continue execution in EL1 with
+/// `runtime_init::runtime_init()`.
+#[inline(always)]
+unsafe fn el2_to_el1_transition() -> ! {
+ use crate::runtime_init;
+
+ // 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 runtime_init().
+ ELR_EL2.set(runtime_init::runtime_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::cpu::BOOT_CORE_STACK_START);
+
+ // Use `eret` to "return" to EL1. This results in execution of runtime_init() in EL1.
+ asm::eret()
+}
+
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
diff -uNr 09_hw_debug_JTAG/src/_arch/aarch64/exception/asynchronous.rs 10_privilege_level/src/_arch/aarch64/exception/asynchronous.rs
--- 09_hw_debug_JTAG/src/_arch/aarch64/exception/asynchronous.rs
+++ 10_privilege_level/src/_arch/aarch64/exception/asynchronous.rs
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Architectural asynchronous exception handling.
+
+use cortex_a::regs::*;
+
+//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+trait DaifField {
+ fn daif_field() -> register::Field<u32, DAIF::Register>;
+}
+
+struct Debug;
+struct SError;
+struct IRQ;
+struct FIQ;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+impl DaifField for Debug {
+ fn daif_field() -> register::Field<u32, DAIF::Register> {
+ DAIF::D
+ }
+}
+
+impl DaifField for SError {
+ fn daif_field() -> register::Field<u32, DAIF::Register> {
+ DAIF::A
+ }
+}
+
+impl DaifField for IRQ {
+ fn daif_field() -> register::Field<u32, DAIF::Register> {
+ DAIF::I
+ }
+}
+
+impl DaifField for FIQ {
+ fn daif_field() -> register::Field<u32, DAIF::Register> {
+ DAIF::F
+ }
+}
+
+fn is_masked<T: DaifField>() -> bool {
+ DAIF.is_set(T::daif_field())
+}
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Print the AArch64 exceptions status.
+#[rustfmt::skip]
+pub fn print_state() {
+ use crate::info;
+
+ let to_mask_str = |x| -> _ {
+ if x { "Masked" } else { "Unmasked" }
+ };
+
+ info!(" Debug: {}", to_mask_str(is_masked::<Debug>()));
+ info!(" SError: {}", to_mask_str(is_masked::<SError>()));
+ info!(" IRQ: {}", to_mask_str(is_masked::<IRQ>()));
+ info!(" FIQ: {}", to_mask_str(is_masked::<FIQ>()));
+}
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,23 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Architectural synchronous and asynchronous exception handling.
+
+use cortex_a::regs::*;
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+use crate::exception::PrivilegeLevel;
+
+/// The processing element's current privilege level.
+pub fn current_privilege_level() -> (PrivilegeLevel, &'static str) {
+ let el = CurrentEL.read_as_enum(CurrentEL::EL);
+ match el {
+ Some(CurrentEL::EL::Value::EL2) => (PrivilegeLevel::Hypervisor, "EL2"),
+ Some(CurrentEL::EL::Value::EL1) => (PrivilegeLevel::Kernel, "EL1"),
+ Some(CurrentEL::EL::Value::EL0) => (PrivilegeLevel::User, "EL0"),
+ _ => (PrivilegeLevel::Unknown, "Unknown"),
+ }
+}
diff -uNr 09_hw_debug_JTAG/src/exception/asynchronous.rs 10_privilege_level/src/exception/asynchronous.rs
--- 09_hw_debug_JTAG/src/exception/asynchronous.rs
+++ 10_privilege_level/src/exception/asynchronous.rs
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Asynchronous exception handling.
+
+#[cfg(target_arch = "aarch64")]
+#[path = "../_arch/aarch64/exception/asynchronous.rs"]
+mod arch_exception_async;
+pub use arch_exception_async::*;
diff -uNr 09_hw_debug_JTAG/src/exception.rs 10_privilege_level/src/exception.rs
--- 09_hw_debug_JTAG/src/exception.rs
+++ 10_privilege_level/src/exception.rs
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Synchronous and asynchronous exception handling.
+
+#[cfg(target_arch = "aarch64")]
+#[path = "_arch/aarch64/exception.rs"]
+mod arch_exception;
+pub use arch_exception::*;
+
+pub mod asynchronous;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Kernel privilege levels.
+#[allow(missing_docs)]
+#[derive(PartialEq)]
+pub enum PrivilegeLevel {
+ User,
+ Kernel,
+ Hypervisor,
+ Unknown,
+}
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
@@ -116,6 +116,7 @@
mod console;
mod cpu;
mod driver;
+mod exception;
mod memory;
mod panic_wait;
mod print;
@@ -146,12 +147,19 @@
/// The main function running after the early init.
fn kernel_main() -> ! {
+ use console::interface::All;
use core::time::Duration;
use driver::interface::DriverManager;
use time::interface::TimeManager;
info!("Booting on: {}", bsp::board_name());
+ let (_, privilege_level) = exception::current_privilege_level();
+ info!("Current privilege level: {}", privilege_level);
+
+ info!("Exception handling state:");
+ exception::asynchronous::print_state();
+
info!(
"Architectural timer resolution: {} ns",
time::time_manager().resolution().as_nanos()
@@ -166,11 +174,12 @@
info!(" {}. {}", i + 1, driver.compatible());
}
- // Test a failing timer case.
- time::time_manager().spin_for(Duration::from_nanos(1));
+ info!("Timer test, spinning for 1 second");
+ time::time_manager().spin_for(Duration::from_secs(1));
+ info!("Echoing input now");
loop {
- info!("Spinning for 1 second");
- time::time_manager().spin_for(Duration::from_secs(1));
+ let c = bsp::console::console().read_char();
+ bsp::console::console().write_char(c);
}
}
```

@ -295,3 +295,810 @@ Minipush 1.0
```
## Diff to previous
```diff
diff -uNr 10_privilege_level/src/_arch/aarch64/memory/mmu.rs 11_virtual_memory/src/_arch/aarch64/memory/mmu.rs
--- 10_privilege_level/src/_arch/aarch64/memory/mmu.rs
+++ 11_virtual_memory/src/_arch/aarch64/memory/mmu.rs
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Memory Management Unit Driver.
+//!
+//! Static page tables, compiled on boot; Everything 64 KiB granule.
+
+use super::{AccessPermissions, AttributeFields, MemAttributes};
+use crate::{bsp, memory};
+use core::convert;
+use cortex_a::{barrier, regs::*};
+use register::register_bitfields;
+
+//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+// A table descriptor, as per ARMv8-A Architecture Reference Manual Figure D4-15.
+register_bitfields! {u64,
+ STAGE1_TABLE_DESCRIPTOR [
+ /// Physical address of the next page table.
+ NEXT_LEVEL_TABLE_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16]
+
+ TYPE OFFSET(1) NUMBITS(1) [
+ Block = 0,
+ Table = 1
+ ],
+
+ VALID OFFSET(0) NUMBITS(1) [
+ False = 0,
+ True = 1
+ ]
+ ]
+}
+
+// A level 3 page descriptor, as per ARMv8-A Architecture Reference Manual Figure D4-17.
+register_bitfields! {u64,
+ STAGE1_PAGE_DESCRIPTOR [
+ /// Privileged execute-never.
+ PXN OFFSET(53) NUMBITS(1) [
+ False = 0,
+ True = 1
+ ],
+
+ /// Physical address of the next page table (lvl2) or the page descriptor (lvl3).
+ OUTPUT_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16]
+
+ /// Access flag.
+ AF OFFSET(10) NUMBITS(1) [
+ False = 0,
+ True = 1
+ ],
+
+ /// Shareability field.
+ SH OFFSET(8) NUMBITS(2) [
+ OuterShareable = 0b10,
+ InnerShareable = 0b11
+ ],
+
+ /// Access Permissions.
+ AP OFFSET(6) NUMBITS(2) [
+ RW_EL1 = 0b00,
+ RW_EL1_EL0 = 0b01,
+ RO_EL1 = 0b10,
+ RO_EL1_EL0 = 0b11
+ ],
+
+ /// Memory attributes index into the MAIR_EL1 register.
+ AttrIndx OFFSET(2) NUMBITS(3) [],
+
+ TYPE OFFSET(1) NUMBITS(1) [
+ Block = 0,
+ Table = 1
+ ],
+
+ VALID OFFSET(0) NUMBITS(1) [
+ False = 0,
+ True = 1
+ ]
+ ]
+}
+
+const SIXTYFOUR_KIB_SHIFT: usize = 16; // log2(64 * 1024)
+const FIVETWELVE_MIB_SHIFT: usize = 29; // log2(512 * 1024 * 1024)
+
+/// A table descriptor for 64 KiB aperture.
+///
+/// The output points to the next table.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+struct TableDescriptor(u64);
+
+/// A page descriptor with 64 KiB aperture.
+///
+/// The output points to physical memory.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+struct PageDescriptor(u64);
+
+/// Big monolithic struct for storing the page tables. Individual levels must be 64 KiB aligned,
+/// hence the "reverse" order of appearance.
+#[repr(C)]
+#[repr(align(65536))]
+struct PageTables<const N: usize> {
+ /// Page descriptors, covering 64 KiB windows per entry.
+ lvl3: [[PageDescriptor; 8192]; N],
+
+ /// Table descriptors, covering 512 MiB windows.
+ lvl2: [TableDescriptor; N],
+}
+
+/// Usually evaluates to 1 GiB for RPi3 and 4 GiB for RPi 4.
+const ENTRIES_512_MIB: usize = bsp::memory::mmu::addr_space_size() >> FIVETWELVE_MIB_SHIFT;
+
+/// The page tables.
+///
+/// # Safety
+///
+/// - Supposed to land in `.bss`. Therefore, ensure that they boil down to all "0" entries.
+static mut TABLES: PageTables<{ ENTRIES_512_MIB }> = PageTables {
+ lvl3: [[PageDescriptor(0); 8192]; ENTRIES_512_MIB],
+ lvl2: [TableDescriptor(0); ENTRIES_512_MIB],
+};
+
+trait BaseAddr {
+ fn base_addr_u64(&self) -> u64;
+ fn base_addr_usize(&self) -> usize;
+}
+
+/// Constants for indexing the MAIR_EL1.
+#[allow(dead_code)]
+mod mair {
+ pub const DEVICE: u64 = 0;
+ pub const NORMAL: u64 = 1;
+}
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Memory Management Unit type.
+pub struct MemoryManagementUnit;
+
+//--------------------------------------------------------------------------------------------------
+// Global instances
+//--------------------------------------------------------------------------------------------------
+
+static MMU: MemoryManagementUnit = MemoryManagementUnit;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+impl<T, const N: usize> BaseAddr for [T; N] {
+ fn base_addr_u64(&self) -> u64 {
+ self as *const T as u64
+ }
+
+ fn base_addr_usize(&self) -> usize {
+ self as *const T as usize
+ }
+}
+
+impl convert::From<usize> for TableDescriptor {
+ fn from(next_lvl_table_addr: usize) -> Self {
+ let shifted = next_lvl_table_addr >> SIXTYFOUR_KIB_SHIFT;
+ let val = (STAGE1_TABLE_DESCRIPTOR::VALID::True
+ + STAGE1_TABLE_DESCRIPTOR::TYPE::Table
+ + STAGE1_TABLE_DESCRIPTOR::NEXT_LEVEL_TABLE_ADDR_64KiB.val(shifted as u64))
+ .value;
+
+ TableDescriptor(val)
+ }
+}
+
+/// Convert the kernel's generic memory range attributes to HW-specific attributes of the MMU.
+impl convert::From<AttributeFields>
+ for register::FieldValue<u64, STAGE1_PAGE_DESCRIPTOR::Register>
+{
+ fn from(attribute_fields: AttributeFields) -> Self {
+ // Memory attributes.
+ let mut desc = match attribute_fields.mem_attributes {
+ MemAttributes::CacheableDRAM => {
+ STAGE1_PAGE_DESCRIPTOR::SH::InnerShareable
+ + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
+ }
+ MemAttributes::Device => {
+ STAGE1_PAGE_DESCRIPTOR::SH::OuterShareable
+ + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
+ }
+ };
+
+ // Access Permissions.
+ desc += match attribute_fields.acc_perms {
+ AccessPermissions::ReadOnly => STAGE1_PAGE_DESCRIPTOR::AP::RO_EL1,
+ AccessPermissions::ReadWrite => STAGE1_PAGE_DESCRIPTOR::AP::RW_EL1,
+ };
+
+ // Execute Never.
+ desc += if attribute_fields.execute_never {
+ STAGE1_PAGE_DESCRIPTOR::PXN::True
+ } else {
+ STAGE1_PAGE_DESCRIPTOR::PXN::False
+ };
+
+ desc
+ }
+}
+
+impl PageDescriptor {
+ fn new(output_addr: usize, attribute_fields: AttributeFields) -> Self {
+ let shifted = output_addr >> SIXTYFOUR_KIB_SHIFT;
+ let val = (STAGE1_PAGE_DESCRIPTOR::VALID::True
+ + STAGE1_PAGE_DESCRIPTOR::AF::True
+ + attribute_fields.into()
+ + STAGE1_PAGE_DESCRIPTOR::TYPE::Table
+ + STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB.val(shifted as u64))
+ .value;
+
+ Self(val)
+ }
+}
+
+/// Setup function for the MAIR_EL1 register.
+fn set_up_mair() {
+ // Define the memory types being mapped.
+ MAIR_EL1.write(
+ // Attribute 1 - Cacheable normal DRAM.
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
+ + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
+
+ // Attribute 0 - Device.
+ + MAIR_EL1::Attr0_HIGH::Device
+ + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
+ );
+}
+
+/// Iterates over all static page table entries and fills them at once.
+///
+/// # Safety
+///
+/// - Modifies a `static mut`. Ensure it only happens from here.
+unsafe fn populate_pt_entries() -> Result<(), &'static str> {
+ for (l2_nr, l2_entry) in TABLES.lvl2.iter_mut().enumerate() {
+ *l2_entry = TABLES.lvl3[l2_nr].base_addr_usize().into();
+
+ for (l3_nr, l3_entry) in TABLES.lvl3[l2_nr].iter_mut().enumerate() {
+ let virt_addr = (l2_nr << FIVETWELVE_MIB_SHIFT) + (l3_nr << SIXTYFOUR_KIB_SHIFT);
+
+ let (output_addr, attribute_fields) =
+ bsp::memory::mmu::virt_mem_layout().get_virt_addr_properties(virt_addr)?;
+
+ *l3_entry = PageDescriptor::new(output_addr, attribute_fields);
+ }
+ }
+
+ Ok(())
+}
+
+/// Configure various settings of stage 1 of the EL1 translation regime.
+fn configure_translation_control() {
+ let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
+ TCR_EL1.write(
+ TCR_EL1::TBI0::Ignored
+ + TCR_EL1::IPS.val(ips)
+ + TCR_EL1::TG0::KiB_64
+ + TCR_EL1::SH0::Inner
+ + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ + TCR_EL1::EPD0::EnableTTBR0Walks
+ + TCR_EL1::T0SZ.val(32), // TTBR0 spans 4 GiB total.
+ );
+}
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Return a reference to the MMU.
+pub fn mmu() -> &'static impl memory::mmu::interface::MMU {
+ &MMU
+}
+
+//------------------------------------------------------------------------------
+// OS Interface Code
+//------------------------------------------------------------------------------
+
+impl memory::mmu::interface::MMU for MemoryManagementUnit {
+ unsafe fn init(&self) -> Result<(), &'static str> {
+ // Fail early if translation granule is not supported. Both RPis support it, though.
+ if !ID_AA64MMFR0_EL1.matches_all(ID_AA64MMFR0_EL1::TGran64::Supported) {
+ return Err("64 KiB translation granule not supported");
+ }
+
+ // Prepare the memory attribute indirection register.
+ set_up_mair();
+
+ // Populate page tables.
+ populate_pt_entries()?;
+
+ // Set the "Translation Table Base Register".
+ TTBR0_EL1.set_baddr(TABLES.lvl2.base_addr_u64());
+
+ configure_translation_control();
+
+ // Switch the MMU on.
+ //
+ // First, force all previous changes to be seen before the MMU is enabled.
+ barrier::isb(barrier::SY);
+
+ // Enable the MMU and turn on data and instruction caching.
+ SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
+
+ // Force MMU init to complete before next instruction.
+ barrier::isb(barrier::SY);
+
+ Ok(())
+ }
+}
diff -uNr 10_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 11_virtual_memory/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
--- 10_privilege_level/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
+++ 11_virtual_memory/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
@@ -118,6 +118,7 @@
//--------------------------------------------------------------------------------------------------
register_structs! {
+ #[allow(missing_docs)]
#[allow(non_snake_case)]
pub RegisterBlock {
(0x00 => DR: ReadWrite<u32>),
@@ -134,6 +135,7 @@
}
}
+#[allow(missing_docs)]
pub struct PL011UartInner {
base_addr: usize,
chars_written: usize,
diff -uNr 10_privilege_level/src/bsp/raspberrypi/link.ld 11_virtual_memory/src/bsp/raspberrypi/link.ld
--- 10_privilege_level/src/bsp/raspberrypi/link.ld
+++ 11_virtual_memory/src/bsp/raspberrypi/link.ld
@@ -8,6 +8,7 @@
/* Set current address to the value from which the RPi starts execution */
. = 0x80000;
+ __ro_start = .;
.text :
{
*(.text._start) *(.text*)
@@ -17,6 +18,8 @@
{
*(.rodata*)
}
+ . = ALIGN(65536); /* Fill up to 64 KiB */
+ __ro_end = .;
.data :
{
diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs
--- 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs
+++ 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! BSP Memory Management Unit.
+
+use super::super::memory;
+use crate::memory::mmu::*;
+use core::ops::RangeInclusive;
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+const NUM_MEM_RANGES: usize = 3;
+
+/// The virtual memory layout.
+///
+/// The layout must contain only special ranges, aka anything that is _not_ normal cacheable DRAM.
+/// It is agnostic of the paging granularity that the architecture's MMU will use.
+pub static LAYOUT: KernelVirtualLayout<{ NUM_MEM_RANGES }> = KernelVirtualLayout::new(
+ memory::map::END_INCLUSIVE,
+ [
+ RangeDescriptor {
+ name: "Kernel code and RO data",
+ virtual_range: || {
+ // Using the linker script, we ensure that the RO area is consecutive and 64 KiB
+ // aligned, and we export the boundaries via symbols:
+ //
+ // [__ro_start, __ro_end)
+ extern "C" {
+ // The inclusive start of the read-only area, aka the address of the first
+ // byte of the area.
+ static __ro_start: usize;
+
+ // The exclusive end of the read-only area, aka the address of the first
+ // byte _after_ the RO area.
+ static __ro_end: usize;
+ }
+
+ unsafe {
+ // Notice the subtraction to turn the exclusive end into an inclusive end.
+ #[allow(clippy::range_minus_one)]
+ RangeInclusive::new(
+ &__ro_start as *const _ as usize,
+ &__ro_end as *const _ as usize - 1,
+ )
+ }
+ },
+ translation: Translation::Identity,
+ attribute_fields: AttributeFields {
+ mem_attributes: MemAttributes::CacheableDRAM,
+ acc_perms: AccessPermissions::ReadOnly,
+ execute_never: false,
+ },
+ },
+ RangeDescriptor {
+ name: "Remapped Device MMIO",
+ virtual_range: || {
+ // The last 64 KiB slot in the first 512 MiB
+ RangeInclusive::new(0x1FFF_0000, 0x1FFF_FFFF)
+ },
+ translation: Translation::Offset(memory::map::mmio::BASE + 0x20_0000),
+ attribute_fields: AttributeFields {
+ mem_attributes: MemAttributes::Device,
+ acc_perms: AccessPermissions::ReadWrite,
+ execute_never: true,
+ },
+ },
+ RangeDescriptor {
+ name: "Device MMIO",
+ virtual_range: || {
+ RangeInclusive::new(memory::map::mmio::BASE, memory::map::mmio::END_INCLUSIVE)
+ },
+ translation: Translation::Identity,
+ attribute_fields: AttributeFields {
+ mem_attributes: MemAttributes::Device,
+ acc_perms: AccessPermissions::ReadWrite,
+ execute_never: true,
+ },
+ },
+ ],
+);
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+/// Return the address space size in bytes.
+pub const fn addr_space_size() -> usize {
+ memory::map::END_INCLUSIVE + 1
+}
+
+/// Return a reference to the virtual memory layout.
+pub fn virt_mem_layout() -> &'static KernelVirtualLayout<{ NUM_MEM_RANGES }> {
+ &LAYOUT
+}
diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory.rs 11_virtual_memory/src/bsp/raspberrypi/memory.rs
--- 10_privilege_level/src/bsp/raspberrypi/memory.rs
+++ 11_virtual_memory/src/bsp/raspberrypi/memory.rs
@@ -4,6 +4,8 @@
//! BSP Memory Management.
+pub mod mmu;
+
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
@@ -11,6 +13,8 @@
/// The board's memory map.
#[rustfmt::skip]
pub(super) mod map {
+ pub const END_INCLUSIVE: usize = 0xFFFF_FFFF;
+
pub const GPIO_OFFSET: usize = 0x0020_0000;
pub const UART_OFFSET: usize = 0x0020_1000;
@@ -22,6 +26,7 @@
pub const BASE: usize = 0x3F00_0000;
pub const GPIO_BASE: usize = BASE + GPIO_OFFSET;
pub const PL011_UART_BASE: usize = BASE + UART_OFFSET;
+ pub const END_INCLUSIVE: usize = 0x4000_FFFF;
}
/// Physical devices.
@@ -32,5 +37,6 @@
pub const BASE: usize = 0xFE00_0000;
pub const GPIO_BASE: usize = BASE + GPIO_OFFSET;
pub const PL011_UART_BASE: usize = BASE + UART_OFFSET;
+ pub const END_INCLUSIVE: usize = 0xFF84_FFFF;
}
}
diff -uNr 10_privilege_level/src/bsp.rs 11_virtual_memory/src/bsp.rs
--- 10_privilege_level/src/bsp.rs
+++ 11_virtual_memory/src/bsp.rs
@@ -4,7 +4,7 @@
//! Conditional re-exporting of Board Support Packages.
-mod device_driver;
+pub mod device_driver;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod raspberrypi;
diff -uNr 10_privilege_level/src/main.rs 11_virtual_memory/src/main.rs
--- 10_privilege_level/src/main.rs
+++ 11_virtual_memory/src/main.rs
@@ -11,10 +11,12 @@
//!
//! - [`bsp::console::console()`] - Returns a reference to the kernel's [console interface].
//! - [`bsp::driver::driver_manager()`] - Returns a reference to the kernel's [driver interface].
+//! - [`memory::mmu::mmu()`] - Returns a reference to the kernel's [MMU interface].
//! - [`time::time_manager()`] - Returns a reference to the kernel's [timer interface].
//!
//! [console interface]: ../libkernel/console/interface/index.html
//! [driver interface]: ../libkernel/driver/interface/trait.DriverManager.html
+//! [MMU interface]: ../libkernel/memory/mmu/interface/trait.MMU.html
//! [timer interface]: ../libkernel/time/interface/trait.TimeManager.html
//!
//! # Code organization and architecture
@@ -102,6 +104,8 @@
//! - `crate::memory::*`
//! - `crate::bsp::memory::*`
+#![allow(incomplete_features)]
+#![feature(const_generics)]
#![feature(format_args_nl)]
#![feature(naked_functions)]
#![feature(panic_info_message)]
@@ -129,9 +133,18 @@
/// # Safety
///
/// - Only a single core must be active and running this function.
-/// - The init calls in this function must appear in the correct order.
+/// - The init calls in this function must appear in the correct order:
+/// - Virtual memory must be activated before the device drivers.
+/// - Without it, any atomic operations, e.g. the yet-to-be-introduced spinlocks in the device
+/// drivers (which currently employ NullLocks instead of spinlocks), will fail to work on
+/// the RPi SoCs.
unsafe fn kernel_init() -> ! {
use driver::interface::DriverManager;
+ use memory::mmu::interface::MMU;
+
+ if let Err(string) = memory::mmu::mmu().init() {
+ panic!("MMU: {}", string);
+ }
for i in bsp::driver::driver_manager().all_device_drivers().iter() {
if i.init().is_err() {
@@ -154,6 +167,9 @@
info!("Booting on: {}", bsp::board_name());
+ info!("MMU online. Special regions:");
+ bsp::memory::mmu::virt_mem_layout().print_layout();
+
let (_, privilege_level) = exception::current_privilege_level();
info!("Current privilege level: {}", privilege_level);
@@ -177,6 +193,13 @@
info!("Timer test, spinning for 1 second");
time::time_manager().spin_for(Duration::from_secs(1));
+ let remapped_uart = unsafe { bsp::device_driver::PL011Uart::new(0x1FFF_1000) };
+ writeln!(
+ remapped_uart,
+ "[ !!! ] Writing through the remapped UART at 0x1FFF_1000"
+ )
+ .unwrap();
+
info!("Echoing input now");
loop {
let c = bsp::console::console().read_char();
diff -uNr 10_privilege_level/src/memory/mmu.rs 11_virtual_memory/src/memory/mmu.rs
--- 10_privilege_level/src/memory/mmu.rs
+++ 11_virtual_memory/src/memory/mmu.rs
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
+
+//! Memory Management Unit.
+//!
+//! In order to decouple `BSP` and `arch` parts of the MMU code (to keep them pluggable), this file
+//! provides types for composing an architecture-agnostic description of the kernel 's virtual
+//! memory layout.
+//!
+//! The `BSP` provides such a description through the `bsp::memory::mmu::virt_mem_layout()`
+//! function.
+//!
+//! The `MMU` driver of the `arch` code uses `bsp::memory::mmu::virt_mem_layout()` to compile and
+//! install respective page tables.
+
+#[cfg(target_arch = "aarch64")]
+#[path = "../_arch/aarch64/memory/mmu.rs"]
+mod arch_mmu;
+pub use arch_mmu::*;
+
+use core::{fmt, ops::RangeInclusive};
+
+//--------------------------------------------------------------------------------------------------
+// Public Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Memory Management interfaces.
+pub mod interface {
+
+ /// MMU functions.
+ pub trait MMU {
+ /// Called by the kernel during early init. Supposed to take the page tables from the
+ /// `BSP`-supplied `virt_mem_layout()` and install/activate them for the respective MMU.
+ ///
+ /// # Safety
+ ///
+ /// - Changes the HW's global state.
+ unsafe fn init(&self) -> Result<(), &'static str>;
+ }
+}
+
+/// Architecture agnostic translation types.
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum Translation {
+ Identity,
+ Offset(usize),
+}
+
+/// Architecture agnostic memory attributes.
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum MemAttributes {
+ CacheableDRAM,
+ Device,
+}
+
+/// Architecture agnostic access permissions.
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub enum AccessPermissions {
+ ReadOnly,
+ ReadWrite,
+}
+
+/// Collection of memory attributes.
+#[allow(missing_docs)]
+#[derive(Copy, Clone)]
+pub struct AttributeFields {
+ pub mem_attributes: MemAttributes,
+ pub acc_perms: AccessPermissions,
+ pub execute_never: bool,
+}
+
+/// Architecture agnostic descriptor for a memory range.
+#[allow(missing_docs)]
+pub struct RangeDescriptor {
+ pub name: &'static str,
+ pub virtual_range: fn() -> RangeInclusive<usize>,
+ pub translation: Translation,
+ pub attribute_fields: AttributeFields,
+}
+
+/// Type for expressing the kernel's virtual memory layout.
+pub struct KernelVirtualLayout<const NUM_SPECIAL_RANGES: usize> {
+ /// The last (inclusive) address of the address space.
+ max_virt_addr_inclusive: usize,
+
+ /// Array of descriptors for non-standard (normal cacheable DRAM) memory regions.
+ inner: [RangeDescriptor; NUM_SPECIAL_RANGES],
+}
+
+//--------------------------------------------------------------------------------------------------
+// Public Code
+//--------------------------------------------------------------------------------------------------
+
+impl Default for AttributeFields {
+ fn default() -> AttributeFields {
+ AttributeFields {
+ mem_attributes: MemAttributes::CacheableDRAM,
+ acc_perms: AccessPermissions::ReadWrite,
+ execute_never: true,
+ }
+ }
+}
+
+/// Human-readable output of a RangeDescriptor.
+impl fmt::Display for RangeDescriptor {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Call the function to which self.range points, and dereference the result, which causes
+ // Rust to copy the value.
+ let start = *(self.virtual_range)().start();
+ let end = *(self.virtual_range)().end();
+ let size = end - start + 1;
+
+ // log2(1024).
+ const KIB_RSHIFT: u32 = 10;
+
+ // log2(1024 * 1024).
+ const MIB_RSHIFT: u32 = 20;
+
+ let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
+ (size >> MIB_RSHIFT, "MiB")
+ } else if (size >> KIB_RSHIFT) > 0 {
+ (size >> KIB_RSHIFT, "KiB")
+ } else {
+ (size, "Byte")
+ };
+
+ let attr = match self.attribute_fields.mem_attributes {
+ MemAttributes::CacheableDRAM => "C",
+ MemAttributes::Device => "Dev",
+ };
+
+ let acc_p = match self.attribute_fields.acc_perms {
+ AccessPermissions::ReadOnly => "RO",
+ AccessPermissions::ReadWrite => "RW",
+ };
+
+ let xn = if self.attribute_fields.execute_never {
+ "PXN"
+ } else {
+ "PX"
+ };
+
+ write!(
+ f,
+ " {:#010x} - {:#010x} | {: >3} {} | {: <3} {} {: <3} | {}",
+ start, end, size, unit, attr, acc_p, xn, self.name
+ )
+ }
+}
+
+impl<const NUM_SPECIAL_RANGES: usize> KernelVirtualLayout<{ NUM_SPECIAL_RANGES }> {
+ /// Create a new instance.
+ pub const fn new(max: usize, layout: [RangeDescriptor; NUM_SPECIAL_RANGES]) -> Self {
+ Self {
+ max_virt_addr_inclusive: max,
+ inner: layout,
+ }
+ }
+
+ /// For a virtual address, find and return the output address and corresponding attributes.
+ ///
+ /// If the address is not found in `inner`, return an identity mapped default with normal
+ /// cacheable DRAM attributes.
+ pub fn get_virt_addr_properties(
+ &self,
+ virt_addr: usize,
+ ) -> Result<(usize, AttributeFields), &'static str> {
+ if virt_addr > self.max_virt_addr_inclusive {
+ return Err("Address out of range");
+ }
+
+ for i in self.inner.iter() {
+ if (i.virtual_range)().contains(&virt_addr) {
+ let output_addr = match i.translation {
+ Translation::Identity => virt_addr,
+ Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
+ };
+
+ return Ok((output_addr, i.attribute_fields));
+ }
+ }
+
+ Ok((virt_addr, AttributeFields::default()))
+ }
+
+ /// Print the memory layout.
+ pub fn print_layout(&self) {
+ use crate::info;
+
+ for i in self.inner.iter() {
+ info!("{}", i);
+ }
+ }
+}
diff -uNr 10_privilege_level/src/memory.rs 11_virtual_memory/src/memory.rs
--- 10_privilege_level/src/memory.rs
+++ 11_virtual_memory/src/memory.rs
@@ -4,6 +4,8 @@
//! Memory Management.
+pub mod mmu;
+
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
```

@ -477,3 +477,544 @@ General purpose register:
```
## Diff to previous
```diff
diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.rs 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs
--- 11_virtual_memory/src/_arch/aarch64/exception.rs
+++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs
@@ -4,7 +4,230 @@
//! Architectural synchronous and asynchronous exception handling.
-use cortex_a::regs::*;
+use core::fmt;
+use cortex_a::{asm, barrier, regs::*};
+use register::InMemoryRegister;
+
+// Assembly counterpart to this file.
+global_asm!(include_str!("exception.S"));
+
+//--------------------------------------------------------------------------------------------------
+// Private Definitions
+//--------------------------------------------------------------------------------------------------
+
+/// Wrapper struct for memory copy of SPSR_EL1.
+#[repr(transparent)]
+struct SpsrEL1(InMemoryRegister<u32, SPSR_EL1::Register>);
+
+/// The exception context as it is stored on the stack on exception entry.
+#[repr(C)]
+struct ExceptionContext {
+ /// General Purpose Registers.
+ gpr: [u64; 30],
+
+ /// The link register, aka x30.
+ lr: u64,
+
+ /// Exception link register. The program counter at the time the exception happened.
+ elr_el1: u64,
+
+ /// Saved program status.
+ spsr_el1: SpsrEL1,
+}
+
+/// Wrapper struct for pretty printing ESR_EL1.
+struct EsrEL1;
+
+//--------------------------------------------------------------------------------------------------
+// Private Code
+//--------------------------------------------------------------------------------------------------
+
+/// Print verbose information about the exception and the panic.
+fn default_exception_handler(e: &ExceptionContext) {
+ panic!(
+ "\n\nCPU Exception!\n\
+ FAR_EL1: {:#018x}\n\
+ {}\n\
+ {}",
+ FAR_EL1.get(),
+ EsrEL1 {},
+ e
+ );
+}
+
+//------------------------------------------------------------------------------
+// Current, EL0
+//------------------------------------------------------------------------------
+
+#[no_mangle]
+unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn current_el0_irq(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+//------------------------------------------------------------------------------
+// Current, ELx
+//------------------------------------------------------------------------------
+
+#[no_mangle]
+unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
+ let far_el1 = FAR_EL1.get();
+
+ // This catches the demo case for this tutorial. If the fault address happens to be 8 GiB,
+ // advance the exception link register for one instruction, so that execution can continue.
+ if far_el1 == 8 * 1024 * 1024 * 1024 {
+ e.elr_el1 += 4;
+
+ asm::eret()
+ }
+
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn current_elx_irq(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn current_elx_serror(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+//------------------------------------------------------------------------------
+// Lower, AArch64
+//------------------------------------------------------------------------------
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+//------------------------------------------------------------------------------
+// Lower, AArch32
+//------------------------------------------------------------------------------
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+#[no_mangle]
+unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext) {
+ default_exception_handler(e);
+}
+
+//------------------------------------------------------------------------------
+// Pretty printing
+//------------------------------------------------------------------------------
+
+/// Human readable ESR_EL1.
+#[rustfmt::skip]
+impl fmt::Display for EsrEL1 {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let esr_el1 = ESR_EL1.extract();
+
+ // Raw print of whole register.
+ writeln!(f, "ESR_EL1: {:#010x}", esr_el1.get())?;
+
+ // Raw print of exception class.
+ write!(f, " Exception Class (EC) : {:#x}", esr_el1.read(ESR_EL1::EC))?;
+
+ // Exception class, translation.
+ let ec_translation = match esr_el1.read_as_enum(ESR_EL1::EC) {
+ Some(ESR_EL1::EC::Value::DataAbortCurrentEL) => "Data Abort, current EL",
+ _ => "N/A",
+ };
+ writeln!(f, " - {}", ec_translation)?;
+
+ // Raw print of instruction specific syndrome.
+ write!(f, " Instr Specific Syndrome (ISS): {:#x}", esr_el1.read(ESR_EL1::ISS))?;
+
+ Ok(())
+ }
+}
+
+/// Human readable SPSR_EL1.
+#[rustfmt::skip]
+impl fmt::Display for SpsrEL1 {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Raw value.
+ writeln!(f, "SPSR_EL1: {:#010x}", self.0.get())?;
+
+ let to_flag_str = |x| -> _ {
+ if x { "Set" } else { "Not set" }
+ };
+
+ writeln!(f, " Flags:")?;
+ writeln!(f, " Negative (N): {}", to_flag_str(self.0.is_set(SPSR_EL1::N)))?;
+ writeln!(f, " Zero (Z): {}", to_flag_str(self.0.is_set(SPSR_EL1::Z)))?;
+ writeln!(f, " Carry (C): {}", to_flag_str(self.0.is_set(SPSR_EL1::C)))?;
+ writeln!(f, " Overflow (V): {}", to_flag_str(self.0.is_set(SPSR_EL1::V)))?;
+
+ let to_mask_str = |x| -> _ {
+ if x { "Masked" } else { "Unmasked" }
+ };
+
+ writeln!(f, " Exception handling state:")?;
+ writeln!(f, " Debug (D): {}", to_mask_str(self.0.is_set(SPSR_EL1::D)))?;
+ writeln!(f, " SError (A): {}", to_mask_str(self.0.is_set(SPSR_EL1::A)))?;
+ writeln!(f, " IRQ (I): {}", to_mask_str(self.0.is_set(SPSR_EL1::I)))?;
+ writeln!(f, " FIQ (F): {}", to_mask_str(self.0.is_set(SPSR_EL1::F)))?;
+
+ write!(f, " Illegal Execution State (IL): {}",
+ to_flag_str(self.0.is_set(SPSR_EL1::IL))
+ )?;
+
+ Ok(())
+ }
+}
+
+/// Human readable print of the exception context.
+impl fmt::Display for ExceptionContext {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "ELR_EL1: {:#018x}", self.elr_el1)?;
+ writeln!(f, "{}", self.spsr_el1)?;
+ writeln!(f)?;
+ writeln!(f, "General purpose register:")?;
+
+ #[rustfmt::skip]
+ let alternating = |x| -> _ {
+ if x modulo 2 == 0 { " " } else { "\n" }
+ };
+
+ // Print two registers per line.
+ for (i, reg) in self.gpr.iter().enumerate() {
+ write!(f, " x{: <2}: {: >#018x}{}", i, reg, alternating(i))?;
+ }
+ write!(f, " lr : {:#018x}", self.lr)?;
+
+ Ok(())
+ }
+}
//--------------------------------------------------------------------------------------------------
// Public Code
@@ -21,3 +244,24 @@
_ => (PrivilegeLevel::Unknown, "Unknown"),
}
}
+
+/// Init exception handling by setting the exception vector base address register.
+///
+/// # Safety
+///
+/// - Changes the HW state of the executing core.
+/// - The vector table and the symbol `__exception_vector_table_start` from the linker script must
+/// adhere to the alignment and size constraints demanded by the ARMv8-A Architecture Reference
+/// Manual.
+pub unsafe fn handling_init() {
+ // Provided by exception.S.
+ extern "C" {
+ static mut __exception_vector_start: u64;
+ }
+ let addr: u64 = &__exception_vector_start as *const _ as u64;
+
+ VBAR_EL1.set(addr);
+
+ // Force VBAR update to complete before next instruction.
+ barrier::isb(barrier::SY);
+}
diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.S 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S
--- 11_virtual_memory/src/_arch/aarch64/exception.S
+++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
+
+/// Call the function provided by parameter `\handler` after saving the exception context. Provide
+/// the context as the first parameter to '\handler'.
+.macro CALL_WITH_CONTEXT handler
+ // Make room on the stack for the exception context.
+ sub sp, sp, #16 * 17
+
+ // Store all general purpose registers on the stack.
+ stp x0, x1, [sp, #16 * 0]
+ stp x2, x3, [sp, #16 * 1]
+ stp x4, x5, [sp, #16 * 2]
+ stp x6, x7, [sp, #16 * 3]
+ stp x8, x9, [sp, #16 * 4]
+ stp x10, x11, [sp, #16 * 5]
+ stp x12, x13, [sp, #16 * 6]
+ stp x14, x15, [sp, #16 * 7]
+ stp x16, x17, [sp, #16 * 8]
+ stp x18, x19, [sp, #16 * 9]
+ stp x20, x21, [sp, #16 * 10]
+ stp x22, x23, [sp, #16 * 11]
+ stp x24, x25, [sp, #16 * 12]
+ stp x26, x27, [sp, #16 * 13]
+ stp x28, x29, [sp, #16 * 14]
+
+ // Add the exception link register (ELR_EL1) and the saved program status (SPSR_EL1).
+ mrs x1, ELR_EL1
+ mrs x2, SPSR_EL1
+
+ stp lr, x1, [sp, #16 * 15]
+ str w2, [sp, #16 * 16]
+
+ // x0 is the first argument for the function called through `\handler`.
+ mov x0, sp
+
+ // Call `\handler`.
+ bl \handler
+
+ // After returning from exception handling code, replay the saved context and return via `eret`.
+ b __exception_restore_context
+.endm
+
+.macro FIQ_SUSPEND
+1: wfe
+ b 1b
+.endm
+
+//--------------------------------------------------------------------------------------------------
+// The exception vector table.
+//--------------------------------------------------------------------------------------------------
+.section .exception_vectors, "ax", @progbits
+
+// Align by 2^11 bytes, as demanded by ARMv8-A. Same as ALIGN(2048) in an ld script.
+.align 11
+
+// Export a symbol for the Rust code to use.
+__exception_vector_start:
+
+// Current exception level with SP_EL0.
+//
+// .org sets the offset relative to section start.
+//
+// # Safety
+//
+// - It must be ensured that `CALL_WITH_CONTEXT` <= 0x80 bytes.
+.org 0x000
+ CALL_WITH_CONTEXT current_el0_synchronous
+.org 0x080
+ CALL_WITH_CONTEXT current_el0_irq
+.org 0x100
+ FIQ_SUSPEND
+.org 0x180
+ CALL_WITH_CONTEXT current_el0_serror
+
+// Current exception level with SP_ELx, x > 0.
+.org 0x200
+ CALL_WITH_CONTEXT current_elx_synchronous
+.org 0x280
+ CALL_WITH_CONTEXT current_elx_irq
+.org 0x300
+ FIQ_SUSPEND
+.org 0x380
+ CALL_WITH_CONTEXT current_elx_serror
+
+// Lower exception level, AArch64
+.org 0x400
+ CALL_WITH_CONTEXT lower_aarch64_synchronous
+.org 0x480
+ CALL_WITH_CONTEXT lower_aarch64_irq
+.org 0x500
+ FIQ_SUSPEND
+.org 0x580
+ CALL_WITH_CONTEXT lower_aarch64_serror
+
+// Lower exception level, AArch32
+.org 0x600
+ CALL_WITH_CONTEXT lower_aarch32_synchronous
+.org 0x680
+ CALL_WITH_CONTEXT lower_aarch32_irq
+.org 0x700
+ FIQ_SUSPEND
+.org 0x780
+ CALL_WITH_CONTEXT lower_aarch32_serror
+.org 0x800
+
+//--------------------------------------------------------------------------------------------------
+// Helper functions
+//--------------------------------------------------------------------------------------------------
+.section .text
+
+__exception_restore_context:
+ ldr w19, [sp, #16 * 16]
+ ldp lr, x20, [sp, #16 * 15]
+
+ msr SPSR_EL1, x19
+ msr ELR_EL1, x20
+
+ ldp x0, x1, [sp, #16 * 0]
+ ldp x2, x3, [sp, #16 * 1]
+ ldp x4, x5, [sp, #16 * 2]
+ ldp x6, x7, [sp, #16 * 3]
+ ldp x8, x9, [sp, #16 * 4]
+ ldp x10, x11, [sp, #16 * 5]
+ ldp x12, x13, [sp, #16 * 6]
+ ldp x14, x15, [sp, #16 * 7]
+ ldp x16, x17, [sp, #16 * 8]
+ ldp x18, x19, [sp, #16 * 9]
+ ldp x20, x21, [sp, #16 * 10]
+ ldp x22, x23, [sp, #16 * 11]
+ ldp x24, x25, [sp, #16 * 12]
+ ldp x26, x27, [sp, #16 * 13]
+ ldp x28, x29, [sp, #16 * 14]
+
+ add sp, sp, #16 * 17
+
+ eret
diff -uNr 11_virtual_memory/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 12_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
--- 11_virtual_memory/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
+++ 12_exceptions_part1_groundwork/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs
@@ -118,7 +118,6 @@
//--------------------------------------------------------------------------------------------------
register_structs! {
- #[allow(missing_docs)]
#[allow(non_snake_case)]
pub RegisterBlock {
(0x00 => DR: ReadWrite<u32>),
@@ -135,7 +134,6 @@
}
}
-#[allow(missing_docs)]
pub struct PL011UartInner {
base_addr: usize,
chars_written: usize,
diff -uNr 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs
--- 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs
+++ 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs
@@ -12,7 +12,7 @@
// Public Definitions
//--------------------------------------------------------------------------------------------------
-const NUM_MEM_RANGES: usize = 3;
+const NUM_MEM_RANGES: usize = 2;
/// The virtual memory layout.
///
@@ -55,19 +55,6 @@
},
},
RangeDescriptor {
- name: "Remapped Device MMIO",
- virtual_range: || {
- // The last 64 KiB slot in the first 512 MiB
- RangeInclusive::new(0x1FFF_0000, 0x1FFF_FFFF)
- },
- translation: Translation::Offset(memory::map::mmio::BASE + 0x20_0000),
- attribute_fields: AttributeFields {
- mem_attributes: MemAttributes::Device,
- acc_perms: AccessPermissions::ReadWrite,
- execute_never: true,
- },
- },
- RangeDescriptor {
name: "Device MMIO",
virtual_range: || {
RangeInclusive::new(memory::map::mmio::BASE, memory::map::mmio::END_INCLUSIVE)
diff -uNr 11_virtual_memory/src/bsp.rs 12_exceptions_part1_groundwork/src/bsp.rs
--- 11_virtual_memory/src/bsp.rs
+++ 12_exceptions_part1_groundwork/src/bsp.rs
@@ -4,7 +4,7 @@
//! Conditional re-exporting of Board Support Packages.
-pub mod device_driver;
+mod device_driver;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod raspberrypi;
diff -uNr 11_virtual_memory/src/main.rs 12_exceptions_part1_groundwork/src/main.rs
--- 11_virtual_memory/src/main.rs
+++ 12_exceptions_part1_groundwork/src/main.rs
@@ -107,6 +107,7 @@
#![allow(incomplete_features)]
#![feature(const_generics)]
#![feature(format_args_nl)]
+#![feature(global_asm)]
#![feature(naked_functions)]
#![feature(panic_info_message)]
#![feature(trait_alias)]
@@ -142,6 +143,8 @@
use driver::interface::DriverManager;
use memory::mmu::interface::MMU;
+ exception::handling_init();
+
if let Err(string) = memory::mmu::mmu().init() {
panic!("MMU: {}", string);
}
@@ -193,13 +196,28 @@
info!("Timer test, spinning for 1 second");
time::time_manager().spin_for(Duration::from_secs(1));
- let remapped_uart = unsafe { bsp::device_driver::PL011Uart::new(0x1FFF_1000) };
- writeln!(
- remapped_uart,
- "[ !!! ] Writing through the remapped UART at 0x1FFF_1000"
- )
- .unwrap();
+ // Cause an exception by accessing a virtual address for which no translation was set up. This
+ // code accesses the address 8 GiB, which is outside the mapped address space.
+ //
+ // For demo purposes, the exception handler will catch the faulting 8 GiB address and allow
+ // execution to continue.
+ info!("");
+ info!("Trying to write to address 8 GiB...");
+ let mut big_addr: u64 = 8 * 1024 * 1024 * 1024;
+ unsafe { core::ptr::read_volatile(big_addr as *mut u64) };
+
+ info!("************************************************");
+ info!("Whoa! We recovered from a synchronous exception!");
+ info!("************************************************");
+ info!("");
+ info!("Let's try again");
+
+ // Now use address 9 GiB. The exception handler won't forgive us this time.
+ info!("Trying to write to address 9 GiB...");
+ big_addr = 9 * 1024 * 1024 * 1024;
+ unsafe { core::ptr::read_volatile(big_addr as *mut u64) };
+ // Will never reach here in this tutorial.
info!("Echoing input now");
loop {
let c = bsp::console::console().read_char();
diff -uNr 11_virtual_memory/src/memory/mmu.rs 12_exceptions_part1_groundwork/src/memory/mmu.rs
--- 11_virtual_memory/src/memory/mmu.rs
+++ 12_exceptions_part1_groundwork/src/memory/mmu.rs
@@ -42,6 +42,7 @@
/// Architecture agnostic translation types.
#[allow(missing_docs)]
+#[allow(dead_code)]
#[derive(Copy, Clone)]
pub enum Translation {
Identity,
```

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save