diff --git a/.gitignore b/.gitignore index 94499626..7618b873 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ xbuild_sysroot **/target/* +**/.gdb_history diff --git a/09_hw_debug_JTAG/.vscode/settings.json b/09_hw_debug_JTAG/.vscode/settings.json new file mode 100644 index 00000000..f2fa6961 --- /dev/null +++ b/09_hw_debug_JTAG/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.formatOnSave": true, + "rust.features": [ + "bsp_rpi3" + ], + "rust.all_targets": false, + "editor.rulers": [ + 100 + ], +} \ No newline at end of file diff --git a/09_hw_debug_JTAG/Cargo.lock b/09_hw_debug_JTAG/Cargo.lock new file mode 100644 index 00000000..9a17339b --- /dev/null +++ b/09_hw_debug_JTAG/Cargo.lock @@ -0,0 +1,42 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "cortex-a" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "r0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "register" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tock-registers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbb16c411ab74044f174746a6cbae67bcdebea126e376b5441e5986e6a6aa950" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "469bb5ddde81d67fb8bba4e14d77689b8166cfd077abe7530591cefe29d05823" +"checksum tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c758f5195a2e0df9d9fecf6f506506b2766ff74cf64db1e995c87e2761a5c3e2" diff --git a/09_hw_debug_JTAG/Cargo.toml b/09_hw_debug_JTAG/Cargo.toml new file mode 100644 index 00000000..cf0f0636 --- /dev/null +++ b/09_hw_debug_JTAG/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kernel" +version = "0.1.0" +authors = ["Andre Richter "] +edition = "2018" + +[package.metadata.cargo-xbuild] +sysroot_path = "../xbuild_sysroot" + +# The features section is used to select the target board. +[features] +default = [] +bsp_rpi3 = ["cortex-a", "register"] +bsp_rpi4 = ["cortex-a", "register"] + +[dependencies] +r0 = "0.2.*" + +# Optional dependencies +cortex-a = { version = "2.*", optional = true } +register = { version = "0.3.*", optional = true } diff --git a/09_hw_debug_JTAG/Makefile b/09_hw_debug_JTAG/Makefile new file mode 100644 index 00000000..4cf5e508 --- /dev/null +++ b/09_hw_debug_JTAG/Makefile @@ -0,0 +1,127 @@ +## SPDX-License-Identifier: MIT +## +## Copyright (c) 2018-2019 Andre Richter + +# Default to the RPi3 +ifndef BSP + BSP = rpi3 +endif + +# BSP-specific arguments +ifeq ($(BSP),rpi3) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = raspi3 + QEMU_MISC_ARGS = -serial stdio + 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 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 +else ifeq ($(BSP),rpi4) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img +# QEMU_BINARY = qemu-system-aarch64 +# QEMU_MACHINE_TYPE = +# QEMU_MISC_ARGS = -serial stdio + 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 + +SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) $(wildcard **/*.ld) + +XRUSTC_CMD = cargo xrustc \ + --target=$(TARGET) \ + --features bsp_$(BSP) \ + --release \ + -- \ + -C link-arg=-T$(LINKER_FILE) \ + $(RUSTC_MISC_ARGS) + +CARGO_OUTPUT = target/$(TARGET)/release/kernel + +OBJCOPY_CMD = cargo objcopy \ + -- \ + --strip-all \ + -O binary + +CONTAINER_UTILS = rustembedded/osdev-utils + +DOCKER_CMD = docker run -it --rm +DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work +DOCKER_ARG_TTY = --privileged -v /dev:/dev +DOCKER_ARG_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag +DOCKER_ARG_NET = --network host + +DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -kernel $(OUTPUT) +DOCKER_EXEC_RASPBOOT = raspbootcom +DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyUSB0 +# DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyACM0 + +.PHONY: all doc qemu chainboot clippy clean readelf objdump nm + +all: clean $(OUTPUT) + +$(CARGO_OUTPUT): $(SOURCES) + RUSTFLAGS="-D warnings -D missing_docs" $(XRUSTC_CMD) + +$(OUTPUT): $(CARGO_OUTPUT) + cp $< . + $(OBJCOPY_CMD) $< $(OUTPUT) + +doc: + cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items + xdg-open target/$(TARGET)/doc/kernel/index.html + +ifeq ($(QEMU_MACHINE_TYPE),) +qemu: + @echo "This board is not yet supported for QEMU." +else +qemu: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_QEMU) $(QEMU_MISC_ARGS) +endif + +chainboot: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_TTY) \ + $(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + $(OUTPUT) + +jtagboot: + $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_JTAG) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + /jtag/$(JTAG_BOOT_IMAGE) + +openocd: + $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ + openocd $(OPENOCD_ARG) + +define gen_gdb + RUSTFLAGS="-D warnings -D missing_docs" $(XRUSTC_CMD) $1 + cp $(CARGO_OUTPUT) kernel_for_jtag + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ + gdb-multiarch -q kernel_for_jtag +endef + +gdb: clean $(SOURCES) + $(call gen_gdb,-C debuginfo=2) + +gdb-opt0: clean $(SOURCES) + $(call gen_gdb,-C debuginfo=2 -C opt-level=0) + +clippy: + cargo xclippy --target=$(TARGET) --features bsp_$(BSP) + +clean: + cargo clean + +readelf: + readelf -a kernel + +objdump: + cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel + +nm: + cargo nm --target $(TARGET) -- kernel | sort diff --git a/09_hw_debug_JTAG/README.md b/09_hw_debug_JTAG/README.md new file mode 100644 index 00000000..6cf67e5a --- /dev/null +++ b/09_hw_debug_JTAG/README.md @@ -0,0 +1,335 @@ +# Tutorial 09 - Hardware Debugging using JTAG + +In the upcoming tutorials, we are going to touch sensitive areas of the RPi's SoC that can make our +debugging life very hard. For example, changing the processor's `Privilege Level` or introducing +`Virtual Memory`. + +A hardware based debugger can sometimes be the last resort when searching for a tricky bug. +Especially for debugging intricate, architecture-specific HW issues, it will be handy, because in +this area `QEMU` sometimes can not help, since it abstracts certain features of the HW and doesn't +simulate down to the very last bit. + +So lets introduce `JTAG` debugging. Once set up, it will allow us to single-step through our kernel +on the real HW. How cool is that?! + +![JTAG live demo](../doc/jtag_demo.gif) + +## Outline + +From kernel perspective, this tutorial is the same as the previous one. We are just wrapping +infrastructure for JTAG debugging around it. + +## Software Setup + +We need to add another line to the `config.txt` file from the SD Card: + +```toml +init_uart_clock=48000000 +enable_jtag_gpio=1 +``` + +## Hardware Setup + +Unlike microcontroller boards like the `STM32F3DISCOVERY`, which is used in our WG's [Embedded Rust +Book](https://rust-embedded.github.io/book/start/hardware.html), the Raspberry Pi does not have an +embedded debugger on its board. Hence, you need to buy one. + +For this tutorial, we will use the [ARM-USB-TINY-H] from OLIMEX. It has a standard [ARM JTAG 20 +connector]. Unfortunately, the RPi does not, so we have to connect it via jumper wires. + +[ARM-USB-TINY-H]: https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H +[ARM JTAG 20 connector]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0499dj/BEHEIHCE.html + +### Wiring + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GPIO #NameJTAG #NoteDiagram
VTREF1to 3.3V
GND4to GND
22TRST3
26TDI5
27TMS7
25TCK9
23RTCK11
24TDO13
+ +

