diff --git a/13_integrated_testing/Cargo.toml b/13_integrated_testing/Cargo.toml index fd66117d..0fe3c710 100644 --- a/13_integrated_testing/Cargo.toml +++ b/13_integrated_testing/Cargo.toml @@ -44,5 +44,5 @@ name = "00_interface_sanity_console" harness = false [[test]] -name = "02_arch_exception_handling" +name = "02_arch_exception_handling_sync_page_fault" harness = false diff --git a/13_integrated_testing/Makefile b/13_integrated_testing/Makefile index 1d7c1525..46559a84 100644 --- a/13_integrated_testing/Makefile +++ b/13_integrated_testing/Makefile @@ -22,7 +22,7 @@ ifeq ($(BSP),rpi3) QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg JTAG_BOOT_IMAGE = jtag_boot_rpi3.img - LINKER_FILE = src/bsp/rpi/link.ld + LINKER_FILE = src/bsp/raspberrypi/link.ld RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 else ifeq ($(BSP),rpi4) TARGET = aarch64-unknown-none-softfloat @@ -33,7 +33,7 @@ else ifeq ($(BSP),rpi4) # QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi4.cfg JTAG_BOOT_IMAGE = jtag_boot_rpi4.img - LINKER_FILE = src/bsp/rpi/link.ld + LINKER_FILE = src/bsp/raspberrypi/link.ld RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif @@ -89,8 +89,7 @@ $(OUTPUT): $(CARGO_OUTPUT) $(OBJCOPY_CMD) $< $(OUTPUT) doc: - cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items - xdg-open target/$(TARGET)/doc/libkernel/index.html + cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items --open ifeq ($(QEMU_MACHINE_TYPE),) qemu: diff --git a/13_integrated_testing/README.md b/13_integrated_testing/README.md index 32e481ef..0681346b 100644 --- a/13_integrated_testing/README.md +++ b/13_integrated_testing/README.md @@ -2,13 +2,12 @@ ## tl;dr -- We implement our own test framework using `Rust`'s [custom_test_frameworks] - feature by enabling `Unit Tests` and `Integration Tests` using `QEMU`. -- It is also possible to have test automation for the kernel's `console` - (provided over `UART` in our case): Sending strings/characters to the console - and expecting specific answers in return. +- We implement our own test framework using `Rust`'s [custom_test_frameworks] feature by enabling + `Unit Tests` and `Integration Tests` using `QEMU`. +- It is also possible to have test automation for the kernel's `console` (provided over `UART` in + our case): Sending strings/characters to the console and expecting specific answers in return. - + ## Table of Contents @@ -54,10 +53,11 @@ testing facilities: ## Challenges Testing Rust `#![no_std]` code like our kernel is, at the point of writing this tutorial, not an -easy endeavor. The short version is: We cannot use Rust's [native testing -framework](https://doc.rust-lang.org/book/ch11-00-testing.html) straight away. Utilizing the -`#[test]` attribute macro and running `cargo test` (`xtest` in our case) would throw compilation -errors, because there are dependencies on the standard library. +easy endeavor. The short version is: We cannot use Rust's [native testing framework] straight away. +Utilizing the `#[test]` attribute macro and running `cargo test` (`xtest` in our case) would throw +compilation errors, because there are dependencies on the standard library. + +[native testing framework]: https://doc.rust-lang.org/book/ch11-00-testing.html We have to fall back to Rust's unstable [custom_test_frameworks] feature. It relieves us from dependencies on the standard library, but comes at the cost of having a reduced feature set. Instead @@ -97,6 +97,8 @@ happens when `make test` aka `cargo xtest` runs. Until now, our kernel was a so-called `binary crate`. As [explained in the official Rust book], this crate type disallows having `integration tests`. Quoting the book: +[explained in the official Rust book]: https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests-for-binary-crates + > If our project is a binary crate that only contains a _src/main.rs_ file and doesn’t have a > _src/lib.rs_ file, we can’t create integration tests in the _tests_ directory and bring functions > defined in the _src/main.rs_ file into scope with a `use` statement. Only library crates expose @@ -129,8 +131,6 @@ name = "kernel" test = false ``` -[explained in the official Rust book]: https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests-for-binary-crates - ### Enabling `custom_test_frameworks` for Unit Tests In `lib.rs`, we add the following headers to get started with `custom_test_frameworks`: @@ -183,9 +183,11 @@ pub fn test_runner(tests: &[&test_types::UnitTest]) { The function signature shows that `test_runner` takes one argument: A slice of `test_types::UnitTest` references. This type definition lives in an external crate stored at `$ROOT/test_types`. It is external because the type is also needed for a self-made [procedural -macro](https://doc.rust-lang.org/reference/procedural-macros.html) that we'll use to write unit -tests, and procedural macros _have_ to live in their own crate. So to avoid a circular dependency -between kernel and proc-macro, this split was needed. Anyways, here is the type definition: +macro] that we'll use to write unit tests, and procedural macros _have_ to live in their own crate. +So to avoid a circular dependency between kernel and proc-macro, this split was needed. Anyways, +here is the type definition: + +[procedural macro]: https://doc.rust-lang.org/reference/procedural-macros.html ```rust /// Unit test container. @@ -214,17 +216,17 @@ call chain during kernel boot: | | Function | File | | - | - | - | | 1. | `_start()` | `lib.rs` | -| 2. | (some more arch code) | `lib.rs` | +| 2. | (some more arch64 code) | `lib.rs` | | 3. | `runtime_init()` | `lib.rs` | | 4. | `kernel_init()` | `main.rs` | | 5. | `kernel_main()` | `main.rs` | A function named `main` is never called. Hence, the `main()` function generated by `cargo xtest` - would be silently dropped, and therefore the tests would never be executed. As you can see, - `runtime_init()` is the last function residing in our carved-out `lib.rs`, and it calls into - `kernel_init()`. So in order to get the tests to execute, we add a test-environment version of - `kernel_init()` to `lib.rs` as well (conditional compilation ensures it is only present when the - test flag is set), and call the `cargo xtest` generated `main()` function from there. +would be silently dropped, and therefore the tests would never be executed. As you can see, +`runtime_init()` is the last function residing in our carved-out `lib.rs`, and it calls into +`kernel_init()`. So in order to get the tests to execute, we add a test-environment version of +`kernel_init()` to `lib.rs` as well (conditional compilation ensures it is only present when the +test flag is set), and call the `cargo xtest` generated `main()` function from there. This is where `#![reexport_test_harness_main = "test_main"]` finally comes into picture. It declares the name of the generated main function so that we can manually call it. Here is the final @@ -235,24 +237,26 @@ implementation in `lib.rs`: #[cfg(test)] #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + bsp::console::qemu_bring_up_console(); test_main(); - arch::qemu_exit_success() + cpu::qemu_exit_success() } ``` -Note that we first call `bsp::qemu_bring_up_console()`. Since we are running all our tests inside -`QEMU`, we need to ensure that whatever peripheral implements the kernel's `console` is initialized, -so that we can print from our tests. If you recall [tutorial 03](../03_hacky_hello_world), bringing +Note that we first call `bsp::console::qemu_bring_up_console()`. Since we are running all our tests +inside `QEMU`, we need to ensure that whatever peripheral implements the kernel's `console` +interface is initialized, so that we can print from our tests. If you recall [tutorial 03], bringing up peripherals in `QEMU` might not need the full initialization as is needed on real hardware -(setting clocks, config registers, etc...) due to the abstractions in `QEMU`'s emulation code, so +(setting clocks, config registers, etc...) due to the abstractions in `QEMU`'s emulation code. So this is an opportunity to cut down on setup code. -As a matter of fact, for the `RPis`, nothing needs to be done and the function is empy. But this -might be different for other hardware emulated by QEMU, so it makes sense to introduce the function -now to make it easier in case new `BSPs` are added to the kernel in the future. +[tutorial 03]: ../03_hacky_hello_world + +As a matter of fact, for the `Raspberrys`, nothing needs to be done and the function is empy. But +this might be different for other hardware emulated by QEMU, so it makes sense to introduce the +function now to make it easier in case new `BSPs` are added to the kernel in the future. Next, the reexported `test_main()` is called, which will call our `test_runner()` which finally prints the unit test names and executes them. @@ -286,20 +290,20 @@ qemu_exit::aarch64::exit_success() // QEMU binary executes `exit(0)`. qemu_exit::aarch64::exit_failure() // QEMU binary executes `exit(1)`. ``` -[Click here](https://github.com/andre-richter/qemu-exit/blob/master/src/aarch64.rs) in case you are -interested in the implementation. Note that for the functions to work, the `-semihosting` flag must -be added to the `QEMU` invocation. +[Click here] in case you are interested in the implementation. Note that for the functions to work, +the `-semihosting` flag must be added to the `QEMU` invocation. [exit status]: https://en.wikipedia.org/wiki/Exit_status [@phil-opp]: https://github.com/phil-opp [learned how to do this]: https://os.phil-opp.com/testing/#exiting-qemu [semihosting]: https://static.docs.arm.com/100863/0200/semihosting.pdf [qemu-exit]: https://github.com/andre-richter/qemu-exit +[Click here]: https://github.com/andre-richter/qemu-exit/blob/master/src/aarch64.rs #### Exiting Unit Tests Unit test failure shall be triggered by the `panic!` macro, either directly or by way of using -`assert!` macros. Until now, our `panic!` implementation finally called `arch::wait_forever()` to +`assert!` macros. Until now, our `panic!` implementation finally called `cpu::wait_forever()` to safely park the panicked CPU core in a busy loop. This can't be used for the unit tests, because `cargo` would wait forever for `QEMU` to exit and stall the whole test run. Again, conditional compilation is used to differentiate between a release and testing version of how a `panic!` @@ -310,12 +314,12 @@ concludes. Here is the new testing version: #[cfg(test)] #[no_mangle] fn _panic_exit() -> ! { - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } ``` In case none of the unit tests panicked, `lib.rs`'s `kernel_init()` calls -`arch::qemu_exit_success()` to successfully conclude the unit test run. +`cpu::qemu_exit_success()` to successfully conclude the unit test run. ### Controlling Test Kernel Execution @@ -434,7 +438,7 @@ mod tests { const TEST1: test_types::UnitTest = test_types::UnitTest { name: "test_runner_executes_in_kernel_mode", test_func: || { - let (level, _) = state::current_privilege_level(); + let (level, _) = current_privilege_level(); assert!(level == PrivilegeLevel::Kernel) }, @@ -451,9 +455,13 @@ macro] named `#[kernel_test]` to simplify this. It should work this way: 2. Populating the `test_func` member with a closure that executes the body of the attributed function. -For the sake of brevity, we're not going to discuss the macro implementation. [Click -here](test-macros/src/lib.rs) if you're interested in it. Using the macro, the example shown before -now boils down to this (this is now an actual example from [arch.rs](src/arch.rs)): +For the sake of brevity, we're not going to discuss the macro implementation. [The source is in the +test-macros crate] if you're interested in it. Using the macro, the example shown before now boils +down to this (this is now an actual example from [exception.rs]: + +[procedural macro]: https://doc.rust-lang.org/reference/procedural-macros.html +[The source is in the test-macros crate]: test-macros/src/lib.rs +[exception.rs]: src/exception.rs ```rust #[cfg(test)] @@ -464,7 +472,7 @@ mod tests { /// Libkernel unit tests must execute in kernel mode. #[kernel_test] fn test_runner_executes_in_kernel_mode() { - let (level, _) = state::current_privilege_level(); + let (level, _) = current_privilege_level(); assert!(level == PrivilegeLevel::Kernel) } @@ -476,8 +484,6 @@ Note that since proc macros need to live in their own crates, we need to create Aaaaaand that's how you write unit tests. We're finished with that part for good now :raised_hands:. -[procedural macro]: https://doc.rust-lang.org/reference/procedural-macros.html - ### Integration Tests We are still not done with the tutorial, though :scream:. @@ -509,30 +515,30 @@ your test code into individual chunks. For example, take a look at mod panic_exit_failure; use core::time::Duration; -use libkernel::{arch, arch::timer, bsp, interface::time::Timer}; +use libkernel::{bsp, cpu, time, time::interface::TimeManager}; use test_macros::kernel_test; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + bsp::console::qemu_bring_up_console(); // Depending on CPU arch, some timer bring-up code could go here. Not needed for the RPi. test_main(); - arch::qemu_exit_success() + cpu::qemu_exit_success() } /// Simple check that the timer is running. #[kernel_test] fn timer_is_counting() { - assert!(timer().uptime().as_nanos() > 0) + assert!(time::time_manager().uptime().as_nanos() > 0) } /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { - assert!(timer().resolution().as_nanos() < 100) + assert!(time::time_manager().resolution().as_nanos() < 100) } ``` @@ -554,7 +560,7 @@ name = "00_interface_sanity_console" harness = false [[test]] -name = "02_arch_exception_handling" +name = "02_arch_exception_handling_sync_page_fault" harness = false ``` @@ -574,7 +580,7 @@ One way to navigate around this is to declare the _release version of the panic #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - arch::wait_forever() + cpu::wait_forever() } ``` @@ -582,8 +588,8 @@ fn _panic_exit() -> ! { Integration tests in `$CRATE/tests/` can now override it according to their needs, because depending on the kind of test, a `panic!` could mean success or failure. For example, -`tests/02_arch_exception_handling.rs` is intentionally causing a page fault, so the wanted outcome -is a `panic!`. Here is the whole test (minus some inline comments): +`tests/02_arch_exception_handling_sync_page_fault.rs` is intentionally causing a page fault, so the +wanted outcome is a `panic!`. Here is the whole test (minus some inline comments): ```rust //! Page faults must result in synchronous exceptions. @@ -594,20 +600,22 @@ is a `panic!`. Here is the whole test (minus some inline comments): mod panic_exit_success; -use libkernel::{arch, bsp, interface::mm::MMU, println}; +use libkernel::{bsp, cpu, exception, memory, println}; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + use memory::mmu::interface::MMU; + + bsp::console::qemu_bring_up_console(); println!("Testing synchronous exception handling by causing a page fault"); println!("-------------------------------------------------------------------\n"); - arch::enable_exception_handling(); + exception::handling_init(); - if let Err(string) = arch::mmu().init() { + if let Err(string) = memory::mmu::mmu().init() { println!("MMU: {}", string); - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } println!("Writing beyond mapped area to address 9 GiB..."); @@ -615,7 +623,7 @@ unsafe fn kernel_init() -> ! { core::ptr::read_volatile(big_addr as *mut u64); // If execution reaches here, the memory access above did not cause a page fault exception. - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } ``` @@ -666,16 +674,19 @@ The subtest first sends `"ABC"` over the console to the kernel, and then expects mod panic_exit_failure; -use libkernel::{bsp, interface::console::*, print}; +use libkernel::{bsp, console, print}; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + use bsp::console::{console, qemu_bring_up_console}; + use console::interface::*; + + qemu_bring_up_console(); // Handshake - assert_eq!(bsp::console().read_char(), 'A'); - assert_eq!(bsp::console().read_char(), 'B'); - assert_eq!(bsp::console().read_char(), 'C'); + assert_eq!(console().read_char(), 'A'); + assert_eq!(console().read_char(), 'B'); + assert_eq!(console().read_char(), 'C'); print!("OK1234"); ``` @@ -689,7 +700,7 @@ Believe it or not, that is all. There are three ways you can run tests: - For example, `TEST=01_interface_sanity_timer make test` ```console -» make test +$ make test [...] RUSTFLAGS="-C link-arg=-Tsrc/bsp/rpi/link.ld -C target-cpu=cortex-a53 -D warnings -D missing_docs" cargo xtest --target=aarch64-unknown-none-softfloat --features bsp_rpi3 --release Finished release [optimized] target(s) in 0.01s @@ -737,7 +748,7 @@ RUSTFLAGS="-C link-arg=-Tsrc/bsp/rpi/link.ld -C target-cpu=cortex-a53 -D warning ------------------------------------------------------------------- - Running target/aarch64-unknown-none-softfloat/release/deps/02_arch_exception_handling-8e8e460dd9041f11 + Running target/aarch64-unknown-none-softfloat/release/deps/02_arch_exception_handling_sync_page_fault-8e8e460dd9041f11 ------------------------------------------------------------------- 🦀 Testing synchronous exception handling by causing a page fault ------------------------------------------------------------------- @@ -752,1123 +763,8 @@ RUSTFLAGS="-C link-arg=-Tsrc/bsp/rpi/link.ld -C target-cpu=cortex-a53 -D warning [...] ------------------------------------------------------------------- - ✅ Success: 02_arch_exception_handling + ✅ Success: 02_arch_exception_handling_sync_page_fault ------------------------------------------------------------------- ``` ## Diff to previous -```diff - -diff -uNr 12_cpu_exceptions_part1/.cargo/config 13_integrated_testing/.cargo/config ---- 12_cpu_exceptions_part1/.cargo/config -+++ 13_integrated_testing/.cargo/config -@@ -0,0 +1,2 @@ -+[target.'cfg(target_os = "none")'] -+runner = "target/kernel_test_runner.sh" - -diff -uNr 12_cpu_exceptions_part1/Cargo.toml 13_integrated_testing/Cargo.toml ---- 12_cpu_exceptions_part1/Cargo.toml -+++ 13_integrated_testing/Cargo.toml -@@ -14,7 +14,35 @@ - bsp_rpi4 = ["cortex-a", "register"] - - [dependencies] -+qemu-exit = "0.1.x" -+test-types = { path = "test-types" } - - # Optional dependencies - cortex-a = { version = "2.9.x", optional = true } --register = { version = "0.5.x", optional = true } -+register = { version = "0.5.x", features=["no_std_unit_tests"], optional = true } -+ -+##-------------------------------------------------------------------------------------------------- -+## Testing -+##-------------------------------------------------------------------------------------------------- -+ -+[dev-dependencies] -+test-macros = { path = "test-macros" } -+ -+# Unit tests are done in the library part of the kernel. -+[lib] -+name = "libkernel" -+test = true -+ -+# Disable unit tests for the kernel binary. -+[[bin]] -+name = "kernel" -+test = false -+ -+# List of tests without harness. -+[[test]] -+name = "00_interface_sanity_console" -+harness = false -+ -+[[test]] -+name = "02_arch_exception_handling" -+harness = false - -diff -uNr 12_cpu_exceptions_part1/Makefile 13_integrated_testing/Makefile ---- 12_cpu_exceptions_part1/Makefile -+++ 13_integrated_testing/Makefile -@@ -19,6 +19,7 @@ - QEMU_BINARY = qemu-system-aarch64 - QEMU_MACHINE_TYPE = raspi3 - QEMU_RELEASE_ARGS = -serial stdio -display none -+ QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting - OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg - JTAG_BOOT_IMAGE = jtag_boot_rpi3.img - LINKER_FILE = src/bsp/rpi/link.ld -@@ -29,21 +30,34 @@ - # QEMU_BINARY = qemu-system-aarch64 - # QEMU_MACHINE_TYPE = - # QEMU_RELEASE_ARGS = -serial stdio -display none -+ # QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting - OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi4.cfg - JTAG_BOOT_IMAGE = jtag_boot_rpi4.img - LINKER_FILE = src/bsp/rpi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - endif - -+# Testing-specific arguments -+ifdef TEST -+ ifeq ($(TEST),unit) -+ TEST_ARG = --lib -+ else -+ TEST_ARG = --test $(TEST) -+ endif -+endif -+ -+QEMU_MISSING_STRING = "This board is not yet supported for QEMU." -+ - RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) - RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs - - SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) $(wildcard **/*.ld) - --XRUSTC_CMD = cargo xrustc \ -- --target=$(TARGET) \ -- --features bsp_$(BSP) \ -+X_CMD_ARGS = --target=$(TARGET) \ -+ --features bsp_$(BSP) \ - --release -+XRUSTC_CMD = cargo xrustc $(X_CMD_ARGS) -+XTEST_CMD = cargo xtest $(X_CMD_ARGS) - - CARGO_OUTPUT = target/$(TARGET)/release/kernel - -@@ -53,7 +67,8 @@ - -O binary - - DOCKER_IMAGE = rustembedded/osdev-utils --DOCKER_CMD = docker run -it --rm -+DOCKER_CMD_TEST = docker run -i --rm -+DOCKER_CMD_USER = $(DOCKER_CMD_TEST) -t - 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 -@@ -62,7 +77,7 @@ - DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) - DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb - --.PHONY: all doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy clean readelf objdump nm -+.PHONY: all doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy clean readelf objdump nm test - - all: clean $(OUTPUT) - -@@ -75,36 +90,55 @@ - - doc: - cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items -- xdg-open target/$(TARGET)/doc/kernel/index.html -+ xdg-open target/$(TARGET)/doc/libkernel/index.html - - ifeq ($(QEMU_MACHINE_TYPE),) - qemu: -- @echo "This board is not yet supported for QEMU." -+ @echo $(QEMU_MISSING_STRING) -+ -+test: -+ @echo $(QEMU_MISSING_STRING) - else - qemu: all -- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \ -- $(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ -+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \ -+ $(DOCKER_EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ - -kernel $(OUTPUT) -+ -+define KERNEL_TEST_RUNNER -+ #!/usr/bin/env bash -+ -+ $(OBJCOPY_CMD) $$1 $$1.img -+ TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') -+ $(DOCKER_CMD_TEST) $(DOCKER_ARG_DIR_TUT) $(DOCKER_IMAGE) \ -+ ruby tests/runner.rb $(DOCKER_EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -+endef -+ -+export KERNEL_TEST_RUNNER -+test: $(SOURCES) -+ @mkdir -p target -+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh -+ @chmod +x target/kernel_test_runner.sh -+ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(XTEST_CMD) $(TEST_ARG) - endif - - chainboot: all -- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_TTY) \ -- $(DOCKER_IMAGE) $(DOCKER_EXEC_MINIPUSH) $(DEV_SERIAL) \ -+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_TTY) \ -+ $(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) \ -+ @$(DOCKER_CMD_USER) $(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) \ -+ @$(DOCKER_CMD_USER) $(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) \ -+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \ - gdb-multiarch -q kernel_for_jtag - endef - - -diff -uNr 12_cpu_exceptions_part1/src/arch/aarch64/exception.rs 13_integrated_testing/src/arch/aarch64/exception.rs ---- 12_cpu_exceptions_part1/src/arch/aarch64/exception.rs -+++ 13_integrated_testing/src/arch/aarch64/exception.rs -@@ -5,7 +5,7 @@ - //! Exception handling. - - use core::fmt; --use cortex_a::{asm, barrier, regs::*}; -+use cortex_a::{barrier, regs::*}; - use register::InMemoryRegister; - - // Assembly counterpart to this file. -@@ -74,16 +74,6 @@ - /// Asynchronous exception taken from the current EL, using SP of the current EL. - #[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); - } - - -diff -uNr 12_cpu_exceptions_part1/src/arch/aarch64.rs 13_integrated_testing/src/arch/aarch64.rs ---- 12_cpu_exceptions_part1/src/arch/aarch64.rs -+++ 13_integrated_testing/src/arch/aarch64.rs -@@ -155,3 +155,17 @@ - info!(" FIQ: {}", to_mask_str(exception::is_masked::())); - } - } -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+/// Make the host QEMU binary execute `exit(1)`. -+pub fn qemu_exit_failure() -> ! { -+ qemu_exit::aarch64::exit_failure() -+} -+ -+/// Make the host QEMU binary execute `exit(0)`. -+pub fn qemu_exit_success() -> ! { -+ qemu_exit::aarch64::exit_success() -+} - -diff -uNr 12_cpu_exceptions_part1/src/arch.rs 13_integrated_testing/src/arch.rs ---- 12_cpu_exceptions_part1/src/arch.rs -+++ 13_integrated_testing/src/arch.rs -@@ -19,3 +19,21 @@ - Hypervisor, - Unknown, - } -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use test_macros::kernel_test; -+ -+ /// Libkernel unit tests must execute in kernel mode. -+ #[kernel_test] -+ fn test_runner_executes_in_kernel_mode() { -+ let (level, _) = state::current_privilege_level(); -+ -+ assert!(level == PrivilegeLevel::Kernel) -+ } -+} - -diff -uNr 12_cpu_exceptions_part1/src/bsp/driver/bcm/bcm2xxx_gpio.rs 13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_gpio.rs ---- 12_cpu_exceptions_part1/src/bsp/driver/bcm/bcm2xxx_gpio.rs -+++ 13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_gpio.rs -@@ -6,7 +6,7 @@ - - use crate::{arch, arch::sync::NullLock, interface}; - use core::ops; --use register::{mmio::ReadWrite, register_bitfields, register_structs}; -+use register::{mmio::*, register_bitfields, register_structs}; - - // GPIO registers. - // - -diff -uNr 12_cpu_exceptions_part1/src/bsp/rpi/virt_mem_layout.rs 13_integrated_testing/src/bsp/rpi/virt_mem_layout.rs ---- 12_cpu_exceptions_part1/src/bsp/rpi/virt_mem_layout.rs -+++ 13_integrated_testing/src/bsp/rpi/virt_mem_layout.rs -@@ -67,3 +67,28 @@ - }, - ], - ); -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use test_macros::kernel_test; -+ -+ /// Check 64 KiB alignment of the kernel's virtual memory layout sections. -+ #[kernel_test] -+ fn virt_mem_layout_sections_are_64KiB_aligned() { -+ const SIXTYFOUR_KIB: usize = 65536; -+ -+ for i in LAYOUT.inner().iter() { -+ let start: usize = *(i.virtual_range)().start(); -+ let end: usize = *(i.virtual_range)().end() + 1; -+ -+ assert_eq!(start modulo SIXTYFOUR_KIB, 0); -+ assert_eq!(end modulo SIXTYFOUR_KIB, 0); -+ assert!(end >= start); -+ } -+ } -+} - -diff -uNr 12_cpu_exceptions_part1/src/bsp/rpi.rs 13_integrated_testing/src/bsp/rpi.rs ---- 12_cpu_exceptions_part1/src/bsp/rpi.rs -+++ 13_integrated_testing/src/bsp/rpi.rs -@@ -83,3 +83,13 @@ - pub fn virt_mem_layout() -> &'static KernelVirtualLayout<{ virt_mem_layout::NUM_MEM_RANGES }> { - &virt_mem_layout::LAYOUT - } -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+/// Minimal code needed to bring up the console in QEMU (for testing only). This is often less steps -+/// than on real hardware due to QEMU's abstractions. -+/// -+/// For the RPi, nothing needs to be done. -+pub fn qemu_bring_up_console() {} - -diff -uNr 12_cpu_exceptions_part1/src/bsp.rs 13_integrated_testing/src/bsp.rs ---- 12_cpu_exceptions_part1/src/bsp.rs -+++ 13_integrated_testing/src/bsp.rs -@@ -11,3 +11,31 @@ - - #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] - pub use rpi::*; -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use test_macros::kernel_test; -+ -+ /// Ensure the kernel's virtual memory layout is free of overlaps. -+ #[kernel_test] -+ fn virt_mem_layout_has_no_overlaps() { -+ let layout = virt_mem_layout().inner(); -+ -+ for (i, first) in layout.iter().enumerate() { -+ for second in layout.iter().skip(i + 1) { -+ let first_range = first.virtual_range; -+ let second_range = second.virtual_range; -+ -+ assert!(!first_range().contains(second_range().start())); -+ assert!(!first_range().contains(second_range().end())); -+ assert!(!second_range().contains(first_range().start())); -+ assert!(!second_range().contains(first_range().end())); -+ } -+ } -+ } -+} - -diff -uNr 12_cpu_exceptions_part1/src/lib.rs 13_integrated_testing/src/lib.rs ---- 12_cpu_exceptions_part1/src/lib.rs -+++ 13_integrated_testing/src/lib.rs -@@ -0,0 +1,70 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2018-2020 Andre Richter -+ -+// Rust embedded logo for `make doc`. -+#![doc(html_logo_url = "https://git.io/JeGIp")] -+ -+//! The `kernel` library. -+//! -+//! Used by `main.rs` to compose the final kernel binary. -+ -+#![allow(incomplete_features)] -+#![feature(const_generics)] -+#![feature(format_args_nl)] -+#![feature(global_asm)] -+#![feature(linkage)] -+#![feature(panic_info_message)] -+#![feature(slice_ptr_range)] -+#![feature(trait_alias)] -+#![no_std] -+// Testing -+#![cfg_attr(test, no_main)] -+#![feature(custom_test_frameworks)] -+#![reexport_test_harness_main = "test_main"] -+#![test_runner(crate::test_runner)] -+ -+// Conditionally includes the selected `architecture` code, which provides the `_start()` function, -+// the first function to run. -+pub mod arch; -+ -+// `_start()` then calls `runtime_init()`, which on completion, jumps to `kernel_init()`. -+mod runtime_init; -+ -+// Conditionally includes the selected `BSP` code. -+pub mod bsp; -+ -+pub mod interface; -+mod memory; -+mod panic_wait; -+pub mod print; -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+/// The default runner for unit tests. -+pub fn test_runner(tests: &[&test_types::UnitTest]) { -+ println!("Running {} tests", tests.len()); -+ println!("-------------------------------------------------------------------\n"); -+ for (i, test) in tests.iter().enumerate() { -+ print!("{:>3}. {:.<58}", i + 1, test.name); -+ -+ // Run the actual test. -+ (test.test_func)(); -+ -+ // Failed tests call panic!(). Execution reaches here only if the test has passed. -+ println!("[ok]") -+ } -+} -+ -+/// The `kernel_init()` for unit tests. Called from `runtime_init()`. -+#[cfg(test)] -+#[no_mangle] -+unsafe fn kernel_init() -> ! { -+ bsp::qemu_bring_up_console(); -+ -+ test_main(); -+ -+ arch::qemu_exit_success() -+} - -diff -uNr 12_cpu_exceptions_part1/src/main.rs 13_integrated_testing/src/main.rs ---- 12_cpu_exceptions_part1/src/main.rs -+++ 13_integrated_testing/src/main.rs -@@ -5,7 +5,7 @@ - // Rust embedded logo for `make doc`. - #![doc(html_logo_url = "https://git.io/JeGIp")] - --//! The `kernel` -+//! The `kernel` binary. - //! - //! The `kernel` is composed by glueing together code from - //! -@@ -19,29 +19,11 @@ - //! [Architecture-specific code]: arch/index.html - //! [`kernel::interface`]: interface/index.html - --#![allow(incomplete_features)] --#![feature(const_generics)] - #![feature(format_args_nl)] --#![feature(global_asm)] --#![feature(panic_info_message)] --#![feature(trait_alias)] - #![no_main] - #![no_std] - --// Conditionally includes the selected `architecture` code, which provides the `_start()` function, --// the first function to run. --mod arch; -- --// `_start()` then calls `runtime_init()`, which on completion, jumps to `kernel_init()`. --mod runtime_init; -- --// Conditionally includes the selected `BSP` code. --mod bsp; -- --mod interface; --mod memory; --mod panic_wait; --mod print; -+use libkernel::{arch, bsp, info, interface}; - - /// Early init code. - /// -@@ -55,6 +37,7 @@ - /// - 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. -+#[no_mangle] - unsafe fn kernel_init() -> ! { - use interface::mm::MMU; - -@@ -78,8 +61,7 @@ - - /// The main function running after the early init. - fn kernel_main() -> ! { -- use core::time::Duration; -- use interface::{console::All, time::Timer}; -+ use interface::console::All; - - info!("Booting on: {}", bsp::board_name()); - -@@ -102,31 +84,6 @@ - info!(" {}. {}", i + 1, driver.compatible()); - } - -- info!("Timer test, spinning for 1 second"); -- arch::timer().spin_for(Duration::from_secs(1)); -- -- // 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().read_char(); - -diff -uNr 12_cpu_exceptions_part1/src/memory.rs 13_integrated_testing/src/memory.rs ---- 12_cpu_exceptions_part1/src/memory.rs -+++ 13_integrated_testing/src/memory.rs -@@ -27,7 +27,6 @@ - } - } - --#[allow(dead_code)] - #[derive(Copy, Clone)] - pub enum Translation { - Identity, -@@ -166,4 +165,30 @@ - info!("{}", i); - } - } -+ -+ #[cfg(test)] -+ pub fn inner(&self) -> &[RangeDescriptor; NUM_SPECIAL_RANGES] { -+ &self.inner -+ } -+} -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use test_macros::kernel_test; -+ -+ /// Check `zero_volatile()`. -+ #[kernel_test] -+ fn zero_volatile_works() { -+ let mut x: [usize; 3] = [10, 11, 12]; -+ let x_range = x.as_mut_ptr_range(); -+ -+ unsafe { zero_volatile(x_range) }; -+ -+ assert_eq!(x, [0, 0, 0]); -+ } - } - -diff -uNr 12_cpu_exceptions_part1/src/panic_wait.rs 13_integrated_testing/src/panic_wait.rs ---- 12_cpu_exceptions_part1/src/panic_wait.rs -+++ 13_integrated_testing/src/panic_wait.rs -@@ -23,6 +23,23 @@ - }) - } - -+/// The point of exit for the "standard" (non-testing) `libkernel`. -+/// -+/// This code will be used by the release kernel binary and the `integration tests`. It is linked -+/// weakly, so that the integration tests can overload it to exit `QEMU` instead of spinning -+/// forever. -+/// -+/// This is one possible approach to solve the problem that `cargo` can not know who the consumer of -+/// the library will be: -+/// - The release kernel binary that should safely park the paniced core, -+/// - or an `integration test` that is executed in QEMU, which should just exit QEMU. -+#[cfg(not(test))] -+#[linkage = "weak"] -+#[no_mangle] -+fn _panic_exit() -> ! { -+ arch::wait_forever() -+} -+ - #[panic_handler] - fn panic(info: &PanicInfo) -> ! { - if let Some(args) = info.message() { -@@ -31,5 +48,16 @@ - panic_println!("\nKernel panic!"); - } - -- arch::wait_forever() -+ _panic_exit() -+} -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+/// The point of exit when the library is compiled for testing. -+#[cfg(test)] -+#[no_mangle] -+fn _panic_exit() -> ! { -+ arch::qemu_exit_failure() - } - -diff -uNr 12_cpu_exceptions_part1/src/runtime_init.rs 13_integrated_testing/src/runtime_init.rs ---- 12_cpu_exceptions_part1/src/runtime_init.rs -+++ 13_integrated_testing/src/runtime_init.rs -@@ -43,7 +43,34 @@ - /// - /// - Only a single core must be active and running this function. - pub unsafe fn runtime_init() -> ! { -+ extern "Rust" { -+ fn kernel_init() -> !; -+ } -+ - zero_bss(); - -- crate::kernel_init() -+ kernel_init() -+} -+ -+//-------------------------------------------------------------------------------------------------- -+// Testing -+//-------------------------------------------------------------------------------------------------- -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use test_macros::kernel_test; -+ -+ /// Check `bss` section layout. -+ #[kernel_test] -+ fn bss_section_is_sane() { -+ use core::mem; -+ -+ let start = unsafe { bss_range().start } as *const _ as usize; -+ let end = unsafe { bss_range().end } as *const _ as usize; -+ -+ assert_eq!(start modulo mem::size_of::(), 0); -+ assert_eq!(end modulo mem::size_of::(), 0); -+ assert!(end >= start); -+ } - } - -diff -uNr 12_cpu_exceptions_part1/test-macros/Cargo.toml 13_integrated_testing/test-macros/Cargo.toml ---- 12_cpu_exceptions_part1/test-macros/Cargo.toml -+++ 13_integrated_testing/test-macros/Cargo.toml -@@ -0,0 +1,14 @@ -+[package] -+name = "test-macros" -+version = "0.1.0" -+authors = ["Andre Richter "] -+edition = "2018" -+ -+[lib] -+proc-macro = true -+ -+[dependencies] -+proc-macro2 = "1.x" -+quote = "1.x" -+syn = { version = "1.x", features = ["full"] } -+test-types = { path = "../test-types" } - -diff -uNr 12_cpu_exceptions_part1/test-macros/src/lib.rs 13_integrated_testing/test-macros/src/lib.rs ---- 12_cpu_exceptions_part1/test-macros/src/lib.rs -+++ 13_integrated_testing/test-macros/src/lib.rs -@@ -0,0 +1,31 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+extern crate proc_macro; -+ -+use proc_macro::TokenStream; -+use proc_macro2::Span; -+use quote::quote; -+use syn::{parse_macro_input, Ident, ItemFn}; -+ -+#[proc_macro_attribute] -+pub fn kernel_test(_attr: TokenStream, input: TokenStream) -> TokenStream { -+ let f = parse_macro_input!(input as ItemFn); -+ -+ let test_name = &format!("{}", f.sig.ident.to_string()); -+ let test_ident = Ident::new( -+ &format!("{}_TEST_CONTAINER", f.sig.ident.to_string().to_uppercase()), -+ Span::call_site(), -+ ); -+ let test_code_block = f.block; -+ -+ quote!( -+ #[test_case] -+ const #test_ident: test_types::UnitTest = test_types::UnitTest { -+ name: #test_name, -+ test_func: || #test_code_block, -+ }; -+ ) -+ .into() -+} - -diff -uNr 12_cpu_exceptions_part1/tests/00_interface_sanity_console.rb 13_integrated_testing/tests/00_interface_sanity_console.rb ---- 12_cpu_exceptions_part1/tests/00_interface_sanity_console.rb -+++ 13_integrated_testing/tests/00_interface_sanity_console.rb -@@ -0,0 +1,50 @@ -+# frozen_string_literal: true -+ -+# SPDX-License-Identifier: MIT OR Apache-2.0 -+# -+# Copyright (c) 2019-2020 Andre Richter -+ -+require 'expect' -+ -+TIMEOUT_SECS = 3 -+ -+# Verify sending and receiving works as expected. -+class TxRxHandshake -+ def name -+ 'Transmit and Receive handshake' -+ end -+ -+ def run(qemu_out, qemu_in) -+ qemu_in.write_nonblock('ABC') -+ raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? -+ end -+end -+ -+# Check for correct TX statistics implementation. Depends on test 1 being run first. -+class TxStatistics -+ def name -+ 'Transmit statistics' -+ end -+ -+ def run(qemu_out, _qemu_in) -+ raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? -+ end -+end -+ -+# Check for correct RX statistics implementation. Depends on test 1 being run first. -+class RxStatistics -+ def name -+ 'Receive statistics' -+ end -+ -+ def run(qemu_out, _qemu_in) -+ raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? -+ end -+end -+ -+##-------------------------------------------------------------------------------------------------- -+## Test registration -+##-------------------------------------------------------------------------------------------------- -+def subtest_collection -+ [TxRxHandshake.new, TxStatistics.new, RxStatistics.new] -+end - -diff -uNr 12_cpu_exceptions_part1/tests/00_interface_sanity_console.rs 13_integrated_testing/tests/00_interface_sanity_console.rs ---- 12_cpu_exceptions_part1/tests/00_interface_sanity_console.rs -+++ 13_integrated_testing/tests/00_interface_sanity_console.rs -@@ -0,0 +1,33 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+//! Console sanity tests - RX, TX and statistics. -+ -+#![feature(format_args_nl)] -+#![no_main] -+#![no_std] -+ -+mod panic_exit_failure; -+ -+use libkernel::{bsp, interface::console::*, print}; -+ -+#[no_mangle] -+unsafe fn kernel_init() -> ! { -+ bsp::qemu_bring_up_console(); -+ -+ // Handshake -+ assert_eq!(bsp::console().read_char(), 'A'); -+ assert_eq!(bsp::console().read_char(), 'B'); -+ assert_eq!(bsp::console().read_char(), 'C'); -+ print!("OK1234"); -+ -+ // 6 -+ print!("{}", bsp::console().chars_written()); -+ -+ // 3 -+ print!("{}", bsp::console().chars_read()); -+ -+ // The QEMU process running this test will be closed by the I/O test harness. -+ loop {} -+} - -diff -uNr 12_cpu_exceptions_part1/tests/01_interface_sanity_timer.rs 13_integrated_testing/tests/01_interface_sanity_timer.rs ---- 12_cpu_exceptions_part1/tests/01_interface_sanity_timer.rs -+++ 13_integrated_testing/tests/01_interface_sanity_timer.rs -@@ -0,0 +1,50 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+//! Timer sanity tests. -+ -+#![feature(custom_test_frameworks)] -+#![no_main] -+#![no_std] -+#![reexport_test_harness_main = "test_main"] -+#![test_runner(libkernel::test_runner)] -+ -+mod panic_exit_failure; -+ -+use core::time::Duration; -+use libkernel::{arch, arch::timer, bsp, interface::time::Timer}; -+use test_macros::kernel_test; -+ -+#[no_mangle] -+unsafe fn kernel_init() -> ! { -+ bsp::qemu_bring_up_console(); -+ -+ // Depending on CPU arch, some timer bring-up code could go here. Not needed for the RPi. -+ -+ test_main(); -+ -+ arch::qemu_exit_success() -+} -+ -+/// Simple check that the timer is running. -+#[kernel_test] -+fn timer_is_counting() { -+ assert!(timer().uptime().as_nanos() > 0) -+} -+ -+/// Timer resolution must be sufficient. -+#[kernel_test] -+fn timer_resolution_is_sufficient() { -+ assert!(timer().resolution().as_nanos() < 100) -+} -+ -+/// Sanity check spin_for() implementation. -+#[kernel_test] -+fn spin_accuracy_check_1_second() { -+ let t1 = timer().uptime(); -+ timer().spin_for(Duration::from_secs(1)); -+ let t2 = timer().uptime(); -+ -+ assert_eq!((t2 - t1).as_secs(), 1) -+} - -diff -uNr 12_cpu_exceptions_part1/tests/02_arch_exception_handling.rs 13_integrated_testing/tests/02_arch_exception_handling.rs ---- 12_cpu_exceptions_part1/tests/02_arch_exception_handling.rs -+++ 13_integrated_testing/tests/02_arch_exception_handling.rs -@@ -0,0 +1,42 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+//! Page faults must result in synchronous exceptions. -+ -+#![feature(format_args_nl)] -+#![no_main] -+#![no_std] -+ -+/// Overwrites libkernel's `panic_wait::_panic_exit()` with the QEMU-exit version. -+/// -+/// Reaching this code is a success, because it is called from the synchronous exception handler, -+/// which is what this test wants to achieve. -+/// -+/// It also means that this integration test can not use any other code that calls panic!() directly -+/// or indirectly. -+mod panic_exit_success; -+ -+use libkernel::{arch, bsp, interface::mm::MMU, println}; -+ -+#[no_mangle] -+unsafe fn kernel_init() -> ! { -+ bsp::qemu_bring_up_console(); -+ -+ println!("Testing synchronous exception handling by causing a page fault"); -+ println!("-------------------------------------------------------------------\n"); -+ -+ arch::enable_exception_handling(); -+ -+ if let Err(string) = arch::mmu().init() { -+ println!("MMU: {}", string); -+ arch::qemu_exit_failure() -+ } -+ -+ println!("Writing beyond mapped area to address 9 GiB..."); -+ let big_addr: u64 = 9 * 1024 * 1024 * 1024; -+ core::ptr::read_volatile(big_addr as *mut u64); -+ -+ // If execution reaches here, the memory access above did not cause a page fault exception. -+ arch::qemu_exit_failure() -+} - -diff -uNr 12_cpu_exceptions_part1/tests/panic_exit_failure/mod.rs 13_integrated_testing/tests/panic_exit_failure/mod.rs ---- 12_cpu_exceptions_part1/tests/panic_exit_failure/mod.rs -+++ 13_integrated_testing/tests/panic_exit_failure/mod.rs -@@ -0,0 +1,9 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+/// Overwrites libkernel's `panic_wait::_panic_exit()` with the QEMU-exit version. -+#[no_mangle] -+fn _panic_exit() -> ! { -+ libkernel::arch::qemu_exit_failure() -+} - -diff -uNr 12_cpu_exceptions_part1/tests/panic_exit_success/mod.rs 13_integrated_testing/tests/panic_exit_success/mod.rs ---- 12_cpu_exceptions_part1/tests/panic_exit_success/mod.rs -+++ 13_integrated_testing/tests/panic_exit_success/mod.rs -@@ -0,0 +1,9 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+/// Overwrites libkernel's `panic_wait::_panic_exit()` with the QEMU-exit version. -+#[no_mangle] -+fn _panic_exit() -> ! { -+ libkernel::arch::qemu_exit_success() -+} - -diff -uNr 12_cpu_exceptions_part1/tests/runner.rb 13_integrated_testing/tests/runner.rb ---- 12_cpu_exceptions_part1/tests/runner.rb -+++ 13_integrated_testing/tests/runner.rb -@@ -0,0 +1,139 @@ -+#!/usr/bin/env ruby -+# frozen_string_literal: true -+ -+# SPDX-License-Identifier: MIT OR Apache-2.0 -+# -+# Copyright (c) 2019-2020 Andre Richter -+ -+require 'English' -+require 'pty' -+ -+# Test base class. -+class Test -+ INDENT = ' ' -+ -+ def print_border(status) -+ puts -+ puts "#{INDENT}-------------------------------------------------------------------" -+ puts status -+ puts "#{INDENT}-------------------------------------------------------------------\n\n\n" -+ end -+ -+ def print_error(error) -+ puts -+ print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") -+ end -+ -+ def print_success -+ print_border("#{INDENT}✅ Success: #{@test_name}") -+ end -+ -+ def print_output -+ puts "#{INDENT}-------------------------------------------------------------------" -+ print INDENT -+ print '🦀 ' -+ print @output.join('').gsub("\n", "\n" + INDENT) -+ end -+ -+ def finish(error) -+ print_output -+ -+ exit_code = if error -+ print_error(error) -+ false -+ else -+ print_success -+ true -+ end -+ -+ exit(exit_code) -+ end -+end -+ -+# Executes tests with console I/O. -+class ConsoleTest < Test -+ def initialize(binary, qemu_cmd, test_name, console_subtests) -+ @binary = binary -+ @qemu_cmd = qemu_cmd -+ @test_name = test_name -+ @console_subtests = console_subtests -+ @cur_subtest = 1 -+ @output = ["Running #{@console_subtests.length} console-based tests\n", -+ "-------------------------------------------------------------------\n\n"] -+ end -+ -+ def format_test_name(number, name) -+ formatted_name = number.to_s.rjust(3) + '. ' + name -+ formatted_name.ljust(63, '.') -+ end -+ -+ def run_subtest(subtest, qemu_out, qemu_in) -+ @output << format_test_name(@cur_subtest, subtest.name) -+ -+ subtest.run(qemu_out, qemu_in) -+ -+ @output << "[ok]\n" -+ @cur_subtest += 1 -+ end -+ -+ def exec -+ error = false -+ -+ PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| -+ begin -+ @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } -+ rescue StandardError => e -+ error = e.message -+ end -+ -+ finish(error) -+ end -+ end -+end -+ -+# A wrapper around the bare QEMU invocation. -+class RawTest < Test -+ MAX_WAIT_SECS = 5 -+ -+ def initialize(binary, qemu_cmd, test_name) -+ @binary = binary -+ @qemu_cmd = qemu_cmd -+ @test_name = test_name -+ @output = [] -+ end -+ -+ def exec -+ error = 'Timed out waiting for test' -+ io = IO.popen(@qemu_cmd) -+ -+ while IO.select([io], nil, nil, MAX_WAIT_SECS) -+ begin -+ @output << io.read_nonblock(1024) -+ rescue EOFError -+ io.close -+ error = $CHILD_STATUS.to_i != 0 -+ break -+ end -+ end -+ -+ finish(error) -+ end -+end -+ -+##-------------------------------------------------------------------------------------------------- -+## Script entry point -+##-------------------------------------------------------------------------------------------------- -+binary = ARGV.last -+test_name = binary.gsub(modulor{.*deps/}, '').split('-')[0] -+console_test_file = 'tests/' + test_name + '.rb' -+qemu_cmd = ARGV.join(' ') -+ -+test_runner = if File.exist?(console_test_file) -+ load console_test_file -+ # subtest_collection is provided by console_test_file -+ ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) -+ else -+ RawTest.new(binary, qemu_cmd, test_name) -+ end -+ -+test_runner.exec - -diff -uNr 12_cpu_exceptions_part1/test-types/Cargo.toml 13_integrated_testing/test-types/Cargo.toml ---- 12_cpu_exceptions_part1/test-types/Cargo.toml -+++ 13_integrated_testing/test-types/Cargo.toml -@@ -0,0 +1,5 @@ -+[package] -+name = "test-types" -+version = "0.1.0" -+authors = ["Andre Richter "] -+edition = "2018" - -diff -uNr 12_cpu_exceptions_part1/test-types/src/lib.rs 13_integrated_testing/test-types/src/lib.rs ---- 12_cpu_exceptions_part1/test-types/src/lib.rs -+++ 13_integrated_testing/test-types/src/lib.rs -@@ -0,0 +1,16 @@ -+// SPDX-License-Identifier: MIT OR Apache-2.0 -+// -+// Copyright (c) 2019-2020 Andre Richter -+ -+//! Types for the `custom_test_frameworks` implementation. -+ -+#![no_std] -+ -+/// Unit test container. -+pub struct UnitTest { -+ /// Name of the test. -+ pub name: &'static str, -+ -+ /// Function pointer to the test. -+ pub test_func: fn(), -+} - -``` diff --git a/13_integrated_testing/kernel b/13_integrated_testing/kernel index 4ddfb194..0800b34b 100755 Binary files a/13_integrated_testing/kernel and b/13_integrated_testing/kernel differ diff --git a/13_integrated_testing/kernel8.img b/13_integrated_testing/kernel8.img index e7dd311b..4dc11e1b 100755 Binary files a/13_integrated_testing/kernel8.img and b/13_integrated_testing/kernel8.img differ diff --git a/13_integrated_testing/src/arch/aarch64.rs b/13_integrated_testing/src/_arch/aarch64/cpu.rs similarity index 51% rename from 13_integrated_testing/src/arch/aarch64.rs rename to 13_integrated_testing/src/_arch/aarch64/cpu.rs index db0a7e8b..7839f142 100644 --- a/13_integrated_testing/src/arch/aarch64.rs +++ b/13_integrated_testing/src/_arch/aarch64/cpu.rs @@ -2,16 +2,15 @@ // // Copyright (c) 2018-2020 Andre Richter -//! AArch64. +//! Architectural processor code. -mod exception; -mod mmu; -pub mod sync; -mod time; - -use crate::{bsp, interface}; +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. @@ -19,12 +18,11 @@ use cortex_a::{asm, regs::*}; /// # Safety /// /// - Linker script must ensure to place this function at `0x80_000`. +#[naked] #[no_mangle] pub unsafe extern "C" fn _start() -> ! { - const CORE_MASK: u64 = 0x3; - // Expect the boot core to start in EL2. - if (bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK) + if (bsp::cpu::BOOT_CORE_ID == cpu::smp::core_id()) && (CurrentEL.get() == CurrentEL::EL::EL2.value) { el2_to_el1_transition() @@ -39,9 +37,12 @@ pub unsafe extern "C" fn _start() -> ! { /// # 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::init()`. +/// - 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); @@ -53,7 +54,7 @@ unsafe fn el2_to_el1_transition() -> ! { // Set up a simulated exception return. // - // First, fake a saved program status, where all interrupts were masked and SP_EL1 was used as a + // 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 @@ -63,42 +64,31 @@ unsafe fn el2_to_el1_transition() -> ! { + SPSR_EL2::M::EL1h, ); - // Second, let the link register point to init(). - ELR_EL2.set(crate::runtime_init::runtime_init as *const () as u64); + // 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::BOOT_CORE_STACK_START); + SP_EL1.set(bsp::cpu::BOOT_CORE_STACK_START); - // Use `eret` to "return" to EL1. This will result in execution of `reset()` in EL1. + // Use `eret` to "return" to EL1. This results in execution of runtime_init() in EL1. asm::eret() } //-------------------------------------------------------------------------------------------------- -// Global instances -//-------------------------------------------------------------------------------------------------- - -static TIMER: time::Timer = time::Timer; -static MMU: mmu::MMU = mmu::MMU; - -//-------------------------------------------------------------------------------------------------- -// Implementation of the kernel's architecture abstraction code +// Public Code //-------------------------------------------------------------------------------------------------- pub use asm::nop; /// Spin for `n` cycles. +#[inline(always)] pub fn spin_for_cycles(n: usize) { for _ in 0..n { asm::nop(); } } -/// Return a reference to a `interface::time::TimeKeeper` implementation. -pub fn timer() -> &'static impl interface::time::Timer { - &TIMER -} - -/// Pause execution on the calling CPU core. +/// Pause execution on the core. #[inline(always)] pub fn wait_forever() -> ! { loop { @@ -106,56 +96,6 @@ pub fn wait_forever() -> ! { } } -/// Enable exception handling. -/// -/// # Safety -/// -/// - Changes the HW state of the processing element. -pub unsafe fn enable_exception_handling() { - exception::set_vbar_el1(); -} - -/// Return a reference to an `interface::mm::MMU` implementation. -pub fn mmu() -> &'static impl interface::mm::MMU { - &MMU -} - -/// Information about the HW state. -pub mod state { - use crate::arch::PrivilegeLevel; - use cortex_a::regs::*; - - /// 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"), - } - } - - /// Print the AArch64 exceptions status. - #[rustfmt::skip] - pub fn print_exception_state() { - use super::{ - exception, - exception::{Debug, SError, FIQ, IRQ}, - }; - use crate::info; - - let to_mask_str = |x| -> _ { - if x { "Masked" } else { "Unmasked" } - }; - - info!(" Debug: {}", to_mask_str(exception::is_masked::())); - info!(" SError: {}", to_mask_str(exception::is_masked::())); - info!(" IRQ: {}", to_mask_str(exception::is_masked::())); - info!(" FIQ: {}", to_mask_str(exception::is_masked::())); - } -} - //-------------------------------------------------------------------------------------------------- // Testing //-------------------------------------------------------------------------------------------------- diff --git a/13_integrated_testing/src/_arch/aarch64/cpu/smp.rs b/13_integrated_testing/src/_arch/aarch64/cpu/smp.rs new file mode 100644 index 00000000..8429e1d2 --- /dev/null +++ b/13_integrated_testing/src/_arch/aarch64/cpu/smp.rs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! Architectural symmetric multiprocessing. + +use cortex_a::regs::*; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return the executing core's id. +#[inline(always)] +pub fn core_id() -> T +where + T: From, +{ + const CORE_MASK: u64 = 0b11; + + T::from((MPIDR_EL1.get() & CORE_MASK) as u8) +} diff --git a/13_integrated_testing/src/arch/aarch64/exception.S b/13_integrated_testing/src/_arch/aarch64/exception.S similarity index 91% rename from 13_integrated_testing/src/arch/aarch64/exception.S rename to 13_integrated_testing/src/_arch/aarch64/exception.S index b358be7e..70817be4 100644 --- a/13_integrated_testing/src/arch/aarch64/exception.S +++ b/13_integrated_testing/src/_arch/aarch64/exception.S @@ -2,8 +2,8 @@ // // Copyright (c) 2018-2020 Andre Richter -/// Call the function provided by parameter `\handler` after saving exception context. Provide the -/// context as the first parameter to '\handler'. +/// 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 @@ -52,16 +52,19 @@ //-------------------------------------------------------------------------------------------------- .section .exception_vectors, "ax", @progbits -// Align by 2^11 bytes, as demanded by the AArch64 spec. Same as ALIGN(2048) in an ld script. +// 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. // -// It must be ensured that `CALL_WITH_CONTEXT` <= 0x80 bytes. +// # Safety +// +// - It must be ensured that `CALL_WITH_CONTEXT` <= 0x80 bytes. .org 0x000 CALL_WITH_CONTEXT current_el0_synchronous .org 0x080 @@ -81,7 +84,7 @@ __exception_vector_start: .org 0x380 CALL_WITH_CONTEXT current_elx_serror -// Lower exception level, aarch64 +// Lower exception level, AArch64 .org 0x400 CALL_WITH_CONTEXT lower_aarch64_synchronous .org 0x480 @@ -91,7 +94,7 @@ __exception_vector_start: .org 0x580 CALL_WITH_CONTEXT lower_aarch64_serror -// Lower exception level, aarch32 +// Lower exception level, AArch32 .org 0x600 CALL_WITH_CONTEXT lower_aarch32_synchronous .org 0x680 @@ -105,6 +108,8 @@ __exception_vector_start: //-------------------------------------------------------------------------------------------------- // Helper functions //-------------------------------------------------------------------------------------------------- +.section .text + __exception_restore_context: ldr w19, [sp, #16 * 16] ldp lr, x20, [sp, #16 * 15] diff --git a/13_integrated_testing/src/arch/aarch64/exception.rs b/13_integrated_testing/src/_arch/aarch64/exception.rs similarity index 83% rename from 13_integrated_testing/src/arch/aarch64/exception.rs rename to 13_integrated_testing/src/_arch/aarch64/exception.rs index c9cbcc66..41bd4ca2 100644 --- a/13_integrated_testing/src/arch/aarch64/exception.rs +++ b/13_integrated_testing/src/_arch/aarch64/exception.rs @@ -2,7 +2,7 @@ // // Copyright (c) 2018-2020 Andre Richter -//! Exception handling. +//! Architectural synchronous and asynchronous exception handling. use core::fmt; use cortex_a::{barrier, regs::*}; @@ -11,6 +11,10 @@ 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); @@ -18,13 +22,16 @@ struct SpsrEL1(InMemoryRegister); /// The exception context as it is stored on the stack on exception entry. #[repr(C)] struct ExceptionContext { - // General Purpose Registers. + /// General Purpose Registers. gpr: [u64; 30], - // The link register, aka x30. + + /// The link register, aka x30. lr: u64, - // Exception link register. The program counter at the time the exception happened. + + /// Exception link register. The program counter at the time the exception happened. elr_el1: u64, - // Saved program status. + + /// Saved program status. spsr_el1: SpsrEL1, } @@ -32,7 +39,7 @@ struct ExceptionContext { struct EsrEL1; //-------------------------------------------------------------------------------------------------- -// Exception vector implementation +// Private Code //-------------------------------------------------------------------------------------------------- /// Print verbose information about the exception and the panic. @@ -48,9 +55,9 @@ fn default_exception_handler(e: &ExceptionContext) { ); } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ // Current, EL0 -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ #[no_mangle] unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) { @@ -67,11 +74,10 @@ unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext) { default_exception_handler(e); } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ // Current, ELx -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ -/// Asynchronous exception taken from the current EL, using SP of the current EL. #[no_mangle] unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) { default_exception_handler(e); @@ -87,9 +93,9 @@ 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) { @@ -106,9 +112,9 @@ 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) { @@ -125,9 +131,9 @@ unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext) { default_exception_handler(e); } -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ // Pretty printing -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ /// Human readable ESR_EL1. #[rustfmt::skip] @@ -214,16 +220,30 @@ impl fmt::Display for ExceptionContext { } //-------------------------------------------------------------------------------------------------- -// Arch-public +// 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"), + } +} -/// Set the exception vector base address register. +/// 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 AArch64 spec. -pub unsafe fn set_vbar_el1() { +/// 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; @@ -235,40 +255,3 @@ pub unsafe fn set_vbar_el1() { // Force VBAR update to complete before next instruction. barrier::isb(barrier::SY); } - -pub trait DaifField { - fn daif_field() -> register::Field; -} - -pub struct Debug; -pub struct SError; -pub struct IRQ; -pub struct FIQ; - -impl DaifField for Debug { - fn daif_field() -> register::Field { - DAIF::D - } -} - -impl DaifField for SError { - fn daif_field() -> register::Field { - DAIF::A - } -} - -impl DaifField for IRQ { - fn daif_field() -> register::Field { - DAIF::I - } -} - -impl DaifField for FIQ { - fn daif_field() -> register::Field { - DAIF::F - } -} - -pub fn is_masked() -> bool { - DAIF.is_set(T::daif_field()) -} diff --git a/13_integrated_testing/src/_arch/aarch64/exception/asynchronous.rs b/13_integrated_testing/src/_arch/aarch64/exception/asynchronous.rs new file mode 100644 index 00000000..1cc2fba8 --- /dev/null +++ b/13_integrated_testing/src/_arch/aarch64/exception/asynchronous.rs @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! Architectural asynchronous exception handling. + +use cortex_a::regs::*; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +trait DaifField { + fn daif_field() -> register::Field; +} + +struct Debug; +struct SError; +struct IRQ; +struct FIQ; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl DaifField for Debug { + fn daif_field() -> register::Field { + DAIF::D + } +} + +impl DaifField for SError { + fn daif_field() -> register::Field { + DAIF::A + } +} + +impl DaifField for IRQ { + fn daif_field() -> register::Field { + DAIF::I + } +} + +impl DaifField for FIQ { + fn daif_field() -> register::Field { + DAIF::F + } +} + +fn is_masked() -> 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::())); + info!(" SError: {}", to_mask_str(is_masked::())); + info!(" IRQ: {}", to_mask_str(is_masked::())); + info!(" FIQ: {}", to_mask_str(is_masked::())); +} diff --git a/13_integrated_testing/src/arch/aarch64/mmu.rs b/13_integrated_testing/src/_arch/aarch64/memory/mmu.rs similarity index 81% rename from 13_integrated_testing/src/arch/aarch64/mmu.rs rename to 13_integrated_testing/src/_arch/aarch64/memory/mmu.rs index c0ba5b26..e9c9d038 100644 --- a/13_integrated_testing/src/arch/aarch64/mmu.rs +++ b/13_integrated_testing/src/_arch/aarch64/memory/mmu.rs @@ -2,19 +2,21 @@ // // Copyright (c) 2018-2020 Andre Richter -//! Memory Management Unit. +//! Memory Management Unit Driver. //! //! Static page tables, compiled on boot; Everything 64 KiB granule. -use crate::{ - bsp, interface, - memory::{AccessPermissions, AttributeFields, MemAttributes}, -}; +use super::{AccessPermissions, AttributeFields, MemAttributes}; +use crate::{bsp, memory}; use core::convert; use cortex_a::{barrier, regs::*}; use register::register_bitfields; -// A table descriptor, as per AArch64 Reference Manual Figure D4-15. +//-------------------------------------------------------------------------------------------------- +// 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. @@ -32,7 +34,7 @@ register_bitfields! {u64, ] } -// A level 3 page descriptor, as per AArch64 Reference Manual Figure D4-17. +// A level 3 page descriptor, as per ARMv8-A Architecture Reference Manual Figure D4-17. register_bitfields! {u64, STAGE1_PAGE_DESCRIPTOR [ /// Privileged execute-never. @@ -101,18 +103,21 @@ struct PageDescriptor(u64); #[repr(C)] #[repr(align(65536))] struct PageTables { - // Page descriptors, covering 64 KiB windows per entry. + /// Page descriptors, covering 64 KiB windows per entry. lvl3: [[PageDescriptor; 8192]; N], - // Table descriptors, covering 512 MiB windows. + + /// 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::addr_space_size() >> FIVETWELVE_MIB_SHIFT; +const ENTRIES_512_MIB: usize = bsp::memory::mmu::addr_space_size() >> FIVETWELVE_MIB_SHIFT; /// The page tables. /// -/// Supposed to land in `.bss`. Therefore, ensure that they boil down to all "0" entries. +/// # 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], @@ -123,6 +128,30 @@ trait BaseAddr { 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 BaseAddr for [T; N] { fn base_addr_u64(&self) -> u64 { self as *const T as u64 @@ -180,7 +209,7 @@ impl convert::From } impl PageDescriptor { - fn new(output_addr: usize, attribute_fields: AttributeFields) -> 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 @@ -189,17 +218,10 @@ impl PageDescriptor { + STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB.val(shifted as u64)) .value; - PageDescriptor(val) + Self(val) } } -/// Constants for indexing the MAIR_EL1. -#[allow(dead_code)] -mod mair { - pub const DEVICE: u64 = 0; - pub const NORMAL: u64 = 1; -} - /// Setup function for the MAIR_EL1 register. fn set_up_mair() { // Define the memory types being mapped. @@ -227,7 +249,7 @@ unsafe fn populate_pt_entries() -> Result<(), &'static str> { let virt_addr = (l2_nr << FIVETWELVE_MIB_SHIFT) + (l3_nr << SIXTYFOUR_KIB_SHIFT); let (output_addr, attribute_fields) = - bsp::virt_mem_layout().get_virt_addr_properties(virt_addr)?; + bsp::memory::mmu::virt_mem_layout().get_virt_addr_properties(virt_addr)?; *l3_entry = PageDescriptor::new(output_addr, attribute_fields); } @@ -252,16 +274,19 @@ fn configure_translation_control() { } //-------------------------------------------------------------------------------------------------- -// Arch-public +// Public Code //-------------------------------------------------------------------------------------------------- -pub struct MMU; +/// Return a reference to the MMU. +pub fn mmu() -> &'static impl memory::mmu::interface::MMU { + &MMU +} -//-------------------------------------------------------------------------------------------------- -// OS interface implementations -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ -impl interface::mm::MMU for MMU { +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) { diff --git a/13_integrated_testing/src/arch/aarch64/time.rs b/13_integrated_testing/src/_arch/aarch64/time.rs similarity index 69% rename from 13_integrated_testing/src/arch/aarch64/time.rs rename to 13_integrated_testing/src/_arch/aarch64/time.rs index 249c498a..fb01ced1 100644 --- a/13_integrated_testing/src/arch/aarch64/time.rs +++ b/13_integrated_testing/src/_arch/aarch64/time.rs @@ -2,25 +2,45 @@ // // Copyright (c) 2018-2020 Andre Richter -//! Timer primitives. +//! Architectural timer primitives. -use crate::{interface, warn}; +use crate::{time, warn}; use core::time::Duration; use cortex_a::regs::*; +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + const NS_PER_S: u64 = 1_000_000_000; //-------------------------------------------------------------------------------------------------- -// Arch-public +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// ARMv8 Generic Timer. +pub struct GenericTimer; + +//-------------------------------------------------------------------------------------------------- +// Global instances //-------------------------------------------------------------------------------------------------- -pub struct Timer; +static TIME_MANAGER: GenericTimer = GenericTimer; //-------------------------------------------------------------------------------------------------- -// OS interface implementations +// Public Code //-------------------------------------------------------------------------------------------------- -impl interface::time::Timer for Timer { +/// 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)) } diff --git a/13_integrated_testing/src/arch/aarch64/sync.rs b/13_integrated_testing/src/arch/aarch64/sync.rs deleted file mode 100644 index 1d1e459f..00000000 --- a/13_integrated_testing/src/arch/aarch64/sync.rs +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// -// Copyright (c) 2018-2020 Andre Richter - -//! Synchronization primitives. - -use crate::interface; -use core::cell::UnsafeCell; - -//-------------------------------------------------------------------------------------------------- -// Arch-public -//-------------------------------------------------------------------------------------------------- - -/// A pseudo-lock for teaching purposes. -/// -/// Used to introduce [interior mutability]. -/// -/// In contrast to a real Mutex implementation, does not protect against concurrent access to the -/// contained data. This part is preserved for later lessons. -/// -/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is -/// executing single-threaded, aka only running on a single core with interrupts disabled. -/// -/// [interior mutability]: https://doc.rust-lang.org/std/cell/index.html -pub struct NullLock { - data: UnsafeCell, -} - -unsafe impl Send for NullLock {} -unsafe impl Sync for NullLock {} - -impl NullLock { - /// Wraps `data` into a new `NullLock`. - pub const fn new(data: T) -> NullLock { - NullLock { - data: UnsafeCell::new(data), - } - } -} - -//-------------------------------------------------------------------------------------------------- -// OS interface implementations -//-------------------------------------------------------------------------------------------------- - -impl interface::sync::Mutex for &NullLock { - type Data = T; - - fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { - // In a real lock, there would be code encapsulating this line that ensures that this - // mutable reference will ever only be given out once at a time. - f(unsafe { &mut *self.data.get() }) - } -} diff --git a/13_integrated_testing/src/bsp.rs b/13_integrated_testing/src/bsp.rs index 3831fb7b..3dbbba8c 100644 --- a/13_integrated_testing/src/bsp.rs +++ b/13_integrated_testing/src/bsp.rs @@ -2,15 +2,15 @@ // // Copyright (c) 2018-2020 Andre Richter -//! Conditional exporting of Board Support Packages. +//! Conditional re-exporting of Board Support Packages. -mod driver; +mod device_driver; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] -mod rpi; +mod raspberrypi; #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] -pub use rpi::*; +pub use raspberrypi::*; //-------------------------------------------------------------------------------------------------- // Testing @@ -24,7 +24,7 @@ mod tests { /// Ensure the kernel's virtual memory layout is free of overlaps. #[kernel_test] fn virt_mem_layout_has_no_overlaps() { - let layout = virt_mem_layout().inner(); + let layout = memory::mmu::virt_mem_layout().inner(); for (i, first) in layout.iter().enumerate() { for second in layout.iter().skip(i + 1) { diff --git a/13_integrated_testing/src/bsp/driver.rs b/13_integrated_testing/src/bsp/device_driver.rs similarity index 93% rename from 13_integrated_testing/src/bsp/driver.rs rename to 13_integrated_testing/src/bsp/device_driver.rs index f75093a5..4508e953 100644 --- a/13_integrated_testing/src/bsp/driver.rs +++ b/13_integrated_testing/src/bsp/device_driver.rs @@ -2,7 +2,7 @@ // // Copyright (c) 2018-2020 Andre Richter -//! Drivers. +//! Device driver. #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] mod bcm; diff --git a/13_integrated_testing/src/bsp/driver/bcm.rs b/13_integrated_testing/src/bsp/device_driver/bcm.rs similarity index 70% rename from 13_integrated_testing/src/bsp/driver/bcm.rs rename to 13_integrated_testing/src/bsp/device_driver/bcm.rs index 40232f30..59071d5d 100644 --- a/13_integrated_testing/src/bsp/driver/bcm.rs +++ b/13_integrated_testing/src/bsp/device_driver/bcm.rs @@ -7,5 +7,5 @@ mod bcm2xxx_gpio; mod bcm2xxx_pl011_uart; -pub use bcm2xxx_gpio::GPIO; -pub use bcm2xxx_pl011_uart::{PL011Uart, PanicUart}; +pub use bcm2xxx_gpio::*; +pub use bcm2xxx_pl011_uart::*; diff --git a/13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_gpio.rs b/13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs similarity index 70% rename from 13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_gpio.rs rename to 13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index f9dd2295..0c17f498 100644 --- a/13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_gpio.rs +++ b/13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -2,12 +2,16 @@ // // Copyright (c) 2018-2020 Andre Richter -//! GPIO driver. +//! GPIO Driver. -use crate::{arch, arch::sync::NullLock, interface}; +use crate::{cpu, driver, synchronization, synchronization::NullLock}; use core::ops; use register::{mmio::*, register_bitfields, register_structs}; +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + // GPIO registers. // // Descriptions taken from @@ -66,12 +70,23 @@ register_structs! { } } -/// The driver's private data. struct GPIOInner { base_addr: usize, } -/// Deref to RegisterBlock. +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO HW. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + impl ops::Deref for GPIOInner { type Target = RegisterBlock; @@ -81,29 +96,28 @@ impl ops::Deref for GPIOInner { } impl GPIOInner { - const fn new(base_addr: usize) -> GPIOInner { - GPIOInner { base_addr } + const fn new(base_addr: usize) -> Self { + Self { base_addr } } - /// Return a pointer to the register block. + /// Return a pointer to the associated MMIO register block. fn ptr(&self) -> *const RegisterBlock { self.base_addr as *const _ } } //-------------------------------------------------------------------------------------------------- -// BSP-public +// Public Code //-------------------------------------------------------------------------------------------------- -use interface::sync::Mutex; - -/// The driver's main struct. -pub struct GPIO { - inner: NullLock, -} impl GPIO { - pub const unsafe fn new(base_addr: usize) -> GPIO { - GPIO { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide the correct `base_addr`. + pub const unsafe fn new(base_addr: usize) -> Self { + Self { inner: NullLock::new(GPIOInner::new(base_addr)), } } @@ -122,24 +136,25 @@ impl GPIO { // Enable pins 14 and 15. inner.GPPUD.set(0); - arch::spin_for_cycles(150); + cpu::spin_for_cycles(150); inner .GPPUDCLK0 .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock); - arch::spin_for_cycles(150); + cpu::spin_for_cycles(150); inner.GPPUDCLK0.set(0); }) } } -//-------------------------------------------------------------------------------------------------- -// OS interface implementations -//-------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; -impl interface::driver::DeviceDriver for GPIO { +impl driver::interface::DeviceDriver for GPIO { fn compatible(&self) -> &str { - "GPIO" + "BCM GPIO" } } diff --git a/13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs b/13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs similarity index 89% rename from 13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs rename to 13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs index b9dd63b6..b15ba818 100644 --- a/13_integrated_testing/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs +++ b/13_integrated_testing/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -4,10 +4,14 @@ //! PL011 UART driver. -use crate::{arch, arch::sync::NullLock, interface}; +use crate::{console, cpu, driver, synchronization, synchronization::NullLock}; use core::{fmt, ops}; use register::{mmio::*, register_bitfields, register_structs}; +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + // PL011 UART registers. // // Descriptions taken from @@ -109,6 +113,10 @@ register_bitfields! { ] } +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + register_structs! { #[allow(non_snake_case)] pub RegisterBlock { @@ -126,13 +134,24 @@ register_structs! { } } -/// The driver's mutex protected part. pub struct PL011UartInner { base_addr: usize, chars_written: usize, chars_read: usize, } +// Export the inner struct so that BSPs can use it for the panic handler. +pub use PL011UartInner as PanicUart; + +/// Representation of the UART. +pub struct PL011Uart { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + /// Deref to RegisterBlock. /// /// Allows writing @@ -152,8 +171,13 @@ impl ops::Deref for PL011UartInner { } impl PL011UartInner { - pub const unsafe fn new(base_addr: usize) -> PL011UartInner { - PL011UartInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide the correct `base_addr`. + pub const unsafe fn new(base_addr: usize) -> Self { + Self { base_addr, chars_written: 0, chars_read: 0, @@ -164,7 +188,7 @@ impl PL011UartInner { /// /// Results in 8N1 and 230400 baud (if the clk has been previously set to 48 MHz by the /// firmware). - pub fn init(&self) { + pub fn init(&mut self) { // Turn it off temporarily. self.CR.set(0); @@ -186,7 +210,7 @@ impl PL011UartInner { fn write_char(&mut self, c: char) { // Spin while TX FIFO full is set, waiting for an empty slot. while self.FR.matches_all(FR::TXFF::SET) { - arch::nop(); + cpu::nop(); } // Write the character to the buffer. @@ -215,42 +239,28 @@ impl fmt::Write for PL011UartInner { } } -//-------------------------------------------------------------------------------------------------- -// Export the inner struct so that BSPs can use it for the panic handler -//-------------------------------------------------------------------------------------------------- -pub use PL011UartInner as PanicUart; - -//-------------------------------------------------------------------------------------------------- -// BSP-public -//-------------------------------------------------------------------------------------------------- - -/// The driver's main struct. -pub struct PL011Uart { - inner: NullLock, -} - impl PL011Uart { /// # Safety /// - /// The user must ensure to provide the correct `base_addr`. - pub const unsafe fn new(base_addr: usize) -> PL011Uart { - PL011Uart { + /// - The user must ensure to provide the correct `base_addr`. + pub const unsafe fn new(base_addr: usize) -> Self { + Self { inner: NullLock::new(PL011UartInner::new(base_addr)), } } } -//-------------------------------------------------------------------------------------------------- -// OS interface implementations -//-------------------------------------------------------------------------------------------------- -use interface::sync::Mutex; +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; -impl interface::driver::DeviceDriver for PL011Uart { +impl driver::interface::DeviceDriver for PL011Uart { fn compatible(&self) -> &str { - "PL011Uart" + "BCM PL011 UART" } - fn init(&self) -> interface::driver::Result { + fn init(&self) -> Result<(), ()> { let mut r = &self.inner; r.lock(|inner| inner.init()); @@ -258,7 +268,7 @@ impl interface::driver::DeviceDriver for PL011Uart { } } -impl interface::console::Write for PL011Uart { +impl console::interface::Write for PL011Uart { /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to /// serialize access. fn write_char(&self, c: char) { @@ -274,23 +284,23 @@ impl interface::console::Write for PL011Uart { } fn flush(&self) { - let mut r = &self.inner; // Spin until TX FIFO empty is set. + let mut r = &self.inner; r.lock(|inner| { while !inner.FR.matches_all(FR::TXFE::SET) { - arch::nop(); + cpu::nop(); } }); } } -impl interface::console::Read for PL011Uart { +impl console::interface::Read for PL011Uart { fn read_char(&self) -> char { let mut r = &self.inner; r.lock(|inner| { // Spin while RX FIFO empty is set. while inner.FR.matches_all(FR::RXFE::SET) { - arch::nop(); + cpu::nop(); } // Read one character. @@ -319,7 +329,7 @@ impl interface::console::Read for PL011Uart { } } -impl interface::console::Statistics for PL011Uart { +impl console::interface::Statistics for PL011Uart { fn chars_written(&self) -> usize { let mut r = &self.inner; r.lock(|inner| inner.chars_written) diff --git a/13_integrated_testing/src/bsp/raspberrypi.rs b/13_integrated_testing/src/bsp/raspberrypi.rs new file mode 100644 index 00000000..c976cc29 --- /dev/null +++ b/13_integrated_testing/src/bsp/raspberrypi.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! Top-level BSP file for the Raspberry Pi 3 and 4. + +pub mod console; +pub mod cpu; +pub mod driver; +pub mod memory; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- +use super::device_driver; + +static GPIO: device_driver::GPIO = + unsafe { device_driver::GPIO::new(memory::map::mmio::GPIO_BASE) }; + +static PL011_UART: device_driver::PL011Uart = + unsafe { device_driver::PL011Uart::new(memory::map::mmio::PL011_UART_BASE) }; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Board identification. +pub fn board_name() -> &'static str { + #[cfg(feature = "bsp_rpi3")] + { + "Raspberry Pi 3" + } + + #[cfg(feature = "bsp_rpi4")] + { + "Raspberry Pi 4" + } +} diff --git a/13_integrated_testing/src/bsp/raspberrypi/console.rs b/13_integrated_testing/src/bsp/raspberrypi/console.rs new file mode 100644 index 00000000..beaaee3b --- /dev/null +++ b/13_integrated_testing/src/bsp/raspberrypi/console.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! BSP console facilities. + +use super::{super::device_driver, memory::map}; +use crate::console; +use core::fmt; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// In case of a panic, the panic handler uses this function to take a last shot at printing +/// something before the system is halted. +/// +/// # Safety +/// +/// - Use only for printing during a panic. +pub unsafe fn panic_console_out() -> impl fmt::Write { + let mut uart = device_driver::PanicUart::new(map::mmio::PL011_UART_BASE); + uart.init(); + uart +} + +/// Return a reference to the console. +pub fn console() -> &'static impl console::interface::All { + &super::PL011_UART +} + +//-------------------------------------------------------------------------------------------------- +// Testing +//-------------------------------------------------------------------------------------------------- + +/// Minimal code needed to bring up the console in QEMU (for testing only). This is often less steps +/// than on real hardware due to QEMU's abstractions. +/// +/// For the RPi, nothing needs to be done. +pub fn qemu_bring_up_console() {} diff --git a/13_integrated_testing/src/bsp/raspberrypi/cpu.rs b/13_integrated_testing/src/bsp/raspberrypi/cpu.rs new file mode 100644 index 00000000..19db276e --- /dev/null +++ b/13_integrated_testing/src/bsp/raspberrypi/cpu.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! 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 --git a/13_integrated_testing/src/bsp/raspberrypi/driver.rs b/13_integrated_testing/src/bsp/raspberrypi/driver.rs new file mode 100644 index 00000000..86526dc0 --- /dev/null +++ b/13_integrated_testing/src/bsp/raspberrypi/driver.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! BSP driver support. + +use crate::driver; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Device Driver Manager type. +pub struct BSPDriverManager { + device_drivers: [&'static (dyn DeviceDriver + Sync); 2], +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static BSP_DRIVER_MANAGER: BSPDriverManager = BSPDriverManager { + device_drivers: [&super::GPIO, &super::PL011_UART], +}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the driver manager. +pub fn driver_manager() -> &'static impl driver::interface::DriverManager { + &BSP_DRIVER_MANAGER +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use driver::interface::DeviceDriver; + +impl driver::interface::DriverManager for BSPDriverManager { + fn all_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)] { + &self.device_drivers[..] + } + + fn post_device_driver_init(&self) { + // Configure PL011Uart's output pins. + super::GPIO.map_pl011_uart(); + } +} diff --git a/13_integrated_testing/src/bsp/rpi/link.ld b/13_integrated_testing/src/bsp/raspberrypi/link.ld similarity index 100% rename from 13_integrated_testing/src/bsp/rpi/link.ld rename to 13_integrated_testing/src/bsp/raspberrypi/link.ld diff --git a/13_integrated_testing/src/bsp/raspberrypi/memory.rs b/13_integrated_testing/src/bsp/raspberrypi/memory.rs new file mode 100644 index 00000000..62f0f74d --- /dev/null +++ b/13_integrated_testing/src/bsp/raspberrypi/memory.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! BSP Memory Management. + +pub mod mmu; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// 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; + + /// Physical devices. + #[cfg(feature = "bsp_rpi3")] + pub mod mmio { + use super::*; + + 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. + #[cfg(feature = "bsp_rpi4")] + pub mod mmio { + use super::*; + + 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 --git a/13_integrated_testing/src/bsp/rpi/virt_mem_layout.rs b/13_integrated_testing/src/bsp/raspberrypi/memory/mmu.rs similarity index 77% rename from 13_integrated_testing/src/bsp/rpi/virt_mem_layout.rs rename to 13_integrated_testing/src/bsp/raspberrypi/memory/mmu.rs index 6c989687..0ceaaf6b 100644 --- a/13_integrated_testing/src/bsp/rpi/virt_mem_layout.rs +++ b/13_integrated_testing/src/bsp/raspberrypi/memory/mmu.rs @@ -2,23 +2,24 @@ // // Copyright (c) 2018-2020 Andre Richter -//! 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. +//! BSP Memory Management Unit. -use super::memory_map; -use crate::memory::*; +use super::super::memory; +use crate::memory::mmu::*; use core::ops::RangeInclusive; //-------------------------------------------------------------------------------------------------- -// BSP-public +// Public Definitions //-------------------------------------------------------------------------------------------------- -pub const NUM_MEM_RANGES: usize = 2; +const NUM_MEM_RANGES: usize = 2; +/// 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, + memory::map::END_INCLUSIVE, [ RangeDescriptor { name: "Kernel code and RO data", @@ -56,7 +57,7 @@ pub static LAYOUT: KernelVirtualLayout<{ NUM_MEM_RANGES }> = KernelVirtualLayout RangeDescriptor { name: "Device MMIO", virtual_range: || { - RangeInclusive::new(memory_map::mmio::BASE, memory_map::mmio::END_INCLUSIVE) + RangeInclusive::new(memory::map::mmio::BASE, memory::map::mmio::END_INCLUSIVE) }, translation: Translation::Identity, attribute_fields: AttributeFields { @@ -68,6 +69,20 @@ pub static LAYOUT: KernelVirtualLayout<{ NUM_MEM_RANGES }> = KernelVirtualLayout ], ); +//-------------------------------------------------------------------------------------------------- +// 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 +} + //-------------------------------------------------------------------------------------------------- // Testing //-------------------------------------------------------------------------------------------------- diff --git a/13_integrated_testing/src/bsp/rpi.rs b/13_integrated_testing/src/bsp/rpi.rs deleted file mode 100644 index 51ef045d..00000000 --- a/13_integrated_testing/src/bsp/rpi.rs +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// -// Copyright (c) 2018-2020 Andre Richter - -//! Board Support Package for the Raspberry Pi. - -mod memory_map; -mod virt_mem_layout; - -use super::driver; -use crate::{interface, memory::KernelVirtualLayout}; -use core::fmt; - -/// Used by `arch` code to find the early boot core. -pub const BOOT_CORE_ID: u64 = 0; - -/// The early boot core's stack address. -pub const BOOT_CORE_STACK_START: u64 = 0x80_000; - -//-------------------------------------------------------------------------------------------------- -// Global BSP driver instances -//-------------------------------------------------------------------------------------------------- - -static GPIO: driver::GPIO = unsafe { driver::GPIO::new(memory_map::mmio::GPIO_BASE) }; -static PL011_UART: driver::PL011Uart = - unsafe { driver::PL011Uart::new(memory_map::mmio::PL011_UART_BASE) }; - -//-------------------------------------------------------------------------------------------------- -// Implementation of the kernel's BSP calls -//-------------------------------------------------------------------------------------------------- - -/// Board identification. -pub fn board_name() -> &'static str { - #[cfg(feature = "bsp_rpi3")] - { - "Raspberry Pi 3" - } - - #[cfg(feature = "bsp_rpi4")] - { - "Raspberry Pi 4" - } -} - -/// Return a reference to a `console::All` implementation. -pub fn console() -> &'static impl interface::console::All { - &PL011_UART -} - -/// In case of a panic, the panic handler uses this function to take a last shot at printing -/// something before the system is halted. -/// -/// # Safety -/// -/// - Use only for printing during a panic. -pub unsafe fn panic_console_out() -> impl fmt::Write { - let uart = driver::PanicUart::new(memory_map::mmio::PL011_UART_BASE); - uart.init(); - uart -} - -/// Return an array of references to all `DeviceDriver` compatible `BSP` drivers. -/// -/// # Safety -/// -/// The order of devices is the order in which `DeviceDriver::init()` is called. -pub fn device_drivers() -> [&'static dyn interface::driver::DeviceDriver; 2] { - [&GPIO, &PL011_UART] -} - -/// BSP initialization code that runs after driver init. -pub fn post_driver_init() { - // Configure PL011Uart's output pins. - GPIO.map_pl011_uart(); -} - -/// 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<{ virt_mem_layout::NUM_MEM_RANGES }> { - &virt_mem_layout::LAYOUT -} - -//-------------------------------------------------------------------------------------------------- -// Testing -//-------------------------------------------------------------------------------------------------- - -/// Minimal code needed to bring up the console in QEMU (for testing only). This is often less steps -/// than on real hardware due to QEMU's abstractions. -/// -/// For the RPi, nothing needs to be done. -pub fn qemu_bring_up_console() {} diff --git a/13_integrated_testing/src/bsp/rpi/memory_map.rs b/13_integrated_testing/src/bsp/rpi/memory_map.rs deleted file mode 100644 index 18505939..00000000 --- a/13_integrated_testing/src/bsp/rpi/memory_map.rs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// -// Copyright (c) 2018-2020 Andre Richter - -//! The board's memory map. - -#[cfg(feature = "bsp_rpi3")] -#[rustfmt::skip] -pub const END_INCLUSIVE: usize = 0x3FFF_FFFF; - -#[cfg(feature = "bsp_rpi4")] -#[rustfmt::skip] -pub const END_INCLUSIVE: usize = 0xFFFF_FFFF; - -/// Physical devices. -#[rustfmt::skip] -pub mod mmio { - #[cfg(feature = "bsp_rpi3")] - pub const BASE: usize = 0x3F00_0000; - - #[cfg(feature = "bsp_rpi4")] - pub const BASE: usize = 0xFE00_0000; - - pub const GPIO_BASE: usize = BASE + 0x0020_0000; - pub const PL011_UART_BASE: usize = BASE + 0x0020_1000; - pub const END_INCLUSIVE: usize = super::END_INCLUSIVE; -} diff --git a/13_integrated_testing/src/console.rs b/13_integrated_testing/src/console.rs new file mode 100644 index 00000000..e6323a20 --- /dev/null +++ b/13_integrated_testing/src/console.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! System console. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Console interfaces. +pub mod interface { + use core::fmt; + + /// Console write functions. + pub trait Write { + /// Write a single character. + fn write_char(&self, c: char); + + /// 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. + pub trait Read { + /// Read a single character. + fn read_char(&self) -> char { + ' ' + } + + /// Clear RX buffers, if any. + fn clear(&self); + } + + /// Console statistics. + pub trait Statistics { + /// Return the number of characters written. + fn chars_written(&self) -> usize { + 0 + } + + /// Return the number of characters read. + fn chars_read(&self) -> usize { + 0 + } + } + + /// Trait alias for a full-fledged console. + pub trait All = Write + Read + Statistics; +} diff --git a/13_integrated_testing/src/cpu.rs b/13_integrated_testing/src/cpu.rs new file mode 100644 index 00000000..9c67c0e7 --- /dev/null +++ b/13_integrated_testing/src/cpu.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020 Andre Richter + +//! Processor code. + +#[cfg(target_arch = "aarch64")] +#[path = "_arch/aarch64/cpu.rs"] +mod arch_cpu; +pub use arch_cpu::*; + +pub mod smp; diff --git a/13_integrated_testing/src/cpu/smp.rs b/13_integrated_testing/src/cpu/smp.rs new file mode 100644 index 00000000..b1428884 --- /dev/null +++ b/13_integrated_testing/src/cpu/smp.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! Symmetric multiprocessing. + +#[cfg(target_arch = "aarch64")] +#[path = "../_arch/aarch64/cpu/smp.rs"] +mod arch_cpu_smp; +pub use arch_cpu_smp::*; diff --git a/13_integrated_testing/src/driver.rs b/13_integrated_testing/src/driver.rs new file mode 100644 index 00000000..c63b8301 --- /dev/null +++ b/13_integrated_testing/src/driver.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2020 Andre Richter + +//! Driver support. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Driver interfaces. +pub mod interface { + + /// Device Driver functions. + pub trait DeviceDriver { + /// Return a compatibility string for identifying the driver. + fn compatible(&self) -> &str; + + /// Called by the kernel to bring up the device. + fn init(&self) -> Result<(), ()> { + Ok(()) + } + } + + /// Device driver management functions. + /// + /// The `BSP` is supposed to supply one global instance. + pub trait DriverManager { + /// Return a slice of references to all `BSP`-instantiated drivers. + /// + /// # Safety + /// + /// - The order of devices is the order in which `DeviceDriver::init()` is called. + fn all_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)]; + + /// Initialization code that runs after driver init. + /// + /// For example, device driver code that depends on other drivers already being online. + fn post_device_driver_init(&self); + } +} diff --git a/13_integrated_testing/src/arch.rs b/13_integrated_testing/src/exception.rs similarity index 53% rename from 13_integrated_testing/src/arch.rs rename to 13_integrated_testing/src/exception.rs index 98ffc4df..81ea2b26 100644 --- a/13_integrated_testing/src/arch.rs +++ b/13_integrated_testing/src/exception.rs @@ -1,16 +1,21 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // -// Copyright (c) 2018-2020 Andre Richter +// Copyright (c) 2020 Andre Richter -//! Conditional exporting of processor architecture code. +//! Synchronous and asynchronous exception handling. -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] -mod aarch64; +#[cfg(target_arch = "aarch64")] +#[path = "_arch/aarch64/exception.rs"] +mod arch_exception; +pub use arch_exception::*; -#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] -pub use aarch64::*; +pub mod asynchronous; -/// Architectural privilege level. +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Kernel privilege levels. #[allow(missing_docs)] #[derive(PartialEq)] pub enum PrivilegeLevel { @@ -32,7 +37,7 @@ mod tests { /// Libkernel unit tests must execute in kernel mode. #[kernel_test] fn test_runner_executes_in_kernel_mode() { - let (level, _) = state::current_privilege_level(); + let (level, _) = current_privilege_level(); assert!(level == PrivilegeLevel::Kernel) } diff --git a/13_integrated_testing/src/exception/asynchronous.rs b/13_integrated_testing/src/exception/asynchronous.rs new file mode 100644 index 00000000..3c75f90a --- /dev/null +++ b/13_integrated_testing/src/exception/asynchronous.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020 Andre Richter + +//! Asynchronous exception handling. + +#[cfg(target_arch = "aarch64")] +#[path = "../_arch/aarch64/exception/asynchronous.rs"] +mod arch_exception_async; +pub use arch_exception_async::*; diff --git a/13_integrated_testing/src/interface.rs b/13_integrated_testing/src/interface.rs deleted file mode 100644 index 3988beef..00000000 --- a/13_integrated_testing/src/interface.rs +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// -// Copyright (c) 2018-2020 Andre Richter - -//! Trait definitions for coupling `kernel` and `BSP` code. -//! -//! ``` -//! +-------------------+ -//! | Interface (Trait) | -//! | | -//! +--+-------------+--+ -//! ^ ^ -//! | | -//! | | -//! +----------+--+ +--+----------+ -//! | Kernel code | | BSP Code | -//! | | | | -//! +-------------+ +-------------+ -//! ``` - -/// System console operations. -pub mod console { - use core::fmt; - - /// Console write functions. - pub trait Write { - /// Write a single character. - fn write_char(&self, c: char); - - /// 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. - pub trait Read { - /// Read a single character. - fn read_char(&self) -> char { - ' ' - } - - /// Clear RX buffers, if any. - fn clear(&self); - } - - /// Console statistics. - pub trait Statistics { - /// Return the number of characters written. - fn chars_written(&self) -> usize { - 0 - } - - /// Return the number of characters read. - fn chars_read(&self) -> usize { - 0 - } - } - - /// Trait alias for a full-fledged console. - pub trait All = Write + Read + Statistics; -} - -/// Synchronization primitives. -pub mod sync { - /// Any object implementing this trait guarantees exclusive access to the data contained within - /// the mutex for the duration of the lock. - /// - /// The trait follows the [Rust embedded WG's - /// proposal](https://github.com/korken89/wg/blob/master/rfcs/0377-mutex-trait.md) and therefore - /// provides some goodness such as [deadlock - /// prevention](https://github.com/korken89/wg/blob/master/rfcs/0377-mutex-trait.md#design-decisions-and-compatibility). - /// - /// # Example - /// - /// Since the lock function takes an `&mut self` to enable deadlock-prevention, the trait is - /// best implemented **for a reference to a container struct**, and has a usage pattern that - /// might feel strange at first: - /// - /// ``` - /// static MUT: Mutex> = Mutex::new(RefCell::new(0)); - /// - /// fn foo() { - /// let mut r = &MUT; // Note that r is mutable - /// r.lock(|data| *data += 1); - /// } - /// ``` - pub trait Mutex { - /// Type of data encapsulated by the mutex. - type Data; - - /// Creates a critical section and grants temporary mutable access to the encapsulated data. - fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R; - } -} - -/// Driver interfaces. -pub mod driver { - /// Driver result type, e.g. for indicating successful driver init. - pub type Result = core::result::Result<(), ()>; - - /// Device Driver functions. - pub trait DeviceDriver { - /// Return a compatibility string for identifying the driver. - fn compatible(&self) -> &str; - - /// Called by the kernel to bring up the device. - fn init(&self) -> Result { - Ok(()) - } - } -} - -/// Timekeeping interfaces. -pub mod time { - use core::time::Duration; - - /// Timer functions. - pub trait Timer { - /// The timer's resolution. - fn resolution(&self) -> Duration; - - /// The uptime since power-on of the device. - /// - /// This includes time consumed by firmware and bootloaders. - fn uptime(&self) -> Duration; - - /// Spin for a given duration. - fn spin_for(&self, duration: Duration); - } -} - -/// Memory Management interfaces. -pub mod mm { - /// 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>; - } -} diff --git a/13_integrated_testing/src/lib.rs b/13_integrated_testing/src/lib.rs index 9e6c97e8..6c6ed7b7 100644 --- a/13_integrated_testing/src/lib.rs +++ b/13_integrated_testing/src/lib.rs @@ -8,12 +8,111 @@ //! The `kernel` library. //! //! Used by `main.rs` to compose the final kernel binary. +//! +//! # TL;DR - Overview of important Kernel entities +//! +//! - [`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 +//! +//! The code is divided into different *modules*, each representing a typical **subsystem** of the +//! `kernel`. Top-level module files of subsystems reside directly in the `src` folder. For example, +//! `src/memory.rs` contains code that is concerned with all things memory management. +//! +//! ## Visibility of processor architecture code +//! +//! Some of the `kernel`'s subsystems depend on low-level code that is specific to the target +//! processor architecture. For each supported processor architecture, there exists a subfolder in +//! `src/_arch`, for example, `src/_arch/aarch64`. +//! +//! The architecture folders mirror the subsystem modules laid out in `src`. For example, +//! architectural code that belongs to the `kernel`'s memory subsystem (`src/memory.rs`) would go +//! into `src/_arch/aarch64/memory.rs`. The latter file is directly included and re-exported in +//! `src/memory.rs`, so that the architectural code parts are transparent with respect to the code's +//! module organization. That means a public function `foo()` defined in +//! `src/_arch/aarch64/memory.rs` would be reachable as `crate::memory::foo()` only. +//! +//! The `_` in `_arch` denotes that this folder is not part of the standard module hierarchy. +//! Rather, it's contents are conditionally pulled into respective files using the `#[path = +//! "_arch/xxx/yyy.rs"]` attribute. +//! +//! ## BSP code +//! +//! `BSP` stands for Board Support Package. `BSP` code is organized under `src/bsp.rs` and contains +//! target board specific definitions and functions. These are things such as the board's memory map +//! or instances of drivers for devices that are featured on the respective board. +//! +//! Just like processor architecture code, the `BSP` code's module structure tries to mirror the +//! `kernel`'s subsystem modules, but there is no transparent re-exporting this time. That means +//! whatever is provided must be called starting from the `bsp` namespace, e.g. +//! `bsp::driver::driver_manager()`. +//! +//! ## Kernel interfaces +//! +//! Both `arch` and `bsp` contain code that is conditionally compiled depending on the actual target +//! and board for which the kernel is compiled. For example, the `interrupt controller` hardware of +//! the `Raspberry Pi 3` and the `Raspberry Pi 4` is different, but we want the rest of the `kernel` +//! code to play nicely with any of the two without much hassle. +//! +//! In order to provide a clean abstraction between `arch`, `bsp` and `generic kernel code`, +//! `interface` traits are provided *whenever possible* and *where it makes sense*. They are defined +//! in the respective subsystem module and help to enforce the idiom of *program to an interface, +//! not an implementation*. For example, there will be a common IRQ handling interface which the two +//! different interrupt controller `drivers` of both Raspberrys will implement, and only export the +//! interface to the rest of the `kernel`. +//! +//! ``` +//! +-------------------+ +//! | Interface (Trait) | +//! | | +//! +--+-------------+--+ +//! ^ ^ +//! | | +//! | | +//! +----------+--+ +--+----------+ +//! | kernel code | | bsp code | +//! | | | arch code | +//! +-------------+ +-------------+ +//! ``` +//! +//! # Summary +//! +//! For a logical `kernel` subsystem, corresponding code can be distributed over several physical +//! locations. Here is an example for the **memory** subsystem: +//! +//! - `src/memory.rs` and `src/memory/**/*` +//! - Common code that is agnostic of target processor architecture and `BSP` characteristics. +//! - Example: A function to zero a chunk of memory. +//! - Interfaces for the memory subsystem that are implemented by `arch` or `BSP` code. +//! - Example: An `MMU` interface that defines `MMU` function prototypes. +//! - `src/bsp/__board_name__/memory.rs` and `src/bsp/__board_name__/memory/**/*` +//! - `BSP` specific code. +//! - Example: The board's memory map (physical addresses of DRAM and MMIO devices). +//! - `src/_arch/__arch_name__/memory.rs` and `src/_arch/__arch_name__/memory/**/*` +//! - Processor architecture specific code. +//! - Example: Implementation of the `MMU` interface for the `__arch_name__` processor +//! architecture. +//! +//! From a namespace perspective, **memory** subsystem code lives in: +//! +//! - `crate::memory::*` +//! - `crate::bsp::memory::*` #![allow(incomplete_features)] #![feature(const_generics)] +#![feature(custom_inner_attributes)] #![feature(format_args_nl)] #![feature(global_asm)] #![feature(linkage)] +#![feature(naked_functions)] #![feature(panic_info_message)] #![feature(slice_ptr_range)] #![feature(trait_alias)] @@ -24,20 +123,21 @@ #![reexport_test_harness_main = "test_main"] #![test_runner(crate::test_runner)] -// Conditionally includes the selected `architecture` code, which provides the `_start()` function, -// the first function to run. -pub mod arch; +// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls +// `runtime_init()`, which jumps to `kernel_init()` (defined in `main.rs`). -// `_start()` then calls `runtime_init()`, which on completion, jumps to `kernel_init()`. +mod panic_wait; mod runtime_init; +mod synchronization; -// Conditionally includes the selected `BSP` code. pub mod bsp; - -pub mod interface; -mod memory; -mod panic_wait; +pub mod console; +pub mod cpu; +pub mod driver; +pub mod exception; +pub mod memory; pub mod print; +pub mod time; //-------------------------------------------------------------------------------------------------- // Testing @@ -62,9 +162,9 @@ pub fn test_runner(tests: &[&test_types::UnitTest]) { #[cfg(test)] #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + bsp::console::qemu_bring_up_console(); test_main(); - arch::qemu_exit_success() + cpu::qemu_exit_success() } diff --git a/13_integrated_testing/src/main.rs b/13_integrated_testing/src/main.rs index 6847a505..56410284 100644 --- a/13_integrated_testing/src/main.rs +++ b/13_integrated_testing/src/main.rs @@ -6,29 +6,15 @@ #![doc(html_logo_url = "https://git.io/JeGIp")] //! The `kernel` binary. -//! -//! The `kernel` is composed by glueing together code from -//! -//! - [Hardware-specific Board Support Packages] (`BSPs`). -//! - [Architecture-specific code]. -//! - HW- and architecture-agnostic `kernel` code. -//! -//! using the [`kernel::interface`] traits. -//! -//! [Hardware-specific Board Support Packages]: bsp/index.html -//! [Architecture-specific code]: arch/index.html -//! [`kernel::interface`]: interface/index.html #![feature(format_args_nl)] #![no_main] #![no_std] -use libkernel::{arch, bsp, info, interface}; +use libkernel::{bsp, console, driver, exception, info, memory, time}; /// Early init code. /// -/// Concerned with with initializing `BSP` and `arch` parts. -/// /// # Safety /// /// - Only a single core must be active and running this function. @@ -39,20 +25,21 @@ use libkernel::{arch, bsp, info, interface}; /// the RPi SoCs. #[no_mangle] unsafe fn kernel_init() -> ! { - use interface::mm::MMU; + use driver::interface::DriverManager; + use memory::mmu::interface::MMU; - arch::enable_exception_handling(); + exception::handling_init(); - if let Err(string) = arch::mmu().init() { + if let Err(string) = memory::mmu::mmu().init() { panic!("MMU: {}", string); } - for i in bsp::device_drivers().iter() { - if let Err(()) = i.init() { + for i in bsp::driver::driver_manager().all_device_drivers().iter() { + if i.init().is_err() { panic!("Error loading driver: {}", i.compatible()) } } - bsp::post_driver_init(); + bsp::driver::driver_manager().post_device_driver_init(); // println! is usable from here on. // Transition from unsafe to safe. @@ -61,32 +48,37 @@ unsafe fn kernel_init() -> ! { /// The main function running after the early init. fn kernel_main() -> ! { - use interface::console::All; + use console::interface::All; + use driver::interface::DriverManager; info!("Booting on: {}", bsp::board_name()); info!("MMU online. Special regions:"); - bsp::virt_mem_layout().print_layout(); + bsp::memory::mmu::virt_mem_layout().print_layout(); - let (_, privilege_level) = arch::state::current_privilege_level(); + let (_, privilege_level) = exception::current_privilege_level(); info!("Current privilege level: {}", privilege_level); info!("Exception handling state:"); - arch::state::print_exception_state(); + exception::asynchronous::print_state(); info!( "Architectural timer resolution: {} ns", - arch::timer().resolution().as_nanos() + time::time_manager().resolution().as_nanos() ); info!("Drivers loaded:"); - for (i, driver) in bsp::device_drivers().iter().enumerate() { + for (i, driver) in bsp::driver::driver_manager() + .all_device_drivers() + .iter() + .enumerate() + { info!(" {}. {}", i + 1, driver.compatible()); } info!("Echoing input now"); loop { - let c = bsp::console().read_char(); - bsp::console().write_char(c); + let c = bsp::console::console().read_char(); + bsp::console::console().write_char(c); } } diff --git a/13_integrated_testing/src/memory.rs b/13_integrated_testing/src/memory.rs index ee7c7967..4a68a6d9 100644 --- a/13_integrated_testing/src/memory.rs +++ b/13_integrated_testing/src/memory.rs @@ -4,10 +4,13 @@ //! Memory Management. -use core::{ - fmt, - ops::{Range, RangeInclusive}, -}; +pub mod mmu; + +use core::ops::Range; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- /// Zero out a memory region. /// @@ -27,151 +30,6 @@ where } } -#[derive(Copy, Clone)] -pub enum Translation { - Identity, - Offset(usize), -} - -#[derive(Copy, Clone)] -pub enum MemAttributes { - CacheableDRAM, - Device, -} - -#[derive(Copy, Clone)] -pub enum AccessPermissions { - ReadOnly, - ReadWrite, -} - -#[derive(Copy, Clone)] -pub struct AttributeFields { - pub mem_attributes: MemAttributes, - pub acc_perms: AccessPermissions, - pub execute_never: bool, -} - -impl Default for AttributeFields { - fn default() -> AttributeFields { - AttributeFields { - mem_attributes: MemAttributes::CacheableDRAM, - acc_perms: AccessPermissions::ReadWrite, - execute_never: true, - } - } -} - -/// An architecture agnostic descriptor for a memory range. -pub struct RangeDescriptor { - pub name: &'static str, - pub virtual_range: fn() -> RangeInclusive, - pub translation: Translation, - pub attribute_fields: AttributeFields, -} - -/// 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 - ) - } -} - -/// Type for expressing the kernel's virtual memory layout. -pub struct KernelVirtualLayout { - max_virt_addr_inclusive: usize, - inner: [RangeDescriptor; NUM_SPECIAL_RANGES], -} - -impl KernelVirtualLayout<{ NUM_SPECIAL_RANGES }> { - 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); - } - } - - #[cfg(test)] - pub fn inner(&self) -> &[RangeDescriptor; NUM_SPECIAL_RANGES] { - &self.inner - } -} - //-------------------------------------------------------------------------------------------------- // Testing //-------------------------------------------------------------------------------------------------- diff --git a/13_integrated_testing/src/memory/mmu.rs b/13_integrated_testing/src/memory/mmu.rs new file mode 100644 index 00000000..8d789704 --- /dev/null +++ b/13_integrated_testing/src/memory/mmu.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020 Andre Richter + +//! 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, + pub translation: Translation, + pub attribute_fields: AttributeFields, +} + +/// Type for expressing the kernel's virtual memory layout. +pub struct KernelVirtualLayout { + /// 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 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); + } + } + + #[cfg(test)] + pub fn inner(&self) -> &[RangeDescriptor; NUM_SPECIAL_RANGES] { + &self.inner + } +} diff --git a/13_integrated_testing/src/panic_wait.rs b/13_integrated_testing/src/panic_wait.rs index 5bf45bc1..218c0a88 100644 --- a/13_integrated_testing/src/panic_wait.rs +++ b/13_integrated_testing/src/panic_wait.rs @@ -4,23 +4,17 @@ //! A panic handler that infinitely waits. -use crate::{arch, bsp}; +use crate::{bsp, cpu}; use core::{fmt, panic::PanicInfo}; +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + fn _panic_print(args: fmt::Arguments) { use fmt::Write; - unsafe { bsp::panic_console_out().write_fmt(args).unwrap() }; -} - -/// Prints with a newline - only use from the panic handler. -/// -/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html -#[macro_export] -macro_rules! panic_println { - ($($arg:tt)*) => ({ - _panic_print(format_args_nl!($($arg)*)); - }) + unsafe { bsp::console::panic_console_out().write_fmt(args).unwrap() }; } /// The point of exit for the "standard" (non-testing) `libkernel`. @@ -31,13 +25,23 @@ macro_rules! panic_println { /// /// This is one possible approach to solve the problem that `cargo` can not know who the consumer of /// the library will be: -/// - The release kernel binary that should safely park the paniced core, -/// - or an `integration test` that is executed in QEMU, which should just exit QEMU. +/// - The release kernel binary that should safely park the paniced core, +/// - or an `integration test` that is executed in QEMU, which should just exit QEMU. #[cfg(not(test))] #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - arch::wait_forever() + cpu::wait_forever() +} + +/// Prints with a newline - only use from the panic handler. +/// +/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! panic_println { + ($($arg:tt)*) => ({ + _panic_print(format_args_nl!($($arg)*)); + }) } #[panic_handler] @@ -59,5 +63,5 @@ fn panic(info: &PanicInfo) -> ! { #[cfg(test)] #[no_mangle] fn _panic_exit() -> ! { - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } diff --git a/13_integrated_testing/src/print.rs b/13_integrated_testing/src/print.rs index 1d0736af..cc303bfc 100644 --- a/13_integrated_testing/src/print.rs +++ b/13_integrated_testing/src/print.rs @@ -4,16 +4,24 @@ //! Printing facilities. -use crate::{bsp, interface}; +use crate::{bsp, console}; use core::fmt; +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + #[doc(hidden)] pub fn _print(args: fmt::Arguments) { - use interface::console::Write; + use console::interface::Write; - bsp::console().write_fmt(args).unwrap(); + bsp::console::console().write_fmt(args).unwrap(); } +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + /// Prints without a newline. /// /// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html @@ -33,14 +41,14 @@ macro_rules! println { }) } -/// Prints an info, with newline. +/// Prints an info, with a newline. #[macro_export] macro_rules! info { ($string:expr) => ({ #[allow(unused_imports)] - use crate::interface::time::Timer; + use crate::time::interface::TimeManager; - let timestamp = $crate::arch::timer().uptime(); + let timestamp = $crate::time::time_manager().uptime(); let timestamp_subsec_us = timestamp.subsec_micros(); $crate::print::_print(format_args_nl!( @@ -52,9 +60,9 @@ macro_rules! info { }); ($format_string:expr, $($arg:tt)*) => ({ #[allow(unused_imports)] - use crate::interface::time::Timer; + use crate::time::interface::TimeManager; - let timestamp = $crate::arch::timer().uptime(); + let timestamp = $crate::time::time_manager().uptime(); let timestamp_subsec_us = timestamp.subsec_micros(); $crate::print::_print(format_args_nl!( @@ -67,14 +75,14 @@ macro_rules! info { }) } -/// Prints a warning, with newline. +/// Prints a warning, with a newline. #[macro_export] macro_rules! warn { ($string:expr) => ({ #[allow(unused_imports)] - use crate::interface::time::Timer; + use crate::time::interface::TimeManager; - let timestamp = $crate::arch::timer().uptime(); + let timestamp = $crate::time::time_manager().uptime(); let timestamp_subsec_us = timestamp.subsec_micros(); $crate::print::_print(format_args_nl!( @@ -86,9 +94,9 @@ macro_rules! warn { }); ($format_string:expr, $($arg:tt)*) => ({ #[allow(unused_imports)] - use crate::interface::time::Timer; + use crate::time::interface::TimeManager; - let timestamp = $crate::arch::timer().uptime(); + let timestamp = $crate::time::time_manager().uptime(); let timestamp_subsec_us = timestamp.subsec_micros(); $crate::print::_print(format_args_nl!( diff --git a/13_integrated_testing/src/runtime_init.rs b/13_integrated_testing/src/runtime_init.rs index ac56eeb3..04cca287 100644 --- a/13_integrated_testing/src/runtime_init.rs +++ b/13_integrated_testing/src/runtime_init.rs @@ -7,6 +7,10 @@ use crate::memory; use core::ops::Range; +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + /// Return the range spanning the .bss section. /// /// # Safety @@ -36,6 +40,10 @@ unsafe fn zero_bss() { memory::zero_volatile(bss_range()); } +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + /// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel /// init code. /// @@ -48,7 +56,6 @@ pub unsafe fn runtime_init() -> ! { } zero_bss(); - kernel_init() } diff --git a/13_integrated_testing/src/synchronization.rs b/13_integrated_testing/src/synchronization.rs new file mode 100644 index 00000000..caa2794a --- /dev/null +++ b/13_integrated_testing/src/synchronization.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020 Andre Richter + +//! 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> = 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(&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 { + data: UnsafeCell, +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +unsafe impl Sync for NullLock {} + +impl NullLock { + /// Wraps `data` into a new `NullLock`. + pub const fn new(data: T) -> Self { + Self { + data: UnsafeCell::new(data), + } + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ + +impl interface::Mutex for &NullLock { + type Data = T; + + fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + let data = unsafe { &mut *self.data.get() }; + + f(data) + } +} diff --git a/13_integrated_testing/src/time.rs b/13_integrated_testing/src/time.rs new file mode 100644 index 00000000..cd3ceec3 --- /dev/null +++ b/13_integrated_testing/src/time.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020 Andre Richter + +//! 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); + } +} diff --git a/13_integrated_testing/tests/00_interface_sanity_console.rs b/13_integrated_testing/tests/00_interface_sanity_console.rs index d76376cd..5aa38f09 100644 --- a/13_integrated_testing/tests/00_interface_sanity_console.rs +++ b/13_integrated_testing/tests/00_interface_sanity_console.rs @@ -10,23 +10,26 @@ mod panic_exit_failure; -use libkernel::{bsp, interface::console::*, print}; +use libkernel::{bsp, console, print}; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + use bsp::console::{console, qemu_bring_up_console}; + use console::interface::*; + + qemu_bring_up_console(); // Handshake - assert_eq!(bsp::console().read_char(), 'A'); - assert_eq!(bsp::console().read_char(), 'B'); - assert_eq!(bsp::console().read_char(), 'C'); + assert_eq!(console().read_char(), 'A'); + assert_eq!(console().read_char(), 'B'); + assert_eq!(console().read_char(), 'C'); print!("OK1234"); // 6 - print!("{}", bsp::console().chars_written()); + print!("{}", console().chars_written()); // 3 - print!("{}", bsp::console().chars_read()); + print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. loop {} diff --git a/13_integrated_testing/tests/01_interface_sanity_timer.rs b/13_integrated_testing/tests/01_interface_sanity_timer.rs index b8711d7a..e0b3c162 100644 --- a/13_integrated_testing/tests/01_interface_sanity_timer.rs +++ b/13_integrated_testing/tests/01_interface_sanity_timer.rs @@ -13,38 +13,38 @@ mod panic_exit_failure; use core::time::Duration; -use libkernel::{arch, arch::timer, bsp, interface::time::Timer}; +use libkernel::{bsp, cpu, time, time::interface::TimeManager}; use test_macros::kernel_test; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + bsp::console::qemu_bring_up_console(); // Depending on CPU arch, some timer bring-up code could go here. Not needed for the RPi. test_main(); - arch::qemu_exit_success() + cpu::qemu_exit_success() } /// Simple check that the timer is running. #[kernel_test] fn timer_is_counting() { - assert!(timer().uptime().as_nanos() > 0) + assert!(time::time_manager().uptime().as_nanos() > 0) } /// Timer resolution must be sufficient. #[kernel_test] fn timer_resolution_is_sufficient() { - assert!(timer().resolution().as_nanos() < 100) + assert!(time::time_manager().resolution().as_nanos() < 100) } /// Sanity check spin_for() implementation. #[kernel_test] fn spin_accuracy_check_1_second() { - let t1 = timer().uptime(); - timer().spin_for(Duration::from_secs(1)); - let t2 = timer().uptime(); + let t1 = time::time_manager().uptime(); + time::time_manager().spin_for(Duration::from_secs(1)); + let t2 = time::time_manager().uptime(); assert_eq!((t2 - t1).as_secs(), 1) } diff --git a/13_integrated_testing/tests/02_arch_exception_handling.rs b/13_integrated_testing/tests/02_arch_exception_handling_sync_page_fault.rs similarity index 79% rename from 13_integrated_testing/tests/02_arch_exception_handling.rs rename to 13_integrated_testing/tests/02_arch_exception_handling_sync_page_fault.rs index ee24f90b..64fc5486 100644 --- a/13_integrated_testing/tests/02_arch_exception_handling.rs +++ b/13_integrated_testing/tests/02_arch_exception_handling_sync_page_fault.rs @@ -17,20 +17,22 @@ /// or indirectly. mod panic_exit_success; -use libkernel::{arch, bsp, interface::mm::MMU, println}; +use libkernel::{bsp, cpu, exception, memory, println}; #[no_mangle] unsafe fn kernel_init() -> ! { - bsp::qemu_bring_up_console(); + use memory::mmu::interface::MMU; + + bsp::console::qemu_bring_up_console(); println!("Testing synchronous exception handling by causing a page fault"); println!("-------------------------------------------------------------------\n"); - arch::enable_exception_handling(); + exception::handling_init(); - if let Err(string) = arch::mmu().init() { + if let Err(string) = memory::mmu::mmu().init() { println!("MMU: {}", string); - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } println!("Writing beyond mapped area to address 9 GiB..."); @@ -38,5 +40,5 @@ unsafe fn kernel_init() -> ! { core::ptr::read_volatile(big_addr as *mut u64); // If execution reaches here, the memory access above did not cause a page fault exception. - arch::qemu_exit_failure() + cpu::qemu_exit_failure() } diff --git a/13_integrated_testing/tests/panic_exit_failure/mod.rs b/13_integrated_testing/tests/panic_exit_failure/mod.rs index a7f41825..b4ac73d1 100644 --- a/13_integrated_testing/tests/panic_exit_failure/mod.rs +++ b/13_integrated_testing/tests/panic_exit_failure/mod.rs @@ -5,5 +5,5 @@ /// Overwrites libkernel's `panic_wait::_panic_exit()` with the QEMU-exit version. #[no_mangle] fn _panic_exit() -> ! { - libkernel::arch::qemu_exit_failure() + libkernel::cpu::qemu_exit_failure() } diff --git a/13_integrated_testing/tests/panic_exit_success/mod.rs b/13_integrated_testing/tests/panic_exit_success/mod.rs index f9a97afa..54bb072d 100644 --- a/13_integrated_testing/tests/panic_exit_success/mod.rs +++ b/13_integrated_testing/tests/panic_exit_success/mod.rs @@ -5,5 +5,5 @@ /// Overwrites libkernel's `panic_wait::_panic_exit()` with the QEMU-exit version. #[no_mangle] fn _panic_exit() -> ! { - libkernel::arch::qemu_exit_success() + libkernel::cpu::qemu_exit_success() } diff --git a/doc/13_demo.gif b/doc/13_demo.gif new file mode 100644 index 00000000..4482c621 Binary files /dev/null and b/doc/13_demo.gif differ diff --git a/doc/testing_demo.gif b/doc/testing_demo.gif deleted file mode 100644 index b9e11158..00000000 Binary files a/doc/testing_demo.gif and /dev/null differ