+ +## Getting ready to connect + +Upon booting, thanks to the changes we made to `config.txt`, the RPi's firmware will configure the +respective GPIO pins for `JTAG` functionality. + +What is left to do now is to pause the execution of the RPi and then connect +over `JTAG`. Therefore, we add a new `Makefile` target, `make jtagboot`, which +uses the `chainboot` approach to load a tiny helper binary onto the RPi that +just parks the executing core into a waiting state. + +The helper binary is maintained separately in this repository's [X1_JTAG_boot](../X1_JTAG_boot) +folder, and is a modified version of the kernel we used in our tutorials so far. + +```console +make jtagboot +[...] +Raspbootcom V1.0 +### Listening on /dev/ttyUSB0 + __ __ _ _ _ _ +| \/ (_)_ _ (_) | ___ __ _ __| | +| |\/| | | ' \| | |__/ _ \/ _` / _` | +|_| |_|_|_||_|_|____\___/\__,_\__,_| + + Raspberry Pi 3 + +[ML] Reqbinary +### sending kernel /jtag/jtag_boot_rpi3.img [8960 byte] +### finished sending +[ML] Loaded! Executing the payload now + +[ 0.805909] Parking CPU core. Please connect over JTAG now. +``` + +It is important to keep the USB serial connected and the terminal with the `jtagboot` open and +running. When we load the actual kernel later, `UART` output will appear here. + +## OpenOCD + +Next, we need to launch the [Open On-Chip Debugger](http://openocd.org/), aka `OpenOCD` to actually +connect the `JTAG`. + +As always, our tutorials try to be as painless as possible regarding dev-tools, which is why we have +packaged everything into the [dedicated Docker container](../docker/rustembedded-osdev-utils) that +is already used for chainbooting and `QEMU`. + +Connect the Olimex USB JTAG debugger, open a new terminal and in the same folder, type `make +openocd` (in that order!). You will see some initial output: + +```console +make openocd +[...] +Open On-Chip Debugger 0.10.0 +[...] +Info : Listening on port 6666 for tcl connections +Info : Listening on port 4444 for telnet connections +Info : clock speed 1000 kHz +Info : JTAG tap: rpi3.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd.), part: 0xba00, ver: 0x4) +Info : rpi3.core0: hardware has 6 breakpoints, 4 watchpoints +Info : rpi3.core1: hardware has 6 breakpoints, 4 watchpoints +Info : rpi3.core2: hardware has 6 breakpoints, 4 watchpoints +Info : rpi3.core3: hardware has 6 breakpoints, 4 watchpoints +Info : Listening on port 3333 for gdb connections +Info : Listening on port 3334 for gdb connections +Info : Listening on port 3335 for gdb connections +Info : Listening on port 3336 for gdb connections +``` + +`OpenOCD` has detected the four cores of the RPi, and opened four network ports to which `gdb` can +now connect to debug the respective core. + +## GDB + +Finally, we need an `AArch64`-capable version of `gdb`. You guessed right, it's already packaged in +the osdev container. It can be launched via `make gdb`. + +This Makefile target actually does a little more. It builds a special version of our kernel with +debug information included. This enables `gdb` to show the `Rust` source code line we are currently +debugging. It also launches `gdb` such that it already loads this debug build (`kernel_for_jtag`). + +We can now use the `gdb` commandline to + 1. Set breakpoints in our kernel + 2. Load the kernel via JTAG into memory (remember that currently, the RPi is still executing the + minimal JTAG boot binary). + 3. Manipulate the program counter of the RPi to start execution at our kernel's entry point. + 4. Single-step through its execution. + +```shell +make gdb +[...] +>>> target remote :3333 # Connect to OpenOCD, core0 +>>> load # Load the kernel into the RPi's DRAM over JTAG. +Loading section .text, size 0x2660 lma 0x80000 +Loading section .rodata, size 0xfa5 lma 0x82660 +Loading section .data, size 0x18 lma 0x83608 +Start address 0x80000, load size 13853 +Transfer rate: 65 KB/sec, 4617 bytes/write. +>>> set $pc = 0x80000 # Set RPI's program counter to the start of the + # kernel binary. +>>> break main.rs:70 +Breakpoint 1 at 0x80124: file src/main.rs, line 70. +>>> cont +Breakpoint 1, kernel::kernel_main () at src/main.rs:70 +70 println!("Booting on: {}", bsp::board_name()); +>>> step # Single-step through the kernel +>>> step +>>> ... +``` + +### Remarks + +#### Optimization + +When debugging an OS binary, you have to make a trade-off between the granularity at which you can +step through your Rust source-code and the optimization level of the generated binary. The `make` +and `make gdb` targets produce a `--release` binary, which includes an optimization level of three +(`-opt-level=3`). However, in this case, the compiler will inline very aggressively and pack +together reads and writes where possible. As a result, it will not always be possible to hit +breakpoints exactly where you want to regarding the line of source code file. + +For this reason, the Makefile also provides the `make gdb-opt0` target, which uses `-opt-level=0`. +Hence, it will allow you to have finer debugging granularity. However, please keep in mind that when +debugging code that closely deals with HW, a compiler optimization that squashes reads or writes to +volatile registers can make all the difference in execution. FYI, the demo gif above has been +recorded with `gdb-opt0`. + +#### GDB control + +At some point, you may reach delay loops or code that waits on user input from the serial. Here, +single stepping might not be feasible or work anymore. You can jump over these roadblocks by setting +other breakpoints beyond these areas, and reach them using the `cont` command. + +Pressing `ctrl+c` in `gdb` will stop execution of the RPi again in case you continued it without +further breakpoints. + +## Notes on USB connection constraints + +If you followed the tutorial from top to bottom, everything should be fine regarding USB +connections. + +Still, please note that in its current form, our `Makefile` makes implicit assumptions about the +naming of the connected USB devices. It expects `/dev/ttyUSB0` to be the `UART` device. + +Hence, please ensure the following order of connecting the devices to your box: + 1. Connect the USB serial. + 2. Afterwards, the Olimex debugger. + +This way, Linux enumerates the devices accordingly. This has to be done only once. It is fine to +disconnect and connect the serial multiple times, e.g. for kicking off different `make jtagboot` +runs, while keeping the debugger connected. + +## In summary + +1. `make jtagboot` and keep terminal open. +2. Connect USB serial device. +3. Connect `JTAG` debugger USB device. +4. In new terminal, `make openocd`. +5. In new terminal, `make gdb` or make `make gdb-opt0`. + +## Acknowledgments + +Thanks to [@naotaco](https://github.com/naotaco) for laying the groundwork for this tutorial. + +## Diff to previous +```diff +Binary files 08_timestamps/kernel_for_jtag and 09_hw_debug_JTAG/kernel_for_jtag differ + +diff -uNr 08_timestamps/Makefile 09_hw_debug_JTAG/Makefile +--- 08_timestamps/Makefile ++++ 09_hw_debug_JTAG/Makefile +@@ -14,6 +14,8 @@ + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = raspi3 + QEMU_MISC_ARGS = -serial stdio ++ 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 + RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 + else ifeq ($(BSP),rpi4) +@@ -22,6 +24,8 @@ + # QEMU_BINARY = qemu-system-aarch64 + # QEMU_MACHINE_TYPE = + # QEMU_MISC_ARGS = -serial stdio ++ 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 +@@ -48,6 +52,8 @@ + DOCKER_CMD = docker run -it --rm + DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work + DOCKER_ARG_TTY = --privileged -v /dev:/dev ++DOCKER_ARG_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag ++DOCKER_ARG_NET = --network host + + DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -kernel $(OUTPUT) + DOCKER_EXEC_RASPBOOT = raspbootcom +@@ -83,6 +89,28 @@ + $(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + $(OUTPUT) + ++jtagboot: ++ $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_JTAG) $(CONTAINER_UTILS) \ ++ $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ ++ /jtag/$(JTAG_BOOT_IMAGE) ++ ++openocd: ++ $(DOCKER_CMD) $(DOCKER_ARG_TTY) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ ++ openocd $(OPENOCD_ARG) ++ ++define gen_gdb ++ RUSTFLAGS="-D warnings -D missing_docs" $(XRUSTC_CMD) $1 ++ cp $(CARGO_OUTPUT) kernel_for_jtag ++ $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_NET) $(CONTAINER_UTILS) \ ++ gdb-multiarch -q kernel_for_jtag ++endef ++ ++gdb: clean $(SOURCES) ++ $(call gen_gdb,-C debuginfo=2) ++ ++gdb-opt0: clean $(SOURCES) ++ $(call gen_gdb,-C debuginfo=2 -C opt-level=0) ++ + clippy: + cargo xclippy --target=$(TARGET) --features bsp_$(BSP) + +``` diff --git a/09_hw_debug_JTAG/kernel b/09_hw_debug_JTAG/kernel new file mode 100755 index 00000000..3bfb485a Binary files /dev/null and b/09_hw_debug_JTAG/kernel differ diff --git a/09_hw_debug_JTAG/kernel8.img b/09_hw_debug_JTAG/kernel8.img new file mode 100755 index 00000000..e6f58b22 Binary files /dev/null and b/09_hw_debug_JTAG/kernel8.img differ diff --git a/09_hw_debug_JTAG/src/arch.rs b/09_hw_debug_JTAG/src/arch.rs new file mode 100644 index 00000000..b1f035c5 --- /dev/null +++ b/09_hw_debug_JTAG/src/arch.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of processor architecture code. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod aarch64; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use aarch64::*; diff --git a/09_hw_debug_JTAG/src/arch/aarch64.rs b/09_hw_debug_JTAG/src/arch/aarch64.rs new file mode 100644 index 00000000..a44ece9b --- /dev/null +++ b/09_hw_debug_JTAG/src/arch/aarch64.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! AArch64. + +pub mod sync; +mod time; + +use crate::{bsp, interface}; +use cortex_a::{asm, regs::*}; + +/// The entry of the `kernel` binary. +/// +/// The function must be named `_start`, because the linker is looking for this exact name. +/// +/// # Safety +/// +/// - Linker script must ensure to place this function at `0x80_000`. +#[no_mangle] +pub unsafe extern "C" fn _start() -> ! { + const CORE_MASK: u64 = 0x3; + + if bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK { + SP.set(bsp::BOOT_CORE_STACK_START); + crate::runtime_init::init() + } else { + // if not core0, infinitely wait for events + wait_forever() + } +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static TIMER: time::Timer = time::Timer; + +//-------------------------------------------------------------------------------------------------- +// Implementation of the kernel's architecture abstraction code +//-------------------------------------------------------------------------------------------------- + +pub use asm::nop; + +/// Spin for `n` cycles. +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. +#[inline(always)] +pub fn wait_forever() -> ! { + loop { + asm::wfe() + } +} diff --git a/09_hw_debug_JTAG/src/arch/aarch64/sync.rs b/09_hw_debug_JTAG/src/arch/aarch64/sync.rs new file mode 100644 index 00000000..dfebc0e1 --- /dev/null +++ b/09_hw_debug_JTAG/src/arch/aarch64/sync.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Synchronization primitives. + +use crate::interface; +use core::cell::UnsafeCell; + +/// A pseudo-lock for teaching purposes. +/// +/// Used to introduce [interior mutability]. +/// +/// In contrast to a real Mutex implementation, does not protect against concurrent access to the +/// contained data. This part is preserved for later lessons. +/// +/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is +/// executing single-threaded, aka only running on a single core with interrupts disabled. +/// +/// [interior mutability]: https://doc.rust-lang.org/std/cell/index.html +pub struct NullLock { + data: UnsafeCell, +} + +unsafe impl Send for NullLock {} +unsafe impl Sync for NullLock {} + +impl NullLock { + pub const fn new(data: T) -> NullLock { + NullLock { + data: UnsafeCell::new(data), + } + } +} + +impl interface::sync::Mutex for &NullLock { + type Data = T; + + fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + f(unsafe { &mut *self.data.get() }) + } +} diff --git a/09_hw_debug_JTAG/src/arch/aarch64/time.rs b/09_hw_debug_JTAG/src/arch/aarch64/time.rs new file mode 100644 index 00000000..ba43474f --- /dev/null +++ b/09_hw_debug_JTAG/src/arch/aarch64/time.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Timer primitives. + +use crate::{interface, warn}; +use core::time::Duration; +use cortex_a::regs::*; + +const NS_PER_S: u64 = 1_000_000_000; + +pub struct Timer; + +impl interface::time::Timer for Timer { + fn resoultion(&self) -> Duration { + Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + } + + fn uptime(&self) -> Duration { + let frq: u64 = CNTFRQ_EL0.get() as u64; + let current_count: u64 = CNTPCT_EL0.get() * NS_PER_S; + + Duration::from_nanos(current_count / frq) + } + + fn spin_for(&self, duration: Duration) { + // Instantly return on zero. + if duration.as_nanos() == 0 { + return; + } + + // Calculate the register compare value. + let frq = CNTFRQ_EL0.get() as u64; + let x = match frq.checked_mul(duration.as_nanos() as u64) { + None => { + warn!("Spin duration too long, skipping"); + return; + } + Some(val) => val, + }; + let tval = x / NS_PER_S; + + // Check if it is within supported bounds. + let warn: Option<&str> = if tval == 0 { + Some("smaller") + } else if tval > u32::max_value().into() { + Some("bigger") + } else { + None + }; + + if let Some(w) = warn { + warn!( + "Spin duration {} than architecturally supported, skipping", + w + ); + return; + } + + // Set the compare value register. + CNTP_TVAL_EL0.set(tval as u32); + + // Kick off the counting. // Disable timer interrupt. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + + loop { + // ISTATUS will be '1' when cval ticks have passed. Busy-check it. + if CNTP_CTL_EL0.is_set(CNTP_CTL_EL0::ISTATUS) { + break; + } + } + + // Disable counting again. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + } +} diff --git a/09_hw_debug_JTAG/src/bsp.rs b/09_hw_debug_JTAG/src/bsp.rs new file mode 100644 index 00000000..3db8e14a --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of Board Support Packages. + +mod driver; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod rpi; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use rpi::*; diff --git a/09_hw_debug_JTAG/src/bsp/driver.rs b/09_hw_debug_JTAG/src/bsp/driver.rs new file mode 100644 index 00000000..c910274e --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/driver.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Drivers. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod bcm; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use bcm::*; diff --git a/09_hw_debug_JTAG/src/bsp/driver/bcm.rs b/09_hw_debug_JTAG/src/bsp/driver/bcm.rs new file mode 100644 index 00000000..15283aea --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/driver/bcm.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! BCM driver top level. + +mod bcm2xxx_gpio; +mod bcm2xxx_pl011_uart; + +pub use bcm2xxx_gpio::GPIO; +pub use bcm2xxx_pl011_uart::PL011Uart; diff --git a/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_gpio.rs b/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_gpio.rs new file mode 100644 index 00000000..a9ceda61 --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_gpio.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! GPIO driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::ops; +use register::{mmio::ReadWrite, register_bitfields}; + +// GPIO registers. +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// GPIO Function Select 1 + GPFSEL1 [ + /// Pin 15 + FSEL15 OFFSET(15) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100 // PL011 UART RX + + ], + + /// Pin 14 + FSEL14 OFFSET(12) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100 // PL011 UART TX + ] + ], + + /// GPIO Pull-up/down Clock Register 0 + GPPUDCLK0 [ + /// Pin 15 + PUDCLK15 OFFSET(15) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 14 + PUDCLK14 OFFSET(14) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + pub GPFSEL0: ReadWrite, // 0x00 + pub GPFSEL1: ReadWrite, // 0x04 + pub GPFSEL2: ReadWrite, // 0x08 + pub GPFSEL3: ReadWrite, // 0x0C + pub GPFSEL4: ReadWrite, // 0x10 + pub GPFSEL5: ReadWrite, // 0x14 + __reserved_0: u32, // 0x18 + GPSET0: ReadWrite, // 0x1C + GPSET1: ReadWrite, // 0x20 + __reserved_1: u32, // + GPCLR0: ReadWrite, // 0x28 + __reserved_2: [u32; 2], // + GPLEV0: ReadWrite, // 0x34 + GPLEV1: ReadWrite, // 0x38 + __reserved_3: u32, // + GPEDS0: ReadWrite, // 0x40 + GPEDS1: ReadWrite, // 0x44 + __reserved_4: [u32; 7], // + GPHEN0: ReadWrite, // 0x64 + GPHEN1: ReadWrite, // 0x68 + __reserved_5: [u32; 10], // + pub GPPUD: ReadWrite, // 0x94 + pub GPPUDCLK0: ReadWrite, // 0x98 + pub GPPUDCLK1: ReadWrite, // 0x9C +} + +/// The driver's private data. +struct GPIOInner { + base_addr: usize, +} + +/// Deref to RegisterBlock. +impl ops::Deref for GPIOInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl GPIOInner { + const fn new(base_addr: usize) -> GPIOInner { + GPIOInner { base_addr } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } +} + +//-------------------------------------------------------------------------------------------------- +// BSP-public +//-------------------------------------------------------------------------------------------------- +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 { + inner: NullLock::new(GPIOInner::new(base_addr)), + } + } + + /// Map PL011 UART as standard output. + /// + /// TX to pin 14 + /// RX to pin 15 + pub fn map_pl011_uart(&self) { + let mut r = &self.inner; + r.lock(|inner| { + // Map to pins. + inner + .GPFSEL1 + .modify(GPFSEL1::FSEL14::AltFunc0 + GPFSEL1::FSEL15::AltFunc0); + + // Enable pins 14 and 15. + inner.GPPUD.set(0); + arch::spin_for_cycles(150); + + inner + .GPPUDCLK0 + .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock); + arch::spin_for_cycles(150); + + inner.GPPUDCLK0.set(0); + }) + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- + +impl interface::driver::DeviceDriver for GPIO { + fn compatible(&self) -> &str { + "GPIO" + } +} diff --git a/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs b/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs new file mode 100644 index 00000000..c75312c9 --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! PL011 UART driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::{fmt, ops}; +use register::{mmio::*, register_bitfields}; + +// PL011 UART registers. +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// Flag Register + FR [ + /// Transmit FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// Line Control Register, UARTLCR_ LCRH. + /// + /// If the FIFO is disabled, this bit is set when the transmit holding register is empty. If + /// the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty. This bit does + /// not indicate if there is data in the transmit shift register. + TXFE OFFSET(7) NUMBITS(1) [], + + /// Transmit FIFO full. The meaning of this bit depends on the state of the FEN bit in the + /// UARTLCR_ LCRH Register. + /// + /// If the FIFO is disabled, this bit is set when the transmit holding register is full. If + /// the FIFO is enabled, the TXFF bit is set when the transmit FIFO is full. + TXFF OFFSET(5) NUMBITS(1) [], + + /// Receive FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// UARTLCR_H Register. + /// + /// If the FIFO is disabled, this bit is set when the receive holding register is empty. If + /// the FIFO is enabled, the RXFE bit is set when the receive FIFO is empty. + RXFE OFFSET(4) NUMBITS(1) [] + ], + + /// Integer Baud rate divisor + IBRD [ + /// Integer Baud rate divisor + IBRD OFFSET(0) NUMBITS(16) [] + ], + + /// Fractional Baud rate divisor + FBRD [ + /// Fractional Baud rate divisor + FBRD OFFSET(0) NUMBITS(6) [] + ], + + /// Line Control register + LCRH [ + /// Word length. These bits indicate the number of data bits transmitted or received in a + /// frame. + WLEN OFFSET(5) NUMBITS(2) [ + FiveBit = 0b00, + SixBit = 0b01, + SevenBit = 0b10, + EightBit = 0b11 + ], + + /// Enable FIFOs: + /// + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding + /// registers + /// + /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). + FEN OFFSET(4) NUMBITS(1) [ + FifosDisabled = 0, + FifosEnabled = 1 + ] + ], + + /// Control Register + CR [ + /// Receive enable. If this bit is set to 1, the receive section of the UART is enabled. + /// Data reception occurs for UART signals. When the UART is disabled in the middle of + /// reception, it completes the current character before stopping. + RXE OFFSET(9) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// Transmit enable. If this bit is set to 1, the transmit section of the UART is enabled. + /// Data transmission occurs for UART signals. When the UART is disabled in the middle of + /// transmission, it completes the current character before stopping. + TXE OFFSET(8) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// UART enable + UARTEN OFFSET(0) NUMBITS(1) [ + /// If the UART is disabled in the middle of transmission or reception, it completes the + /// current character before stopping. + Disabled = 0, + Enabled = 1 + ] + ], + + /// Interupt Clear Register + ICR [ + /// Meta field for all pending interrupts + ALL OFFSET(0) NUMBITS(11) [] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + DR: ReadWrite, // 0x00 + __reserved_0: [u32; 5], // 0x04 + FR: ReadOnly, // 0x18 + __reserved_1: [u32; 2], // 0x1c + IBRD: WriteOnly, // 0x24 + FBRD: WriteOnly, // 0x28 + LCRH: WriteOnly, // 0x2C + CR: WriteOnly, // 0x30 + __reserved_2: [u32; 4], // 0x34 + ICR: WriteOnly, // 0x44 +} + +/// The driver's mutex protected part. +struct PL011UartInner { + base_addr: usize, + chars_written: usize, +} + +/// Deref to RegisterBlock. +/// +/// Allows writing +/// ``` +/// self.DR.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*PL011UartInner::ptr()).DR.read() } +/// ``` +impl ops::Deref for PL011UartInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl PL011UartInner { + const fn new(base_addr: usize) -> PL011UartInner { + PL011UartInner { + base_addr, + chars_written: 0, + } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } + + /// Send a character. + fn write_char(&mut self, c: char) { + // Wait until we can send. + loop { + if !self.FR.is_set(FR::TXFF) { + break; + } + + arch::nop(); + } + + // Write the character to the buffer. + self.DR.set(c as u32); + } +} + +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl fmt::Write for PL011UartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + // Convert newline to carrige return + newline. + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + + self.chars_written += s.len(); + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// 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 { + inner: NullLock::new(PL011UartInner::new(base_addr)), + } + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- +use interface::sync::Mutex; + +impl interface::driver::DeviceDriver for PL011Uart { + fn compatible(&self) -> &str { + "PL011Uart" + } + + /// Set up baud rate and characteristics + /// + /// Results in 8N1 and 115200 baud (if the clk has been previously set to 4 MHz by the + /// firmware). + fn init(&self) -> interface::driver::Result { + let mut r = &self.inner; + r.lock(|inner| { + // Turn it off temporarily. + inner.CR.set(0); + + inner.ICR.write(ICR::ALL::CLEAR); + inner.IBRD.write(IBRD::IBRD.val(26)); // Results in 115200 baud for UART Clk of 48 MHz. + inner.FBRD.write(FBRD::FBRD.val(3)); + inner + .LCRH + .write(LCRH::WLEN::EightBit + LCRH::FEN::FifosEnabled); // 8N1 + Fifo on + inner + .CR + .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); + }); + + Ok(()) + } +} + +impl interface::console::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) { + let mut r = &self.inner; + r.lock(|inner| inner.write_char(c)); + } + + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + // Fully qualified syntax for the call to `core::fmt::Write::write:fmt()` to increase + // readability. + let mut r = &self.inner; + r.lock(|inner| fmt::Write::write_fmt(inner, args)) + } + + fn flush(&self) { + let mut r = &self.inner; + // Spin until the TX FIFO empty flag is set. + r.lock(|inner| loop { + if inner.FR.is_set(FR::TXFE) { + break; + } + + arch::nop(); + }); + } +} + +impl interface::console::Read for PL011Uart { + fn read_char(&self) -> char { + let mut r = &self.inner; + r.lock(|inner| { + // Wait until buffer is filled. + loop { + if !inner.FR.is_set(FR::RXFE) { + break; + } + + arch::nop(); + } + + // Read one character. + let mut ret = inner.DR.get() as u8 as char; + + // Convert carrige return to newline. + if ret == '\r' { + ret = '\n' + } + + ret + }) + } + + fn clear(&self) { + let mut r = &self.inner; + r.lock(|inner| loop { + // Read from the RX FIFO until the empty bit is '1'. + if !inner.FR.is_set(FR::RXFE) { + inner.DR.get(); + } else { + break; + } + }) + } +} + +impl interface::console::Statistics for PL011Uart { + fn chars_written(&self) -> usize { + let mut r = &self.inner; + r.lock(|inner| inner.chars_written) + } +} diff --git a/09_hw_debug_JTAG/src/bsp/rpi.rs b/09_hw_debug_JTAG/src/bsp/rpi.rs new file mode 100644 index 00000000..c22c47bb --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/rpi.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board Support Package for the Raspberry Pi. + +mod memory_map; + +use super::driver; +use crate::interface; + +pub const BOOT_CORE_ID: u64 = 0; +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 +} + +/// 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(); +} diff --git a/09_hw_debug_JTAG/src/bsp/rpi/link.ld b/09_hw_debug_JTAG/src/bsp/rpi/link.ld new file mode 100644 index 00000000..53b65640 --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/rpi/link.ld @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (c) 2018-2019 Andre Richter + */ + +SECTIONS +{ + /* Set current address to the value from which the RPi starts execution */ + . = 0x80000; + + .text : + { + *(.text._start) *(.text*) + } + + .rodata : + { + *(.rodata*) + } + + .data : + { + *(.data*) + } + + /* Align to 8 byte boundary */ + .bss ALIGN(8): + { + __bss_start = .; + *(.bss*); + __bss_end = .; + } + + /DISCARD/ : { *(.comment*) } +} diff --git a/09_hw_debug_JTAG/src/bsp/rpi/memory_map.rs b/09_hw_debug_JTAG/src/bsp/rpi/memory_map.rs new file mode 100644 index 00000000..ed617f5e --- /dev/null +++ b/09_hw_debug_JTAG/src/bsp/rpi/memory_map.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! The board's memory map. + +/// 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; +} diff --git a/09_hw_debug_JTAG/src/interface.rs b/09_hw_debug_JTAG/src/interface.rs new file mode 100644 index 00000000..15fa3c15 --- /dev/null +++ b/09_hw_debug_JTAG/src/interface.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Trait definitions for coupling `kernel` and `BSP` code. +//! +//! ``` +//! +-------------------+ +//! | Interface (Trait) | +//! | | +//! +--+-------------+--+ +//! ^ ^ +//! | | +//! | | +//! +----------+--+ +--+----------+ +//! | Kernel code | | BSP Code | +//! | | | | +//! +-------------+ +-------------+ +//! ``` + +/// System console operations. +pub mod console { + use core::fmt; + + /// Console write functions. + pub trait Write { + fn write_char(&self, c: char); + 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 { + 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 resoultion(&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/09_hw_debug_JTAG/src/main.rs b/09_hw_debug_JTAG/src/main.rs new file mode 100644 index 00000000..6730048a --- /dev/null +++ b/09_hw_debug_JTAG/src/main.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +// Rust embedded logo for `make doc`. +#![doc(html_logo_url = "https://git.io/JeGIp")] + +//! The `kernel` +//! +//! The `kernel` is composed by glueing together 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)] +#![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::init()`, which on completion, jumps to `kernel_init()`. +mod runtime_init; + +// Conditionally includes the selected `BSP` code. +mod bsp; + +mod interface; +mod panic_wait; +mod print; + +/// Early init code. +/// +/// Concerned with with initializing `BSP` and `arch` parts. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +/// - The init calls in this function must appear in the correct order. +unsafe fn kernel_init() -> ! { + for i in bsp::device_drivers().iter() { + if let Err(()) = i.init() { + // This message will only be readable if, at the time of failure, the return value of + // `bsp::console()` is already in functioning state. + panic!("Error loading driver: {}", i.compatible()) + } + } + + bsp::post_driver_init(); + + // Transition from unsafe to safe. + kernel_main() +} + +/// The main function running after the early init. +fn kernel_main() -> ! { + use core::time::Duration; + use interface::time::Timer; + + println!("Booting on: {}", bsp::board_name()); + println!( + "Architectural timer resolution: {} ns", + arch::timer().resoultion().as_nanos() + ); + + println!("Drivers loaded:"); + for (i, driver) in bsp::device_drivers().iter().enumerate() { + println!(" {}. {}", i + 1, driver.compatible()); + } + + // Test a failing timer case. + arch::timer().spin_for(Duration::from_nanos(1)); + + loop { + println!("Spinning for 1 second"); + arch::timer().spin_for(Duration::from_secs(1)); + } +} diff --git a/09_hw_debug_JTAG/src/panic_wait.rs b/09_hw_debug_JTAG/src/panic_wait.rs new file mode 100644 index 00000000..5e6d3fa5 --- /dev/null +++ b/09_hw_debug_JTAG/src/panic_wait.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! A panic handler that infinitely waits. + +use crate::{arch, println}; +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + if let Some(args) = info.message() { + println!("Kernel panic: {}", args); + } else { + println!("Kernel panic!"); + } + + arch::wait_forever() +} diff --git a/09_hw_debug_JTAG/src/print.rs b/09_hw_debug_JTAG/src/print.rs new file mode 100644 index 00000000..86ab59b3 --- /dev/null +++ b/09_hw_debug_JTAG/src/print.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Printing facilities. + +use crate::{bsp, interface}; +use core::fmt; + +/// Prints without a newline. +/// +/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// Prints with a newline. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +/// Prints a warning, with newline. +#[macro_export] +macro_rules! warn { + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +pub fn _print(args: fmt::Arguments) { + use interface::console::Write; + + bsp::console().write_fmt(args).unwrap(); +} diff --git a/09_hw_debug_JTAG/src/runtime_init.rs b/09_hw_debug_JTAG/src/runtime_init.rs new file mode 100644 index 00000000..4cd0415a --- /dev/null +++ b/09_hw_debug_JTAG/src/runtime_init.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Rust runtime initialization code. + +/// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel +/// init code. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +pub unsafe fn init() -> ! { + extern "C" { + // Boundaries of the .bss section, provided by the linker script. + static mut __bss_start: u64; + static mut __bss_end: u64; + } + + // Zero out the .bss section. + r0::zero_bss(&mut __bss_start, &mut __bss_end); + + crate::kernel_init() +} diff --git a/X1_JTAG_boot/.vscode/settings.json b/X1_JTAG_boot/.vscode/settings.json new file mode 100644 index 00000000..f2fa6961 --- /dev/null +++ b/X1_JTAG_boot/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.formatOnSave": true, + "rust.features": [ + "bsp_rpi3" + ], + "rust.all_targets": false, + "editor.rulers": [ + 100 + ], +} \ No newline at end of file diff --git a/X1_JTAG_boot/Cargo.lock b/X1_JTAG_boot/Cargo.lock new file mode 100644 index 00000000..9a17339b --- /dev/null +++ b/X1_JTAG_boot/Cargo.lock @@ -0,0 +1,42 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "cortex-a" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "r0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "register" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tock-registers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum cortex-a 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbb16c411ab74044f174746a6cbae67bcdebea126e376b5441e5986e6a6aa950" +"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f" +"checksum register 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "469bb5ddde81d67fb8bba4e14d77689b8166cfd077abe7530591cefe29d05823" +"checksum tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c758f5195a2e0df9d9fecf6f506506b2766ff74cf64db1e995c87e2761a5c3e2" diff --git a/X1_JTAG_boot/Cargo.toml b/X1_JTAG_boot/Cargo.toml new file mode 100644 index 00000000..cf0f0636 --- /dev/null +++ b/X1_JTAG_boot/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "kernel" +version = "0.1.0" +authors = ["Andre Richter "] +edition = "2018" + +[package.metadata.cargo-xbuild] +sysroot_path = "../xbuild_sysroot" + +# The features section is used to select the target board. +[features] +default = [] +bsp_rpi3 = ["cortex-a", "register"] +bsp_rpi4 = ["cortex-a", "register"] + +[dependencies] +r0 = "0.2.*" + +# Optional dependencies +cortex-a = { version = "2.*", optional = true } +register = { version = "0.3.*", optional = true } diff --git a/X1_JTAG_boot/Makefile b/X1_JTAG_boot/Makefile new file mode 100644 index 00000000..849fac9f --- /dev/null +++ b/X1_JTAG_boot/Makefile @@ -0,0 +1,100 @@ +## SPDX-License-Identifier: MIT +## +## Copyright (c) 2018-2019 Andre Richter + +# Default to the RPi3 +ifndef BSP + BSP = rpi3 +endif + +# BSP-specific arguments +ifeq ($(BSP),rpi3) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = raspi3 + QEMU_MISC_ARGS = -serial stdio + LINKER_FILE = src/bsp/rpi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 +else ifeq ($(BSP),rpi4) + TARGET = aarch64-unknown-none-softfloat + OUTPUT = kernel8.img +# QEMU_BINARY = qemu-system-aarch64 +# QEMU_MACHINE_TYPE = +# QEMU_MISC_ARGS = -serial stdio + LINKER_FILE = src/bsp/rpi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +endif + +SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) $(wildcard **/*.ld) + +XRUSTC_CMD = cargo xrustc \ + --target=$(TARGET) \ + --features bsp_$(BSP) \ + --release \ + -- \ + -C link-arg=-T$(LINKER_FILE) \ + $(RUSTC_MISC_ARGS) + +CARGO_OUTPUT = target/$(TARGET)/release/kernel + +OBJCOPY_CMD = cargo objcopy \ + -- \ + --strip-all \ + -O binary + +CONTAINER_UTILS = rustembedded/osdev-utils + +DOCKER_CMD = docker run -it --rm +DOCKER_ARG_CURDIR = -v $(shell pwd):/work -w /work +DOCKER_ARG_TTY = --privileged -v /dev:/dev + +DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -kernel $(OUTPUT) +DOCKER_EXEC_RASPBOOT = raspbootcom +DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyUSB0 +# DOCKER_EXEC_RASPBOOT_DEV = /dev/ttyACM0 + +.PHONY: all doc qemu chainboot clippy clean readelf objdump nm + +all: clean $(OUTPUT) + +$(CARGO_OUTPUT): $(SOURCES) + # RUSTFLAGS="-D warnings -D missing_docs" + $(XRUSTC_CMD) + +$(OUTPUT): $(CARGO_OUTPUT) + cp $< . + $(OBJCOPY_CMD) $< $(OUTPUT) + +doc: + cargo xdoc --target=$(TARGET) --features bsp_$(BSP) --document-private-items + xdg-open target/$(TARGET)/doc/kernel/index.html + +ifeq ($(QEMU_MACHINE_TYPE),) +qemu: + @echo "This board is not yet supported for QEMU." +else +qemu: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(CONTAINER_UTILS) \ + $(DOCKER_EXEC_QEMU) $(QEMU_MISC_ARGS) +endif + +chainboot: all + $(DOCKER_CMD) $(DOCKER_ARG_CURDIR) $(DOCKER_ARG_TTY) \ + $(CONTAINER_UTILS) $(DOCKER_EXEC_RASPBOOT) $(DOCKER_EXEC_RASPBOOT_DEV) \ + $(OUTPUT) + +clippy: + cargo xclippy --target=$(TARGET) --features bsp_$(BSP) + +clean: + cargo clean + +readelf: + readelf -a kernel + +objdump: + cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel + +nm: + cargo nm --target $(TARGET) -- kernel | sort diff --git a/X1_JTAG_boot/README.md b/X1_JTAG_boot/README.md new file mode 100644 index 00000000..98efb8f4 --- /dev/null +++ b/X1_JTAG_boot/README.md @@ -0,0 +1,3 @@ +# Xtra 1 - JTAG boot + +Not much is happening here. The binary just patiently waits for a `JTAG` debugger to connect. diff --git a/X1_JTAG_boot/jtag_boot_rpi3.img b/X1_JTAG_boot/jtag_boot_rpi3.img new file mode 100755 index 00000000..ec8198e9 Binary files /dev/null and b/X1_JTAG_boot/jtag_boot_rpi3.img differ diff --git a/X1_JTAG_boot/jtag_boot_rpi4.img b/X1_JTAG_boot/jtag_boot_rpi4.img new file mode 100755 index 00000000..468ed917 Binary files /dev/null and b/X1_JTAG_boot/jtag_boot_rpi4.img differ diff --git a/X1_JTAG_boot/src/arch.rs b/X1_JTAG_boot/src/arch.rs new file mode 100644 index 00000000..b1f035c5 --- /dev/null +++ b/X1_JTAG_boot/src/arch.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of processor architecture code. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod aarch64; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use aarch64::*; diff --git a/X1_JTAG_boot/src/arch/aarch64.rs b/X1_JTAG_boot/src/arch/aarch64.rs new file mode 100644 index 00000000..a44ece9b --- /dev/null +++ b/X1_JTAG_boot/src/arch/aarch64.rs @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! AArch64. + +pub mod sync; +mod time; + +use crate::{bsp, interface}; +use cortex_a::{asm, regs::*}; + +/// The entry of the `kernel` binary. +/// +/// The function must be named `_start`, because the linker is looking for this exact name. +/// +/// # Safety +/// +/// - Linker script must ensure to place this function at `0x80_000`. +#[no_mangle] +pub unsafe extern "C" fn _start() -> ! { + const CORE_MASK: u64 = 0x3; + + if bsp::BOOT_CORE_ID == MPIDR_EL1.get() & CORE_MASK { + SP.set(bsp::BOOT_CORE_STACK_START); + crate::runtime_init::init() + } else { + // if not core0, infinitely wait for events + wait_forever() + } +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static TIMER: time::Timer = time::Timer; + +//-------------------------------------------------------------------------------------------------- +// Implementation of the kernel's architecture abstraction code +//-------------------------------------------------------------------------------------------------- + +pub use asm::nop; + +/// Spin for `n` cycles. +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. +#[inline(always)] +pub fn wait_forever() -> ! { + loop { + asm::wfe() + } +} diff --git a/X1_JTAG_boot/src/arch/aarch64/sync.rs b/X1_JTAG_boot/src/arch/aarch64/sync.rs new file mode 100644 index 00000000..dfebc0e1 --- /dev/null +++ b/X1_JTAG_boot/src/arch/aarch64/sync.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Synchronization primitives. + +use crate::interface; +use core::cell::UnsafeCell; + +/// A pseudo-lock for teaching purposes. +/// +/// Used to introduce [interior mutability]. +/// +/// In contrast to a real Mutex implementation, does not protect against concurrent access to the +/// contained data. This part is preserved for later lessons. +/// +/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is +/// executing single-threaded, aka only running on a single core with interrupts disabled. +/// +/// [interior mutability]: https://doc.rust-lang.org/std/cell/index.html +pub struct NullLock { + data: UnsafeCell, +} + +unsafe impl Send for NullLock {} +unsafe impl Sync for NullLock {} + +impl NullLock { + pub const fn new(data: T) -> NullLock { + NullLock { + data: UnsafeCell::new(data), + } + } +} + +impl interface::sync::Mutex for &NullLock { + type Data = T; + + fn lock(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + f(unsafe { &mut *self.data.get() }) + } +} diff --git a/X1_JTAG_boot/src/arch/aarch64/time.rs b/X1_JTAG_boot/src/arch/aarch64/time.rs new file mode 100644 index 00000000..ba43474f --- /dev/null +++ b/X1_JTAG_boot/src/arch/aarch64/time.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Timer primitives. + +use crate::{interface, warn}; +use core::time::Duration; +use cortex_a::regs::*; + +const NS_PER_S: u64 = 1_000_000_000; + +pub struct Timer; + +impl interface::time::Timer for Timer { + fn resoultion(&self) -> Duration { + Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64)) + } + + fn uptime(&self) -> Duration { + let frq: u64 = CNTFRQ_EL0.get() as u64; + let current_count: u64 = CNTPCT_EL0.get() * NS_PER_S; + + Duration::from_nanos(current_count / frq) + } + + fn spin_for(&self, duration: Duration) { + // Instantly return on zero. + if duration.as_nanos() == 0 { + return; + } + + // Calculate the register compare value. + let frq = CNTFRQ_EL0.get() as u64; + let x = match frq.checked_mul(duration.as_nanos() as u64) { + None => { + warn!("Spin duration too long, skipping"); + return; + } + Some(val) => val, + }; + let tval = x / NS_PER_S; + + // Check if it is within supported bounds. + let warn: Option<&str> = if tval == 0 { + Some("smaller") + } else if tval > u32::max_value().into() { + Some("bigger") + } else { + None + }; + + if let Some(w) = warn { + warn!( + "Spin duration {} than architecturally supported, skipping", + w + ); + return; + } + + // Set the compare value register. + CNTP_TVAL_EL0.set(tval as u32); + + // Kick off the counting. // Disable timer interrupt. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET); + + loop { + // ISTATUS will be '1' when cval ticks have passed. Busy-check it. + if CNTP_CTL_EL0.is_set(CNTP_CTL_EL0::ISTATUS) { + break; + } + } + + // Disable counting again. + CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR); + } +} diff --git a/X1_JTAG_boot/src/bsp.rs b/X1_JTAG_boot/src/bsp.rs new file mode 100644 index 00000000..3db8e14a --- /dev/null +++ b/X1_JTAG_boot/src/bsp.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Conditional exporting of Board Support Packages. + +mod driver; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod rpi; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use rpi::*; diff --git a/X1_JTAG_boot/src/bsp/driver.rs b/X1_JTAG_boot/src/bsp/driver.rs new file mode 100644 index 00000000..c910274e --- /dev/null +++ b/X1_JTAG_boot/src/bsp/driver.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Drivers. + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +mod bcm; + +#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] +pub use bcm::*; diff --git a/X1_JTAG_boot/src/bsp/driver/bcm.rs b/X1_JTAG_boot/src/bsp/driver/bcm.rs new file mode 100644 index 00000000..15283aea --- /dev/null +++ b/X1_JTAG_boot/src/bsp/driver/bcm.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! BCM driver top level. + +mod bcm2xxx_gpio; +mod bcm2xxx_pl011_uart; + +pub use bcm2xxx_gpio::GPIO; +pub use bcm2xxx_pl011_uart::PL011Uart; diff --git a/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_gpio.rs b/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_gpio.rs new file mode 100644 index 00000000..f6ef27b8 --- /dev/null +++ b/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_gpio.rs @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! GPIO driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::ops; +use register::{mmio::ReadWrite, register_bitfields}; + +// GPIO registers. +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// GPIO Function Select 1 + GPFSEL1 [ + /// Pin 15 + FSEL15 OFFSET(15) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100 // PL011 UART RX + + ], + + /// Pin 14 + FSEL14 OFFSET(12) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100 // PL011 UART TX + ] + ], + + /// GPIO Function Select 2 + GPFSEL2 [ + /// Pin 27 + FSEL27 OFFSET(21) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG TMS + ], + + /// Pin 26 + FSEL26 OFFSET(18) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG TDI + ], + + /// Pin 25 + FSEL25 OFFSET(15) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG TCK + ], + + /// Pin 24 + FSEL24 OFFSET(12) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG TDO + ], + + /// Pin 23 + FSEL23 OFFSET(9) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG RTCK + ], + + /// Pin 22 + FSEL22 OFFSET(6) NUMBITS(3)[ + Input = 0b000, + Output = 0b001, + AltFunc4 = 0b011 // JTAG TRST + ] + ], + + /// GPIO Pull-up/down Clock Register 0 + GPPUDCLK0 [ + /// Pin 27 + PUDCLK27 OFFSET(27) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + /// Pin 26 + PUDCLK26 OFFSET(26) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 25 + PUDCLK25 OFFSET(25) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 24 + PUDCLK24 OFFSET(24) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 23 + PUDCLK23 OFFSET(23) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 22 + PUDCLK22 OFFSET(22) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 15 + PUDCLK15 OFFSET(15) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 14 + PUDCLK14 OFFSET(14) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + pub GPFSEL0: ReadWrite, // 0x00 + pub GPFSEL1: ReadWrite, // 0x04 + pub GPFSEL2: ReadWrite, // 0x08 + pub GPFSEL3: ReadWrite, // 0x0C + pub GPFSEL4: ReadWrite, // 0x10 + pub GPFSEL5: ReadWrite, // 0x14 + __reserved_0: u32, // 0x18 + GPSET0: ReadWrite, // 0x1C + GPSET1: ReadWrite, // 0x20 + __reserved_1: u32, // + GPCLR0: ReadWrite, // 0x28 + __reserved_2: [u32; 2], // + GPLEV0: ReadWrite, // 0x34 + GPLEV1: ReadWrite, // 0x38 + __reserved_3: u32, // + GPEDS0: ReadWrite, // 0x40 + GPEDS1: ReadWrite, // 0x44 + __reserved_4: [u32; 7], // + GPHEN0: ReadWrite, // 0x64 + GPHEN1: ReadWrite, // 0x68 + __reserved_5: [u32; 10], // + pub GPPUD: ReadWrite, // 0x94 + pub GPPUDCLK0: ReadWrite, // 0x98 + pub GPPUDCLK1: ReadWrite, // 0x9C +} + +/// The driver's private data. +struct GPIOInner { + base_addr: usize, +} + +/// Deref to RegisterBlock. +impl ops::Deref for GPIOInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl GPIOInner { + const fn new(base_addr: usize) -> GPIOInner { + GPIOInner { base_addr } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } +} + +//-------------------------------------------------------------------------------------------------- +// BSP-public +//-------------------------------------------------------------------------------------------------- +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 { + inner: NullLock::new(GPIOInner::new(base_addr)), + } + } + + /// Map PL011 UART as standard output. + /// + /// TX to pin 14 + /// RX to pin 15 + pub fn map_pl011_uart(&self) { + let mut r = &self.inner; + r.lock(|inner| { + // Map to pins. + inner + .GPFSEL1 + .modify(GPFSEL1::FSEL14::AltFunc0 + GPFSEL1::FSEL15::AltFunc0); + + // Enable pins 14 and 15. + inner.GPPUD.set(0); + arch::spin_for_cycles(150); + + inner + .GPPUDCLK0 + .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock); + arch::spin_for_cycles(150); + + inner.GPPUDCLK0.set(0); + }) + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- + +impl interface::driver::DeviceDriver for GPIO { + fn compatible(&self) -> &str { + "GPIO" + } +} + +impl interface::driver::JTAGOps for GPIO { + fn enable(&self) -> interface::driver::Result { + let mut r = &self.inner; + r.lock(|inner| { + inner.GPFSEL2.modify( + GPFSEL2::FSEL22::AltFunc4 + + GPFSEL2::FSEL23::AltFunc4 + + GPFSEL2::FSEL24::AltFunc4 + + GPFSEL2::FSEL25::AltFunc4 + + GPFSEL2::FSEL26::AltFunc4 + + GPFSEL2::FSEL27::AltFunc4, + ); + + inner.GPPUD.set(0); + arch::spin_for_cycles(150); + + inner.GPPUDCLK0.write( + GPPUDCLK0::PUDCLK22::AssertClock + + GPPUDCLK0::PUDCLK23::AssertClock + + GPPUDCLK0::PUDCLK24::AssertClock + + GPPUDCLK0::PUDCLK25::AssertClock + + GPPUDCLK0::PUDCLK26::AssertClock + + GPPUDCLK0::PUDCLK27::AssertClock, + ); + arch::spin_for_cycles(150); + + inner.GPPUDCLK0.set(0); + + Ok(()) + }) + } +} diff --git a/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs b/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs new file mode 100644 index 00000000..c75312c9 --- /dev/null +++ b/X1_JTAG_boot/src/bsp/driver/bcm/bcm2xxx_pl011_uart.rs @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! PL011 UART driver. + +use crate::{arch, arch::sync::NullLock, interface}; +use core::{fmt, ops}; +use register::{mmio::*, register_bitfields}; + +// PL011 UART registers. +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// Flag Register + FR [ + /// Transmit FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// Line Control Register, UARTLCR_ LCRH. + /// + /// If the FIFO is disabled, this bit is set when the transmit holding register is empty. If + /// the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty. This bit does + /// not indicate if there is data in the transmit shift register. + TXFE OFFSET(7) NUMBITS(1) [], + + /// Transmit FIFO full. The meaning of this bit depends on the state of the FEN bit in the + /// UARTLCR_ LCRH Register. + /// + /// If the FIFO is disabled, this bit is set when the transmit holding register is full. If + /// the FIFO is enabled, the TXFF bit is set when the transmit FIFO is full. + TXFF OFFSET(5) NUMBITS(1) [], + + /// Receive FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// UARTLCR_H Register. + /// + /// If the FIFO is disabled, this bit is set when the receive holding register is empty. If + /// the FIFO is enabled, the RXFE bit is set when the receive FIFO is empty. + RXFE OFFSET(4) NUMBITS(1) [] + ], + + /// Integer Baud rate divisor + IBRD [ + /// Integer Baud rate divisor + IBRD OFFSET(0) NUMBITS(16) [] + ], + + /// Fractional Baud rate divisor + FBRD [ + /// Fractional Baud rate divisor + FBRD OFFSET(0) NUMBITS(6) [] + ], + + /// Line Control register + LCRH [ + /// Word length. These bits indicate the number of data bits transmitted or received in a + /// frame. + WLEN OFFSET(5) NUMBITS(2) [ + FiveBit = 0b00, + SixBit = 0b01, + SevenBit = 0b10, + EightBit = 0b11 + ], + + /// Enable FIFOs: + /// + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding + /// registers + /// + /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). + FEN OFFSET(4) NUMBITS(1) [ + FifosDisabled = 0, + FifosEnabled = 1 + ] + ], + + /// Control Register + CR [ + /// Receive enable. If this bit is set to 1, the receive section of the UART is enabled. + /// Data reception occurs for UART signals. When the UART is disabled in the middle of + /// reception, it completes the current character before stopping. + RXE OFFSET(9) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// Transmit enable. If this bit is set to 1, the transmit section of the UART is enabled. + /// Data transmission occurs for UART signals. When the UART is disabled in the middle of + /// transmission, it completes the current character before stopping. + TXE OFFSET(8) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// UART enable + UARTEN OFFSET(0) NUMBITS(1) [ + /// If the UART is disabled in the middle of transmission or reception, it completes the + /// current character before stopping. + Disabled = 0, + Enabled = 1 + ] + ], + + /// Interupt Clear Register + ICR [ + /// Meta field for all pending interrupts + ALL OFFSET(0) NUMBITS(11) [] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + DR: ReadWrite, // 0x00 + __reserved_0: [u32; 5], // 0x04 + FR: ReadOnly, // 0x18 + __reserved_1: [u32; 2], // 0x1c + IBRD: WriteOnly, // 0x24 + FBRD: WriteOnly, // 0x28 + LCRH: WriteOnly, // 0x2C + CR: WriteOnly, // 0x30 + __reserved_2: [u32; 4], // 0x34 + ICR: WriteOnly, // 0x44 +} + +/// The driver's mutex protected part. +struct PL011UartInner { + base_addr: usize, + chars_written: usize, +} + +/// Deref to RegisterBlock. +/// +/// Allows writing +/// ``` +/// self.DR.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*PL011UartInner::ptr()).DR.read() } +/// ``` +impl ops::Deref for PL011UartInner { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl PL011UartInner { + const fn new(base_addr: usize) -> PL011UartInner { + PL011UartInner { + base_addr, + chars_written: 0, + } + } + + /// Return a pointer to the register block. + fn ptr(&self) -> *const RegisterBlock { + self.base_addr as *const _ + } + + /// Send a character. + fn write_char(&mut self, c: char) { + // Wait until we can send. + loop { + if !self.FR.is_set(FR::TXFF) { + break; + } + + arch::nop(); + } + + // Write the character to the buffer. + self.DR.set(c as u32); + } +} + +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl fmt::Write for PL011UartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + // Convert newline to carrige return + newline. + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + + self.chars_written += s.len(); + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// 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 { + inner: NullLock::new(PL011UartInner::new(base_addr)), + } + } +} + +//-------------------------------------------------------------------------------------------------- +// OS interface implementations +//-------------------------------------------------------------------------------------------------- +use interface::sync::Mutex; + +impl interface::driver::DeviceDriver for PL011Uart { + fn compatible(&self) -> &str { + "PL011Uart" + } + + /// Set up baud rate and characteristics + /// + /// Results in 8N1 and 115200 baud (if the clk has been previously set to 4 MHz by the + /// firmware). + fn init(&self) -> interface::driver::Result { + let mut r = &self.inner; + r.lock(|inner| { + // Turn it off temporarily. + inner.CR.set(0); + + inner.ICR.write(ICR::ALL::CLEAR); + inner.IBRD.write(IBRD::IBRD.val(26)); // Results in 115200 baud for UART Clk of 48 MHz. + inner.FBRD.write(FBRD::FBRD.val(3)); + inner + .LCRH + .write(LCRH::WLEN::EightBit + LCRH::FEN::FifosEnabled); // 8N1 + Fifo on + inner + .CR + .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); + }); + + Ok(()) + } +} + +impl interface::console::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) { + let mut r = &self.inner; + r.lock(|inner| inner.write_char(c)); + } + + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + // Fully qualified syntax for the call to `core::fmt::Write::write:fmt()` to increase + // readability. + let mut r = &self.inner; + r.lock(|inner| fmt::Write::write_fmt(inner, args)) + } + + fn flush(&self) { + let mut r = &self.inner; + // Spin until the TX FIFO empty flag is set. + r.lock(|inner| loop { + if inner.FR.is_set(FR::TXFE) { + break; + } + + arch::nop(); + }); + } +} + +impl interface::console::Read for PL011Uart { + fn read_char(&self) -> char { + let mut r = &self.inner; + r.lock(|inner| { + // Wait until buffer is filled. + loop { + if !inner.FR.is_set(FR::RXFE) { + break; + } + + arch::nop(); + } + + // Read one character. + let mut ret = inner.DR.get() as u8 as char; + + // Convert carrige return to newline. + if ret == '\r' { + ret = '\n' + } + + ret + }) + } + + fn clear(&self) { + let mut r = &self.inner; + r.lock(|inner| loop { + // Read from the RX FIFO until the empty bit is '1'. + if !inner.FR.is_set(FR::RXFE) { + inner.DR.get(); + } else { + break; + } + }) + } +} + +impl interface::console::Statistics for PL011Uart { + fn chars_written(&self) -> usize { + let mut r = &self.inner; + r.lock(|inner| inner.chars_written) + } +} diff --git a/X1_JTAG_boot/src/bsp/rpi.rs b/X1_JTAG_boot/src/bsp/rpi.rs new file mode 100644 index 00000000..d96cf21a --- /dev/null +++ b/X1_JTAG_boot/src/bsp/rpi.rs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Board Support Package for the Raspberry Pi. + +mod memory_map; + +use super::driver; +use crate::interface; + +pub const BOOT_CORE_ID: u64 = 0; +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 +} + +/// 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(); +} + +pub fn jtag() -> &'static impl interface::driver::JTAGOps { + &GPIO +} diff --git a/X1_JTAG_boot/src/bsp/rpi/link.ld b/X1_JTAG_boot/src/bsp/rpi/link.ld new file mode 100644 index 00000000..53b65640 --- /dev/null +++ b/X1_JTAG_boot/src/bsp/rpi/link.ld @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (c) 2018-2019 Andre Richter + */ + +SECTIONS +{ + /* Set current address to the value from which the RPi starts execution */ + . = 0x80000; + + .text : + { + *(.text._start) *(.text*) + } + + .rodata : + { + *(.rodata*) + } + + .data : + { + *(.data*) + } + + /* Align to 8 byte boundary */ + .bss ALIGN(8): + { + __bss_start = .; + *(.bss*); + __bss_end = .; + } + + /DISCARD/ : { *(.comment*) } +} diff --git a/X1_JTAG_boot/src/bsp/rpi/memory_map.rs b/X1_JTAG_boot/src/bsp/rpi/memory_map.rs new file mode 100644 index 00000000..ed617f5e --- /dev/null +++ b/X1_JTAG_boot/src/bsp/rpi/memory_map.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! The board's memory map. + +/// 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; +} diff --git a/X1_JTAG_boot/src/interface.rs b/X1_JTAG_boot/src/interface.rs new file mode 100644 index 00000000..4a660c76 --- /dev/null +++ b/X1_JTAG_boot/src/interface.rs @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Trait definitions for coupling `kernel` and `BSP` code. +//! +//! ``` +//! +-------------------+ +//! | Interface (Trait) | +//! | | +//! +--+-------------+--+ +//! ^ ^ +//! | | +//! | | +//! +----------+--+ +--+----------+ +//! | Kernel code | | BSP Code | +//! | | | | +//! +-------------+ +-------------+ +//! ``` + +/// System console operations. +pub mod console { + use core::fmt; + + /// Console write functions. + pub trait Write { + fn write_char(&self, c: char); + 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 { + 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(()) + } + } + + /// JTAG driver operations + pub trait JTAGOps { + /// Enable JTAG. + fn enable(&self) -> Result { + Err(()) + } + } +} + +/// Timekeeping interfaces. +pub mod time { + use core::time::Duration; + + /// Timer functions. + pub trait Timer { + /// The timer's resolution. + fn resoultion(&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/X1_JTAG_boot/src/main.rs b/X1_JTAG_boot/src/main.rs new file mode 100644 index 00000000..b26ef330 --- /dev/null +++ b/X1_JTAG_boot/src/main.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +// Rust embedded logo for `make doc`. +#![doc(html_logo_url = "https://git.io/JeGIp")] + +//! The `kernel` +//! +//! The `kernel` is composed by glueing together 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)] +#![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::init()`, which on completion, jumps to `kernel_init()`. +mod runtime_init; + +// Conditionally includes the selected `BSP` code. +mod bsp; + +mod interface; +mod panic_wait; +mod print; + +/// Early init code. +/// +/// Concerned with with initializing `BSP` and `arch` parts. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +/// - The init calls in this function must appear in the correct order. +unsafe fn kernel_init() -> ! { + for i in bsp::device_drivers().iter() { + if let Err(()) = i.init() { + // This message will only be readable if, at the time of failure, the return value of + // `bsp::console()` is already in functioning state. + panic!("Error loading driver: {}", i.compatible()) + } + } + + bsp::post_driver_init(); + + // Transition from unsafe to safe. + kernel_main() +} + +/// The main function running after the early init. +fn kernel_main() -> ! { + println!("Parking CPU core. Please connect over JTAG now."); + + arch::wait_forever() +} diff --git a/X1_JTAG_boot/src/panic_wait.rs b/X1_JTAG_boot/src/panic_wait.rs new file mode 100644 index 00000000..5e6d3fa5 --- /dev/null +++ b/X1_JTAG_boot/src/panic_wait.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! A panic handler that infinitely waits. + +use crate::{arch, println}; +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + if let Some(args) = info.message() { + println!("Kernel panic: {}", args); + } else { + println!("Kernel panic!"); + } + + arch::wait_forever() +} diff --git a/X1_JTAG_boot/src/print.rs b/X1_JTAG_boot/src/print.rs new file mode 100644 index 00000000..86ab59b3 --- /dev/null +++ b/X1_JTAG_boot/src/print.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Printing facilities. + +use crate::{bsp, interface}; +use core::fmt; + +/// Prints without a newline. +/// +/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// Prints with a newline. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[ {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +/// Prints a warning, with newline. +#[macro_export] +macro_rules! warn { + ($string:expr) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000 + )); + }); + ($format_string:expr, $($arg:tt)*) => ({ + #[allow(unused_imports)] + use crate::interface::time::Timer; + + let timestamp = $crate::arch::timer().uptime(); + let timestamp_subsec_us = timestamp.subsec_micros(); + + $crate::print::_print(format_args_nl!( + concat!("[W {:>3}.{:03}{:03}] ", $format_string), + timestamp.as_secs(), + timestamp_subsec_us / 1_000, + timestamp_subsec_us % 1_000, + $($arg)* + )); + }) +} + +pub fn _print(args: fmt::Arguments) { + use interface::console::Write; + + bsp::console().write_fmt(args).unwrap(); +} diff --git a/X1_JTAG_boot/src/runtime_init.rs b/X1_JTAG_boot/src/runtime_init.rs new file mode 100644 index 00000000..4cd0415a --- /dev/null +++ b/X1_JTAG_boot/src/runtime_init.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2018-2019 Andre Richter + +//! Rust runtime initialization code. + +/// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel +/// init code. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +pub unsafe fn init() -> ! { + extern "C" { + // Boundaries of the .bss section, provided by the linker script. + static mut __bss_start: u64; + static mut __bss_end: u64; + } + + // Zero out the .bss section. + r0::zero_bss(&mut __bss_start, &mut __bss_end); + + crate::kernel_init() +} diff --git a/doc/image_jtag_connected.jpg b/doc/image_jtag_connected.jpg new file mode 100644 index 00000000..43075cee Binary files /dev/null and b/doc/image_jtag_connected.jpg differ diff --git a/doc/jtag_demo.gif b/doc/jtag_demo.gif new file mode 100644 index 00000000..689620ae Binary files /dev/null and b/doc/jtag_demo.gif differ diff --git a/doc/wiring_jtag.fzz b/doc/wiring_jtag.fzz new file mode 100644 index 00000000..d7f89095 Binary files /dev/null and b/doc/wiring_jtag.fzz differ diff --git a/doc/wiring_jtag.png b/doc/wiring_jtag.png new file mode 100644 index 00000000..0a3e0db2 Binary files /dev/null and b/doc/wiring_jtag.png differ diff --git a/doc/wiring_jtag.xcf b/doc/wiring_jtag.xcf new file mode 100644 index 00000000..0499589e Binary files /dev/null and b/doc/wiring_jtag.xcf differ diff --git a/docker/raspi3-gdb/Dockerfile b/docker/raspi3-gdb/Dockerfile deleted file mode 100644 index 286dd94c..00000000 --- a/docker/raspi3-gdb/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -## SPDX-License-Identifier: MIT -## -## Copyright (c) 2019 Andre Richter -FROM ubuntu:18.04 - -LABEL maintainer="The resources team , Andre Richter " - -RUN set -ex; \ - tempPkgs=' \ - ca-certificates \ - wget \ - '; \ - apt-get update; \ - apt-get install -q -y --no-install-recommends \ - $tempPkgs \ - gdb-multiarch \ - ; \ - wget -P ~ git.io/.gdbinit; \ - apt-get purge -y --auto-remove $tempPkgs; \ - apt-get autoremove -q -y; \ - apt-get clean -q -y; \ - rm -rf /var/lib/apt/lists/* - -COPY auto /root/.gdbinit.d/auto diff --git a/docker/raspi3-openocd/Dockerfile b/docker/raspi3-openocd/Dockerfile deleted file mode 100644 index dae935a5..00000000 --- a/docker/raspi3-openocd/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -## SPDX-License-Identifier: MIT -## -## Copyright (c) 2018-2019 Andre Richter -## Copyright (c) 2019 Nao Taco -FROM ubuntu:18.04 - -LABEL maintainer="The resources team , Andre Richter " - -RUN set -ex; \ - tempPkgs=' \ - automake \ - build-essential \ - ca-certificates \ - git \ - libtool \ - pkg-config \ - '; \ - apt-get update; \ - apt-get install -q -y --no-install-recommends \ - $tempPkgs \ - libusb-1.0.0-dev \ - ; \ - git clone --depth 1 https://git.code.sf.net/p/openocd/code openocd; \ - cd openocd; \ - ./bootstrap; \ - ./configure --enable-ftdi; \ - make; \ - make install; \ - apt-get purge -y --auto-remove $tempPkgs; \ - apt-get autoremove -q -y; \ - apt-get clean -q -y; \ - rm -rf /var/lib/apt/lists/* - -COPY rpi3.cfg /openocd/ - -ENTRYPOINT ["openocd"] -CMD ["-f", "/openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg", "-f", "/openocd/rpi3.cfg"] diff --git a/docker/rustembedded-osdev-utils/Dockerfile b/docker/rustembedded-osdev-utils/Dockerfile index c4a5486e..34922eda 100644 --- a/docker/rustembedded-osdev-utils/Dockerfile +++ b/docker/rustembedded-osdev-utils/Dockerfile @@ -1,49 +1,74 @@ ## SPDX-License-Identifier: MIT ## ## Copyright (c) 2017-2019 Andre Richter +## Copyright (c) 2019 Nao Taco FROM ubuntu:18.04 LABEL maintainer="The resources team , Andre Richter " RUN set -ex; \ - apt-get update; \ - apt-get install -q -y --no-install-recommends \ + tempPkgs=' \ + automake \ build-essential \ ca-certificates \ git \ libglib2.0-dev \ libpixman-1-dev \ - locales \ + libtool \ pkg-config \ + wget \ + '; \ + apt-get update; \ + apt-get install -q -y --no-install-recommends \ + $tempPkgs \ + # persistent packages + gdb-multiarch \ + libusb-1.0.0-dev \ python \ - screen \ - tmux \ + locales \ ; \ - echo "set -g mouse on" > ~/.tmux.conf; \ + # QEMU + git clone --depth 1 git://git.qemu.org/qemu.git; \ + cd qemu; \ + ./configure --target-list=aarch64-softmmu --enable-modules \ + --enable-tcg-interpreter --enable-debug-tcg \ + --python=/usr/bin/python2.7; \ + make; \ + make install; \ + cd ..; \ + rm -rf qemu; \ + # Raspbootcom + git clone --depth 1 https://github.com/andre-richter/raspbootin.git; \ + cd raspbootin/raspbootcom; \ + make; \ + cp raspbootcom /usr/bin; \ + cd ../..; \ + rm -rf raspbootin; \ + # Openocd + git clone --depth 1 https://git.code.sf.net/p/openocd/code openocd; \ + cd openocd; \ + ./bootstrap; \ + ./configure --enable-ftdi; \ + make; \ + make install; \ + # GDB + wget -P ~ git.io/.gdbinit; \ # Cleanup - apt-get autoremove -q -y; \ - apt-get clean -q -y; \ + apt-get purge -y --auto-remove $tempPkgs; \ + apt-get autoremove -q -y; \ + apt-get clean -q -y; \ rm -rf /var/lib/apt/lists/* +# Locales RUN locale-gen en_US.UTF-8 ENV LANG=en_US.UTF-8 \ LANGUAGE=en_US:en \ LC_ALL=en_US.UTF-8 -RUN git clone git://git.qemu.org/qemu.git; \ - cd qemu; \ - ./configure --target-list=aarch64-softmmu --enable-modules \ - --enable-tcg-interpreter --enable-debug-tcg \ - --python=/usr/bin/python2.7; \ - make; \ - make install; \ - cd ..; \ - rm -rf qemu +# Openocd +COPY rpi3.cfg /openocd/ +COPY rpi4.cfg /openocd/ -RUN git clone https://github.com/andre-richter/raspbootin.git; \ - cd raspbootin/raspbootcom; \ - make; \ - cp raspbootcom /usr/bin; \ - cd ../..; \ - rm -rf raspbootin +# GDB +COPY auto /root/.gdbinit.d/auto diff --git a/docker/raspi3-gdb/auto b/docker/rustembedded-osdev-utils/auto similarity index 100% rename from docker/raspi3-gdb/auto rename to docker/rustembedded-osdev-utils/auto diff --git a/docker/raspi3-openocd/rpi3.cfg b/docker/rustembedded-osdev-utils/rpi3.cfg similarity index 100% rename from docker/raspi3-openocd/rpi3.cfg rename to docker/rustembedded-osdev-utils/rpi3.cfg diff --git a/docker/rustembedded-osdev-utils/rpi4.cfg b/docker/rustembedded-osdev-utils/rpi4.cfg new file mode 100644 index 00000000..68b24562 --- /dev/null +++ b/docker/rustembedded-osdev-utils/rpi4.cfg @@ -0,0 +1,49 @@ +# Script from +# https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/ +# with minor adaptions. + +transport select jtag + +# we need to enable srst even though we don't connect it +reset_config trst_and_srst + +adapter_khz 1000 +jtag_ntrst_delay 500 + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME rpi4 +} + +# +# Main DAP +# +if { [info exists DAP_TAPID] } { + set _DAP_TAPID $DAP_TAPID +} else { + set _DAP_TAPID 0x4ba00477 +} + +jtag newtap $_CHIPNAME tap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_DAP_TAPID -enable +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.tap + +set _TARGETNAME $_CHIPNAME.core +set _CTINAME $_CHIPNAME.cti + +set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000} +set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000} +set _cores 4 + +for { set _core 0 } { $_core < $_cores } { incr _core } { + + cti create $_CTINAME.$_core -dap $_CHIPNAME.dap -ap-num 0 \ + -ctibase [lindex $CTIBASE $_core] + + target create $_TARGETNAME$_core aarch64 \ + -dap $_CHIPNAME.dap -coreid $_core \ + -dbgbase [lindex $DBGBASE $_core] -cti $_CTINAME.$_core + + $_TARGETNAME$_core configure -event reset-assert-post "aarch64 dbginit" + $_TARGETNAME$_core configure -event gdb-attach { halt } +}