Add tutorial 15: MMIO Remap

pull/84/head
Andre Richter 4 years ago
parent 64658a6ce9
commit 4caa0188c7
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -1,9 +1,10 @@
# Tutorial 11 - Virtual Memory # Tutorial 11 - Virtual Memory Part 1: Identity Map All The Things!
## tl;dr ## tl;dr
The `MMU` is turned on; A simple scheme is used: static `64 KiB` translation tables; For educational - The `MMU` is turned on.
purposes, we write to a remapped `UART`. - A simple scheme is used: static `64 KiB` translation tables.
- For educational purposes, we write to a remapped `UART`, and `identity map` everything else.
## Table of Contents ## Table of Contents
@ -23,8 +24,9 @@ purposes, we write to a remapped `UART`.
## Introduction ## Introduction
Virtual memory is an immensely complex, but important and powerful topic. In this tutorial, we start Virtual memory is an immensely complex, but important and powerful topic. In this tutorial, we start
slow and easy by switching on the `MMU`, using static translation tables and mapping everything at slow and easy by switching on the `MMU`, using static translation tables and `identity-map`
once. everything at once (except for the `UART`, which we remap for educational purposes; This will be
gone again in the next tutorial).
## MMU and paging theory ## MMU and paging theory
@ -297,9 +299,9 @@ Minipush 1.0
## Diff to previous ## Diff to previous
```diff ```diff
diff -uNr 10_privilege_level/src/_arch/aarch64/memory/mmu.rs 11_virtual_memory/src/_arch/aarch64/memory/mmu.rs diff -uNr 10_privilege_level/src/_arch/aarch64/memory/mmu.rs 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/memory/mmu.rs
--- 10_privilege_level/src/_arch/aarch64/memory/mmu.rs --- 10_privilege_level/src/_arch/aarch64/memory/mmu.rs
+++ 11_virtual_memory/src/_arch/aarch64/memory/mmu.rs +++ 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/memory/mmu.rs
@@ -0,0 +1,333 @@ @@ -0,0 +1,333 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT OR Apache-2.0
+// +//
@ -635,9 +637,9 @@ diff -uNr 10_privilege_level/src/_arch/aarch64/memory/mmu.rs 11_virtual_memory/s
+ } + }
+} +}
diff -uNr 10_privilege_level/src/bsp/raspberrypi/link.ld 11_virtual_memory/src/bsp/raspberrypi/link.ld diff -uNr 10_privilege_level/src/bsp/raspberrypi/link.ld 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/link.ld
--- 10_privilege_level/src/bsp/raspberrypi/link.ld --- 10_privilege_level/src/bsp/raspberrypi/link.ld
+++ 11_virtual_memory/src/bsp/raspberrypi/link.ld +++ 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/link.ld
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
/* Set current address to the value from which the RPi starts execution */ /* Set current address to the value from which the RPi starts execution */
. = 0x80000; . = 0x80000;
@ -656,9 +658,9 @@ diff -uNr 10_privilege_level/src/bsp/raspberrypi/link.ld 11_virtual_memory/src/b
.data : .data :
{ {
diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory/mmu.rs
--- 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs --- 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs
+++ 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs +++ 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory/mmu.rs
@@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT OR Apache-2.0
+// +//
@ -749,9 +751,9 @@ diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory/mmu.rs 11_virtual_memory
+ &LAYOUT + &LAYOUT
+} +}
diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory.rs 11_virtual_memory/src/bsp/raspberrypi/memory.rs diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory.rs 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory.rs
--- 10_privilege_level/src/bsp/raspberrypi/memory.rs --- 10_privilege_level/src/bsp/raspberrypi/memory.rs
+++ 11_virtual_memory/src/bsp/raspberrypi/memory.rs +++ 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory.rs
@@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
//! BSP Memory Management. //! BSP Memory Management.
@ -824,9 +826,9 @@ diff -uNr 10_privilege_level/src/bsp/raspberrypi/memory.rs 11_virtual_memory/src
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
diff -uNr 10_privilege_level/src/bsp.rs 11_virtual_memory/src/bsp.rs diff -uNr 10_privilege_level/src/bsp.rs 11_virtual_memory_part1_identity_mapping/src/bsp.rs
--- 10_privilege_level/src/bsp.rs --- 10_privilege_level/src/bsp.rs
+++ 11_virtual_memory/src/bsp.rs +++ 11_virtual_memory_part1_identity_mapping/src/bsp.rs
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
//! Conditional re-exporting of Board Support Packages. //! Conditional re-exporting of Board Support Packages.
@ -837,9 +839,9 @@ diff -uNr 10_privilege_level/src/bsp.rs 11_virtual_memory/src/bsp.rs
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod raspberrypi; mod raspberrypi;
diff -uNr 10_privilege_level/src/main.rs 11_virtual_memory/src/main.rs diff -uNr 10_privilege_level/src/main.rs 11_virtual_memory_part1_identity_mapping/src/main.rs
--- 10_privilege_level/src/main.rs --- 10_privilege_level/src/main.rs
+++ 11_virtual_memory/src/main.rs +++ 11_virtual_memory_part1_identity_mapping/src/main.rs
@@ -11,10 +11,12 @@ @@ -11,10 +11,12 @@
//! //!
//! - [`bsp::console::console()`] - Returns a reference to the kernel's [console interface]. //! - [`bsp::console::console()`] - Returns a reference to the kernel's [console interface].
@ -908,9 +910,9 @@ diff -uNr 10_privilege_level/src/main.rs 11_virtual_memory/src/main.rs
loop { loop {
let c = bsp::console::console().read_char(); let c = bsp::console::console().read_char();
diff -uNr 10_privilege_level/src/memory/mmu.rs 11_virtual_memory/src/memory/mmu.rs diff -uNr 10_privilege_level/src/memory/mmu.rs 11_virtual_memory_part1_identity_mapping/src/memory/mmu.rs
--- 10_privilege_level/src/memory/mmu.rs --- 10_privilege_level/src/memory/mmu.rs
+++ 11_virtual_memory/src/memory/mmu.rs +++ 11_virtual_memory_part1_identity_mapping/src/memory/mmu.rs
@@ -0,0 +1,199 @@ @@ -0,0 +1,199 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT OR Apache-2.0
+// +//
@ -1112,9 +1114,9 @@ diff -uNr 10_privilege_level/src/memory/mmu.rs 11_virtual_memory/src/memory/mmu.
+ } + }
+} +}
diff -uNr 10_privilege_level/src/memory.rs 11_virtual_memory/src/memory.rs diff -uNr 10_privilege_level/src/memory.rs 11_virtual_memory_part1_identity_mapping/src/memory.rs
--- 10_privilege_level/src/memory.rs --- 10_privilege_level/src/memory.rs
+++ 11_virtual_memory/src/memory.rs +++ 11_virtual_memory_part1_identity_mapping/src/memory.rs
@@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
//! Memory Management. //! Memory Management.

@ -479,8 +479,8 @@ General purpose register:
## Diff to previous ## Diff to previous
```diff ```diff
diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.rs 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs diff -uNr 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/exception.rs 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs
--- 11_virtual_memory/src/_arch/aarch64/exception.rs --- 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/exception.rs
+++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs +++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.rs
@@ -4,7 +4,230 @@ @@ -4,7 +4,230 @@
@ -740,8 +740,8 @@ diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.rs 12_exceptions_part1_g
+ barrier::isb(barrier::SY); + barrier::isb(barrier::SY);
+} +}
diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.S 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S diff -uNr 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/exception.S 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S
--- 11_virtual_memory/src/_arch/aarch64/exception.S --- 11_virtual_memory_part1_identity_mapping/src/_arch/aarch64/exception.S
+++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S +++ 12_exceptions_part1_groundwork/src/_arch/aarch64/exception.S
@@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
+// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: MIT OR Apache-2.0
@ -883,8 +883,8 @@ diff -uNr 11_virtual_memory/src/_arch/aarch64/exception.S 12_exceptions_part1_gr
+ +
+ eret + eret
diff -uNr 11_virtual_memory/src/bsp/raspberrypi/link.ld 12_exceptions_part1_groundwork/src/bsp/raspberrypi/link.ld diff -uNr 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/link.ld 12_exceptions_part1_groundwork/src/bsp/raspberrypi/link.ld
--- 11_virtual_memory/src/bsp/raspberrypi/link.ld --- 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/link.ld
+++ 12_exceptions_part1_groundwork/src/bsp/raspberrypi/link.ld +++ 12_exceptions_part1_groundwork/src/bsp/raspberrypi/link.ld
@@ -14,6 +14,11 @@ @@ -14,6 +14,11 @@
*(.text._start) *(.text*) *(.text._start) *(.text*)
@ -899,8 +899,8 @@ diff -uNr 11_virtual_memory/src/bsp/raspberrypi/link.ld 12_exceptions_part1_grou
{ {
*(.rodata*) *(.rodata*)
diff -uNr 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs diff -uNr 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory/mmu.rs 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs
--- 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs --- 11_virtual_memory_part1_identity_mapping/src/bsp/raspberrypi/memory/mmu.rs
+++ 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs +++ 12_exceptions_part1_groundwork/src/bsp/raspberrypi/memory/mmu.rs
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
// Public Definitions // Public Definitions
@ -941,8 +941,8 @@ diff -uNr 11_virtual_memory/src/bsp/raspberrypi/memory/mmu.rs 12_exceptions_part
RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE) RangeInclusive::new(memory_map::mmio::START, memory_map::mmio::END_INCLUSIVE)
} }
diff -uNr 11_virtual_memory/src/bsp.rs 12_exceptions_part1_groundwork/src/bsp.rs diff -uNr 11_virtual_memory_part1_identity_mapping/src/bsp.rs 12_exceptions_part1_groundwork/src/bsp.rs
--- 11_virtual_memory/src/bsp.rs --- 11_virtual_memory_part1_identity_mapping/src/bsp.rs
+++ 12_exceptions_part1_groundwork/src/bsp.rs +++ 12_exceptions_part1_groundwork/src/bsp.rs
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
@ -954,8 +954,8 @@ diff -uNr 11_virtual_memory/src/bsp.rs 12_exceptions_part1_groundwork/src/bsp.rs
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))] #[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod raspberrypi; mod raspberrypi;
diff -uNr 11_virtual_memory/src/main.rs 12_exceptions_part1_groundwork/src/main.rs diff -uNr 11_virtual_memory_part1_identity_mapping/src/main.rs 12_exceptions_part1_groundwork/src/main.rs
--- 11_virtual_memory/src/main.rs --- 11_virtual_memory_part1_identity_mapping/src/main.rs
+++ 12_exceptions_part1_groundwork/src/main.rs +++ 12_exceptions_part1_groundwork/src/main.rs
@@ -108,6 +108,7 @@ @@ -108,6 +108,7 @@
#![feature(const_generics)] #![feature(const_generics)]
@ -1010,8 +1010,8 @@ diff -uNr 11_virtual_memory/src/main.rs 12_exceptions_part1_groundwork/src/main.
loop { loop {
let c = bsp::console::console().read_char(); let c = bsp::console::console().read_char();
diff -uNr 11_virtual_memory/src/memory/mmu.rs 12_exceptions_part1_groundwork/src/memory/mmu.rs diff -uNr 11_virtual_memory_part1_identity_mapping/src/memory/mmu.rs 12_exceptions_part1_groundwork/src/memory/mmu.rs
--- 11_virtual_memory/src/memory/mmu.rs --- 11_virtual_memory_part1_identity_mapping/src/memory/mmu.rs
+++ 12_exceptions_part1_groundwork/src/memory/mmu.rs +++ 12_exceptions_part1_groundwork/src/memory/mmu.rs
@@ -42,6 +42,7 @@ @@ -42,6 +42,7 @@

@ -0,0 +1,2 @@
[target.'cfg(target_os = "none")']
runner = "target/kernel_test_runner.sh"

@ -0,0 +1,7 @@
{
"editor.formatOnSave": true,
"editor.rulers": [100],
"rust-analyzer.checkOnSave.overrideCommand": ["make", "check"],
"rust-analyzer.cargo.target": "aarch64-unknown-none-softfloat",
"rust-analyzer.cargo.features": ["bsp_rpi3"]
}

@ -0,0 +1,91 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "cortex-a"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6922a40af4d1a2deac8c963b9f3e57311b8912490740234f1ad182425c547f80"
dependencies = [
"register",
]
[[package]]
name = "kernel"
version = "0.1.0"
dependencies = [
"cortex-a",
"qemu-exit",
"register",
"test-macros",
"test-types",
]
[[package]]
name = "proc-macro2"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9"
dependencies = [
"unicode-xid",
]
[[package]]
name = "qemu-exit"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73ae13954572c7ca0ec48ba9fe6a59c0392066eba62f8cb384ffd5addf538c5"
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "register"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deaba5b0e477d21f61a57504bb5cef4a1e86de30300b457d38971c1cfc98b815"
dependencies = [
"tock-registers",
]
[[package]]
name = "syn"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "test-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"test-types",
]
[[package]]
name = "test-types"
version = "0.1.0"
[[package]]
name = "tock-registers"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70323afdb8082186c0986da0e10f6e4ed103d681c921c00597e98d9806dac20f"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

@ -0,0 +1,45 @@
[package]
name = "kernel"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
edition = "2018"
# The features section is used to select the target board.
[features]
default = []
bsp_rpi3 = ["cortex-a", "register"]
bsp_rpi4 = ["cortex-a", "register"]
[dependencies]
qemu-exit = "1.0.x"
test-types = { path = "test-types" }
# Optional dependencies
cortex-a = { version = "3.0.x", optional = true }
register = { version = "0.5.x", features = ["no_std_unit_tests"], optional = true }
##--------------------------------------------------------------------------------------------------
## Testing
##--------------------------------------------------------------------------------------------------
[dev-dependencies]
test-macros = { path = "test-macros" }
# Unit tests are done in the library part of the kernel.
[lib]
name = "libkernel"
test = true
# Disable unit tests for the kernel binary.
[[bin]]
name = "kernel"
test = false
# List of tests without harness.
[[test]]
name = "00_console_sanity"
harness = false
[[test]]
name = "02_exception_sync_page_fault"
harness = false

@ -0,0 +1,173 @@
## SPDX-License-Identifier: MIT OR Apache-2.0
##
## Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
# Default to the RPi3
BSP ?= rpi3
# Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name
UNAME_S = $(shell uname -s)
# BSP-specific arguments
ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none
QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting
OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg
JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi3.img
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE =
QEMU_RELEASE_ARGS = -serial stdio -display none
QEMU_TEST_ARGS = $(QEMU_RELEASE_ARGS) -semihosting
OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi4.cfg
JTAG_BOOT_IMAGE = ../X1_JTAG_boot/jtag_boot_rpi4.img
LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif
# Export for build.rs
export LINKER_FILE
# Testing-specific arguments
ifdef TEST
ifeq ($(TEST),unit)
TEST_ARG = --lib
else
TEST_ARG = --test $(TEST)
endif
endif
QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
FEATURES = bsp_$(BSP)
COMPILER_ARGS = --target=$(TARGET) \
--features $(FEATURES) \
--release
RUSTC_CMD = cargo rustc $(COMPILER_ARGS)
DOC_CMD = cargo doc $(COMPILER_ARGS)
CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
CHECK_CMD = cargo check $(COMPILER_ARGS)
TEST_CMD = cargo test $(COMPILER_ARGS)
OBJCOPY_CMD = rust-objcopy \
--strip-all \
-O binary
KERNEL_ELF = target/$(TARGET)/release/kernel
DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_CMD_TEST = docker run -i --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
DOCKER_CMD_USER = $(DOCKER_CMD_TEST) -t
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot
DOCKER_ARG_DEV = --privileged -v /dev:/dev
DOCKER_ARG_NET = --network host
DOCKER_QEMU = $(DOCKER_CMD_USER) $(DOCKER_IMAGE)
DOCKER_GDB = $(DOCKER_CMD_USER) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TEST = $(DOCKER_CMD_TEST) $(DOCKER_IMAGE)
# Dockerize commands that require USB device passthrough only on Linux
ifeq ($(UNAME_S),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_USER) $(DOCKER_ARG_DEV)
DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE)
DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif
EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
EXEC_MINIPUSH = ruby ../utils/minipush.rb
.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu test chainboot jtagboot openocd gdb gdb-opt0 \
clippy clean readelf objdump nm check
all: $(KERNEL_BIN)
$(KERNEL_ELF):
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
$(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
doc:
$(DOC_CMD) --document-private-items --open
ifeq ($(QEMU_MACHINE_TYPE),)
qemu test:
@echo $(QEMU_MISSING_STRING)
else
qemu: $(KERNEL_BIN)
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
define KERNEL_TEST_RUNNER
#!/usr/bin/env bash
$(OBJCOPY_CMD) $$1 $$1.img
TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g')
$(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY
endef
export KERNEL_TEST_RUNNER
test:
@mkdir -p target
@echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
@chmod +x target/kernel_test_runner.sh
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG)
endif
chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot:
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE)
openocd:
@$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG)
define gen_gdb
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(RUSTC_CMD)
@$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF)
endef
gdb:
$(call gen_gdb,-C debuginfo=2)
gdb-opt0:
$(call gen_gdb,-C debuginfo=2 -C opt-level=0)
clippy:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
clean:
rm -rf target $(KERNEL_BIN)
readelf: $(KERNEL_ELF)
readelf --headers $(KERNEL_ELF)
objdump: $(KERNEL_ELF)
rust-objdump --arch-name aarch64 --disassemble --demangle --no-show-raw-insn \
--print-imm-hex $(KERNEL_ELF)
nm: $(KERNEL_ELF)
rust-nm --demangle --print-size $(KERNEL_ELF) | sort
# For rust-analyzer
check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json

File diff suppressed because it is too large Load Diff

@ -0,0 +1,7 @@
use std::env;
fn main() {
let linker_file = env::var("LINKER_FILE").unwrap();
println!("cargo:rerun-if-changed={}", linker_file);
}

@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Architectural processor code.
use crate::{bsp, cpu};
use cortex_a::{asm, regs::*};
//--------------------------------------------------------------------------------------------------
// Boot Code
//--------------------------------------------------------------------------------------------------
/// The entry of the `kernel` binary.
///
/// The function must be named `_start`, because the linker is looking for this exact name.
///
/// # Safety
///
/// - Linker script must ensure to place this function at `0x80_000`.
#[naked]
#[no_mangle]
pub unsafe extern "C" fn _start() -> ! {
// Expect the boot core to start in EL2.
if (bsp::cpu::BOOT_CORE_ID == cpu::smp::core_id())
&& (CurrentEL.get() == CurrentEL::EL::EL2.value)
{
el2_to_el1_transition()
} else {
// If not core0, infinitely wait for events.
wait_forever()
}
}
/// Transition from EL2 to EL1.
///
/// # Safety
///
/// - The HW state of EL1 must be prepared in a sound way.
/// - Exception return from EL2 must must continue execution in EL1 with
/// `runtime_init::runtime_init()`.
#[inline(always)]
unsafe fn el2_to_el1_transition() -> ! {
use crate::runtime_init;
// Enable timer counter registers for EL1.
CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
// No offset for reading the counters.
CNTVOFF_EL2.set(0);
// Set EL1 execution state to AArch64.
HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64);
// Set up a simulated exception return.
//
// First, fake a saved program status where all interrupts were masked and SP_EL1 was used as a
// stack pointer.
SPSR_EL2.write(
SPSR_EL2::D::Masked
+ SPSR_EL2::A::Masked
+ SPSR_EL2::I::Masked
+ SPSR_EL2::F::Masked
+ SPSR_EL2::M::EL1h,
);
// Second, let the link register point to runtime_init().
ELR_EL2.set(runtime_init::runtime_init as *const () as u64);
// Set up SP_EL1 (stack pointer), which will be used by EL1 once we "return" to it.
SP_EL1.set(bsp::memory::phys_boot_core_stack_end().into_usize() as u64);
// Use `eret` to "return" to EL1. This results in execution of runtime_init() in EL1.
asm::eret()
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
pub use asm::nop;
/// Spin for `n` cycles.
#[inline(always)]
pub fn spin_for_cycles(n: usize) {
for _ in 0..n {
asm::nop();
}
}
/// Pause execution on the core.
#[inline(always)]
pub fn wait_forever() -> ! {
loop {
asm::wfe()
}
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
use qemu_exit::QEMUExit;
const QEMU_EXIT_HANDLE: qemu_exit::AArch64 = qemu_exit::AArch64::new();
/// Make the host QEMU binary execute `exit(1)`.
pub fn qemu_exit_failure() -> ! {
QEMU_EXIT_HANDLE.exit_failure()
}
/// Make the host QEMU binary execute `exit(0)`.
pub fn qemu_exit_success() -> ! {
QEMU_EXIT_HANDLE.exit_success()
}

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

@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
/// Call the function provided by parameter `\handler` after saving the exception context. Provide
/// the context as the first parameter to '\handler'.
.macro CALL_WITH_CONTEXT handler
// Make room on the stack for the exception context.
sub sp, sp, #16 * 17
// Store all general purpose registers on the stack.
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
// Add the exception link register (ELR_EL1) and the saved program status (SPSR_EL1).
mrs x1, ELR_EL1
mrs x2, SPSR_EL1
stp lr, x1, [sp, #16 * 15]
str w2, [sp, #16 * 16]
// x0 is the first argument for the function called through `\handler`.
mov x0, sp
// Call `\handler`.
bl \handler
// After returning from exception handling code, replay the saved context and return via `eret`.
b __exception_restore_context
.endm
.macro FIQ_SUSPEND
1: wfe
b 1b
.endm
//--------------------------------------------------------------------------------------------------
// The exception vector table.
//--------------------------------------------------------------------------------------------------
.section .exception_vectors, "ax", @progbits
// Align by 2^11 bytes, as demanded by ARMv8-A. Same as ALIGN(2048) in an ld script.
.align 11
// Export a symbol for the Rust code to use.
__exception_vector_start:
// Current exception level with SP_EL0.
//
// .org sets the offset relative to section start.
//
// # Safety
//
// - It must be ensured that `CALL_WITH_CONTEXT` <= 0x80 bytes.
.org 0x000
CALL_WITH_CONTEXT current_el0_synchronous
.org 0x080
CALL_WITH_CONTEXT current_el0_irq
.org 0x100
FIQ_SUSPEND
.org 0x180
CALL_WITH_CONTEXT current_el0_serror
// Current exception level with SP_ELx, x > 0.
.org 0x200
CALL_WITH_CONTEXT current_elx_synchronous
.org 0x280
CALL_WITH_CONTEXT current_elx_irq
.org 0x300
FIQ_SUSPEND
.org 0x380
CALL_WITH_CONTEXT current_elx_serror
// Lower exception level, AArch64
.org 0x400
CALL_WITH_CONTEXT lower_aarch64_synchronous
.org 0x480
CALL_WITH_CONTEXT lower_aarch64_irq
.org 0x500
FIQ_SUSPEND
.org 0x580
CALL_WITH_CONTEXT lower_aarch64_serror
// Lower exception level, AArch32
.org 0x600
CALL_WITH_CONTEXT lower_aarch32_synchronous
.org 0x680
CALL_WITH_CONTEXT lower_aarch32_irq
.org 0x700
FIQ_SUSPEND
.org 0x780
CALL_WITH_CONTEXT lower_aarch32_serror
.org 0x800
//--------------------------------------------------------------------------------------------------
// Helper functions
//--------------------------------------------------------------------------------------------------
.section .text
__exception_restore_context:
ldr w19, [sp, #16 * 16]
ldp lr, x20, [sp, #16 * 15]
msr SPSR_EL1, x19
msr ELR_EL1, x20
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
add sp, sp, #16 * 17
eret

@ -0,0 +1,261 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Architectural synchronous and asynchronous exception handling.
use crate::{bsp, exception};
use core::fmt;
use cortex_a::{barrier, regs::*};
use register::InMemoryRegister;
// Assembly counterpart to this file.
global_asm!(include_str!("exception.S"));
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// Wrapper struct for memory copy of SPSR_EL1.
#[repr(transparent)]
struct SpsrEL1(InMemoryRegister<u32, SPSR_EL1::Register>);
/// The exception context as it is stored on the stack on exception entry.
#[repr(C)]
struct ExceptionContext {
/// General Purpose Registers.
gpr: [u64; 30],
/// The link register, aka x30.
lr: u64,
/// Exception link register. The program counter at the time the exception happened.
elr_el1: u64,
/// Saved program status.
spsr_el1: SpsrEL1,
}
/// Wrapper struct for pretty printing ESR_EL1.
struct EsrEL1;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
/// Prints verbose information about the exception and then panics.
fn default_exception_handler(e: &ExceptionContext) {
panic!(
"\n\nCPU Exception!\n\
FAR_EL1: {:#018x}\n\
{}\n\
{}",
FAR_EL1.get(),
EsrEL1 {},
e
);
}
//------------------------------------------------------------------------------
// Current, EL0
//------------------------------------------------------------------------------
#[no_mangle]
unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn current_el0_irq(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext) {
default_exception_handler(e);
}
//------------------------------------------------------------------------------
// Current, ELx
//------------------------------------------------------------------------------
#[no_mangle]
unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn current_elx_irq(_e: &mut ExceptionContext) {
use exception::asynchronous::interface::IRQManager;
let token = &exception::asynchronous::IRQContext::new();
bsp::exception::asynchronous::irq_manager().handle_pending_irqs(token);
}
#[no_mangle]
unsafe extern "C" fn current_elx_serror(e: &mut ExceptionContext) {
default_exception_handler(e);
}
//------------------------------------------------------------------------------
// Lower, AArch64
//------------------------------------------------------------------------------
#[no_mangle]
unsafe extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext) {
default_exception_handler(e);
}
//------------------------------------------------------------------------------
// Lower, AArch32
//------------------------------------------------------------------------------
#[no_mangle]
unsafe extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext) {
default_exception_handler(e);
}
#[no_mangle]
unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext) {
default_exception_handler(e);
}
//------------------------------------------------------------------------------
// Pretty printing
//------------------------------------------------------------------------------
/// Human readable ESR_EL1.
#[rustfmt::skip]
impl fmt::Display for EsrEL1 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let esr_el1 = ESR_EL1.extract();
// Raw print of whole register.
writeln!(f, "ESR_EL1: {:#010x}", esr_el1.get())?;
// Raw print of exception class.
write!(f, " Exception Class (EC) : {:#x}", esr_el1.read(ESR_EL1::EC))?;
// Exception class, translation.
let ec_translation = match esr_el1.read_as_enum(ESR_EL1::EC) {
Some(ESR_EL1::EC::Value::DataAbortCurrentEL) => "Data Abort, current EL",
_ => "N/A",
};
writeln!(f, " - {}", ec_translation)?;
// Raw print of instruction specific syndrome.
write!(f, " Instr Specific Syndrome (ISS): {:#x}", esr_el1.read(ESR_EL1::ISS))?;
Ok(())
}
}
/// Human readable SPSR_EL1.
#[rustfmt::skip]
impl fmt::Display for SpsrEL1 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Raw value.
writeln!(f, "SPSR_EL1: {:#010x}", self.0.get())?;
let to_flag_str = |x| -> _ {
if x { "Set" } else { "Not set" }
};
writeln!(f, " Flags:")?;
writeln!(f, " Negative (N): {}", to_flag_str(self.0.is_set(SPSR_EL1::N)))?;
writeln!(f, " Zero (Z): {}", to_flag_str(self.0.is_set(SPSR_EL1::Z)))?;
writeln!(f, " Carry (C): {}", to_flag_str(self.0.is_set(SPSR_EL1::C)))?;
writeln!(f, " Overflow (V): {}", to_flag_str(self.0.is_set(SPSR_EL1::V)))?;
let to_mask_str = |x| -> _ {
if x { "Masked" } else { "Unmasked" }
};
writeln!(f, " Exception handling state:")?;
writeln!(f, " Debug (D): {}", to_mask_str(self.0.is_set(SPSR_EL1::D)))?;
writeln!(f, " SError (A): {}", to_mask_str(self.0.is_set(SPSR_EL1::A)))?;
writeln!(f, " IRQ (I): {}", to_mask_str(self.0.is_set(SPSR_EL1::I)))?;
writeln!(f, " FIQ (F): {}", to_mask_str(self.0.is_set(SPSR_EL1::F)))?;
write!(f, " Illegal Execution State (IL): {}",
to_flag_str(self.0.is_set(SPSR_EL1::IL))
)?;
Ok(())
}
}
/// Human readable print of the exception context.
impl fmt::Display for ExceptionContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "ELR_EL1: {:#018x}", self.elr_el1)?;
writeln!(f, "{}", self.spsr_el1)?;
writeln!(f)?;
writeln!(f, "General purpose register:")?;
#[rustfmt::skip]
let alternating = |x| -> _ {
if x % 2 == 0 { " " } else { "\n" }
};
// Print two registers per line.
for (i, reg) in self.gpr.iter().enumerate() {
write!(f, " x{: <2}: {: >#018x}{}", i, reg, alternating(i))?;
}
write!(f, " lr : {:#018x}", self.lr)?;
Ok(())
}
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use crate::exception::PrivilegeLevel;
/// The processing element's current privilege level.
pub fn current_privilege_level() -> (PrivilegeLevel, &'static str) {
let el = CurrentEL.read_as_enum(CurrentEL::EL);
match el {
Some(CurrentEL::EL::Value::EL2) => (PrivilegeLevel::Hypervisor, "EL2"),
Some(CurrentEL::EL::Value::EL1) => (PrivilegeLevel::Kernel, "EL1"),
Some(CurrentEL::EL::Value::EL0) => (PrivilegeLevel::User, "EL0"),
_ => (PrivilegeLevel::Unknown, "Unknown"),
}
}
/// Init exception handling by setting the exception vector base address register.
///
/// # Safety
///
/// - Changes the HW state of the executing core.
/// - The vector table and the symbol `__exception_vector_table_start` from the linker script must
/// adhere to the alignment and size constraints demanded by the ARMv8-A Architecture Reference
/// Manual.
pub unsafe fn handling_init() {
// Provided by exception.S.
extern "C" {
static mut __exception_vector_start: u64;
}
let addr: u64 = &__exception_vector_start as *const _ as u64;
VBAR_EL1.set(addr);
// Force VBAR update to complete before next instruction.
barrier::isb(barrier::SY);
}

@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Architectural asynchronous exception handling.
use cortex_a::regs::*;
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
mod daif_bits {
pub const IRQ: u8 = 0b0010;
}
trait DaifField {
fn daif_field() -> register::Field<u32, DAIF::Register>;
}
struct Debug;
struct SError;
struct IRQ;
struct FIQ;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl DaifField for Debug {
fn daif_field() -> register::Field<u32, DAIF::Register> {
DAIF::D
}
}
impl DaifField for SError {
fn daif_field() -> register::Field<u32, DAIF::Register> {
DAIF::A
}
}
impl DaifField for IRQ {
fn daif_field() -> register::Field<u32, DAIF::Register> {
DAIF::I
}
}
impl DaifField for FIQ {
fn daif_field() -> register::Field<u32, DAIF::Register> {
DAIF::F
}
}
fn is_masked<T: DaifField>() -> bool {
DAIF.is_set(T::daif_field())
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Returns whether IRQs are masked on the executing core.
pub fn is_local_irq_masked() -> bool {
!is_masked::<IRQ>()
}
/// Unmask IRQs on the executing core.
///
/// It is not needed to place an explicit instruction synchronization barrier after the `msr`.
/// Quoting the Architecture Reference Manual for ARMv8-A, section C5.1.3:
///
/// "Writes to PSTATE.{PAN, D, A, I, F} occur in program order without the need for additional
/// synchronization."
///
/// # Safety
///
/// - Changes the HW state of the executing core.
#[inline(always)]
pub unsafe fn local_irq_unmask() {
#[rustfmt::skip]
asm!(
"msr DAIFClr, {arg}",
arg = const daif_bits::IRQ,
options(nomem, nostack, preserves_flags)
);
}
/// Mask IRQs on the executing core.
///
/// # Safety
///
/// - Changes the HW state of the executing core.
#[inline(always)]
pub unsafe fn local_irq_mask() {
#[rustfmt::skip]
asm!(
"msr DAIFSet, {arg}",
arg = const daif_bits::IRQ,
options(nomem, nostack, preserves_flags)
);
}
/// Mask IRQs on the executing core and return the previously saved interrupt mask bits (DAIF).
///
/// # Safety
///
/// - Changes the HW state of the executing core.
#[inline(always)]
pub unsafe fn local_irq_mask_save() -> u32 {
let saved = DAIF.get();
local_irq_mask();
saved
}
/// Restore the interrupt mask bits (DAIF) using the callee's argument.
///
/// # Safety
///
/// - Changes the HW state of the executing core.
/// - No sanity checks on the input.
#[inline(always)]
pub unsafe fn local_irq_restore(saved: u32) {
DAIF.set(saved);
}
/// Print the AArch64 exceptions status.
#[rustfmt::skip]
pub fn print_state() {
use crate::info;
let to_mask_str = |x| -> _ {
if x { "Masked" } else { "Unmasked" }
};
info!(" Debug: {}", to_mask_str(is_masked::<Debug>()));
info!(" SError: {}", to_mask_str(is_masked::<SError>()));
info!(" IRQ: {}", to_mask_str(is_masked::<IRQ>()));
info!(" FIQ: {}", to_mask_str(is_masked::<FIQ>()));
}

@ -0,0 +1,553 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Memory Management Unit Driver.
//!
//! Only 64 KiB granule is supported.
use crate::{
bsp,
memory::{
mmu,
mmu::{
AccessPermissions, Address, AddressType, AttributeFields, MemAttributes, Page,
PageSliceDescriptor, Physical, Virtual,
},
},
synchronization::InitStateLock,
};
use core::convert;
use cortex_a::{barrier, regs::*};
use register::{register_bitfields, InMemoryRegister};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
use mmu::interface::TranslationGranule;
// A table descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-15.
register_bitfields! {u64,
STAGE1_TABLE_DESCRIPTOR [
/// Physical address of the next descriptor.
NEXT_LEVEL_TABLE_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16]
TYPE OFFSET(1) NUMBITS(1) [
Block = 0,
Table = 1
],
VALID OFFSET(0) NUMBITS(1) [
False = 0,
True = 1
]
]
}
// A level 3 page descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-17.
register_bitfields! {u64,
STAGE1_PAGE_DESCRIPTOR [
/// Privileged execute-never.
PXN OFFSET(53) NUMBITS(1) [
False = 0,
True = 1
],
/// Physical address of the next table descriptor (lvl2) or the page descriptor (lvl3).
OUTPUT_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16]
/// Access flag.
AF OFFSET(10) NUMBITS(1) [
False = 0,
True = 1
],
/// Shareability field.
SH OFFSET(8) NUMBITS(2) [
OuterShareable = 0b10,
InnerShareable = 0b11
],
/// Access Permissions.
AP OFFSET(6) NUMBITS(2) [
RW_EL1 = 0b00,
RW_EL1_EL0 = 0b01,
RO_EL1 = 0b10,
RO_EL1_EL0 = 0b11
],
/// Memory attributes index into the MAIR_EL1 register.
AttrIndx OFFSET(2) NUMBITS(3) [],
TYPE OFFSET(1) NUMBITS(1) [
Block = 0,
Table = 1
],
VALID OFFSET(0) NUMBITS(1) [
False = 0,
True = 1
]
]
}
/// A table descriptor for 64 KiB aperture.
///
/// The output points to the next table.
#[derive(Copy, Clone)]
#[repr(transparent)]
struct TableDescriptor(InMemoryRegister<u64, STAGE1_TABLE_DESCRIPTOR::Register>);
/// A page descriptor with 64 KiB aperture.
///
/// The output points to physical memory.
#[derive(Copy, Clone)]
#[repr(transparent)]
struct PageDescriptor(InMemoryRegister<u64, STAGE1_PAGE_DESCRIPTOR::Register>);
#[derive(Copy, Clone)]
enum Granule512MiB {}
trait BaseAddr {
fn phys_base_addr(&self) -> Address<Physical>;
}
/// Constants for indexing the MAIR_EL1.
#[allow(dead_code)]
mod mair {
pub const DEVICE: u64 = 0;
pub const NORMAL: u64 = 1;
}
/// Memory Management Unit type.
struct MemoryManagementUnit;
/// This constant is the power-of-two exponent that defines the virtual address space size.
///
/// Values tested and known to be working:
/// - 30 (1 GiB)
/// - 31 (2 GiB)
/// - 32 (4 GiB)
/// - 33 (8 GiB)
const ADDR_SPACE_SIZE_EXPONENT: usize = 33;
const NUM_LVL2_TABLES: usize = (1 << ADDR_SPACE_SIZE_EXPONENT) >> Granule512MiB::SHIFT;
const T0SZ: u64 = (64 - ADDR_SPACE_SIZE_EXPONENT) as u64;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Big monolithic struct for storing the translation tables. Individual levels must be 64 KiB
/// aligned, hence the "reverse" order of appearance.
#[repr(C)]
#[repr(align(65536))]
pub(in crate::memory::mmu) struct FixedSizeTranslationTable<const NUM_TABLES: usize> {
/// Page descriptors, covering 64 KiB windows per entry.
lvl3: [[PageDescriptor; 8192]; NUM_TABLES],
/// Table descriptors, covering 512 MiB windows.
lvl2: [TableDescriptor; NUM_TABLES],
/// Index of the next free MMIO page.
cur_l3_mmio_index: usize,
/// Have the tables been initialized?
initialized: bool,
}
pub(in crate::memory::mmu) type ArchTranslationTable = FixedSizeTranslationTable<NUM_LVL2_TABLES>;
// Supported translation granules are exported below, so that BSP code can pick between the options.
// This driver only supports 64 KiB at the moment.
#[derive(Copy, Clone)]
/// 64 KiB translation granule.
pub enum Granule64KiB {}
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
/// The translation tables.
///
/// # Safety
///
/// - Supposed to land in `.bss`. Therefore, ensure that all initial member values boil down to "0".
static KERNEL_TABLES: InitStateLock<ArchTranslationTable> =
InitStateLock::new(ArchTranslationTable::new());
static MMU: MemoryManagementUnit = MemoryManagementUnit;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl mmu::interface::TranslationGranule for Granule512MiB {
const SIZE: usize = 512 * 1024 * 1024;
const SHIFT: usize = 29; // log2(SIZE)
}
impl<T, const N: usize> BaseAddr for [T; N] {
fn phys_base_addr(&self) -> Address<Physical> {
// The binary is still identity mapped, so we don't need to convert here.
Address::new(self as *const _ as usize)
}
}
impl convert::From<usize> for TableDescriptor {
fn from(next_lvl_table_addr: usize) -> Self {
let val = InMemoryRegister::<u64, STAGE1_TABLE_DESCRIPTOR::Register>::new(0);
let shifted = next_lvl_table_addr >> Granule64KiB::SHIFT;
val.write(
STAGE1_TABLE_DESCRIPTOR::VALID::True
+ STAGE1_TABLE_DESCRIPTOR::TYPE::Table
+ STAGE1_TABLE_DESCRIPTOR::NEXT_LEVEL_TABLE_ADDR_64KiB.val(shifted as u64),
);
TableDescriptor(val)
}
}
/// Convert the kernel's generic memory attributes to HW-specific attributes of the MMU.
impl convert::From<AttributeFields>
for register::FieldValue<u64, STAGE1_PAGE_DESCRIPTOR::Register>
{
fn from(attribute_fields: AttributeFields) -> Self {
// Memory attributes.
let mut desc = match attribute_fields.mem_attributes {
MemAttributes::CacheableDRAM => {
STAGE1_PAGE_DESCRIPTOR::SH::InnerShareable
+ STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
}
MemAttributes::Device => {
STAGE1_PAGE_DESCRIPTOR::SH::OuterShareable
+ STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
}
};
// Access Permissions.
desc += match attribute_fields.acc_perms {
AccessPermissions::ReadOnly => STAGE1_PAGE_DESCRIPTOR::AP::RO_EL1,
AccessPermissions::ReadWrite => STAGE1_PAGE_DESCRIPTOR::AP::RW_EL1,
};
// Execute Never.
desc += if attribute_fields.execute_never {
STAGE1_PAGE_DESCRIPTOR::PXN::True
} else {
STAGE1_PAGE_DESCRIPTOR::PXN::False
};
desc
}
}
impl PageDescriptor {
/// Create an instance.
fn new(output_addr: *const Page<Physical>, attribute_fields: &AttributeFields) -> Self {
let val = InMemoryRegister::<u64, STAGE1_PAGE_DESCRIPTOR::Register>::new(0);
let shifted = output_addr as u64 >> Granule64KiB::SHIFT;
val.write(
STAGE1_PAGE_DESCRIPTOR::VALID::True
+ STAGE1_PAGE_DESCRIPTOR::AF::True
+ attribute_fields.clone().into()
+ STAGE1_PAGE_DESCRIPTOR::TYPE::Table
+ STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB.val(shifted),
);
Self(val)
}
/// Returns the valid bit.
fn is_valid(&self) -> bool {
self.0.is_set(STAGE1_PAGE_DESCRIPTOR::VALID)
}
}
impl<const NUM_TABLES: usize> FixedSizeTranslationTable<{ NUM_TABLES }> {
// Reserve the last 256 MiB of the address space for MMIO mappings.
const L2_MMIO_START_INDEX: usize = NUM_TABLES - 1;
const L3_MMIO_START_INDEX: usize = 8192 / 2;
/// Create an instance.
pub const fn new() -> Self {
assert!(NUM_TABLES > 0);
Self {
lvl3: [[PageDescriptor(InMemoryRegister::new(0)); 8192]; NUM_TABLES],
lvl2: [TableDescriptor(InMemoryRegister::new(0)); NUM_TABLES],
cur_l3_mmio_index: 0,
initialized: false,
}
}
/// The start address of the table's MMIO range.
#[inline(always)]
fn mmio_start_addr(&self) -> Address<Virtual> {
Address::new(
(Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT)
| (Self::L3_MMIO_START_INDEX << Granule64KiB::SHIFT),
)
}
/// The inclusive end address of the table's MMIO range.
#[inline(always)]
fn mmio_end_addr_inclusive(&self) -> Address<Virtual> {
Address::new(
(Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT)
| (8191 << Granule64KiB::SHIFT)
| (Granule64KiB::SIZE - 1),
)
}
/// Helper to calculate the lvl2 and lvl3 indices from an address.
#[inline(always)]
fn lvl2_lvl3_index_from<ATYPE: AddressType>(
&self,
addr: *const Page<ATYPE>,
) -> Result<(usize, usize), &'static str> {
let lvl2_index = addr as usize >> Granule512MiB::SHIFT;
let lvl3_index = (addr as usize & Granule512MiB::MASK) >> Granule64KiB::SHIFT;
if lvl2_index > (NUM_TABLES - 1) {
return Err("Virtual page is out of bounds of translation table");
}
Ok((lvl2_index, lvl3_index))
}
/// Returns the PageDescriptor corresponding to the supplied Page.
#[inline(always)]
fn page_descriptor_from(
&mut self,
addr: *const Page<Virtual>,
) -> Result<&mut PageDescriptor, &'static str> {
let (lvl2_index, lvl3_index) = self.lvl2_lvl3_index_from(addr)?;
Ok(&mut self.lvl3[lvl2_index][lvl3_index])
}
}
/// Setup function for the MAIR_EL1 register.
fn set_up_mair() {
// Define the memory types being mapped.
MAIR_EL1.write(
// Attribute 1 - Cacheable normal DRAM.
MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc +
MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc +
// Attribute 0 - Device.
MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck,
);
}
/// Configure various settings of stage 1 of the EL1 translation regime.
fn configure_translation_control() {
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
TCR_EL1.write(
TCR_EL1::TBI0::Ignored
+ TCR_EL1::IPS.val(ips)
+ TCR_EL1::TG0::KiB_64
+ TCR_EL1::SH0::Inner
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
+ TCR_EL1::EPD0::EnableTTBR0Walks
+ TCR_EL1::T0SZ.val(T0SZ),
);
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return a guarded reference to the kernel's translation tables.
pub(in crate::memory::mmu) fn kernel_translation_tables(
) -> &'static InitStateLock<ArchTranslationTable> {
&KERNEL_TABLES
}
/// Return a reference to the MMU instance.
pub(in crate::memory::mmu) fn mmu() -> &'static impl mmu::interface::MMU {
&MMU
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
impl mmu::interface::TranslationGranule for Granule64KiB {
const SIZE: usize = 64 * 1024;
const SHIFT: usize = 16; // log2(SIZE)
}
impl<const NUM_TABLES: usize> mmu::interface::TranslationTable
for FixedSizeTranslationTable<{ NUM_TABLES }>
{
unsafe fn init(&mut self) {
if self.initialized {
return;
}
// Populate the l2 entries.
for (lvl2_nr, lvl2_entry) in self.lvl2.iter_mut().enumerate() {
*lvl2_entry = self.lvl3[lvl2_nr].phys_base_addr().into_usize().into();
}
self.cur_l3_mmio_index = Self::L3_MMIO_START_INDEX;
self.initialized = true;
}
fn phys_base_address(&self) -> Address<Physical> {
self.lvl2.phys_base_addr()
}
unsafe fn map_pages_at(
&mut self,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str> {
assert_eq!(self.initialized, true, "Translation tables not initialized");
let p = phys_pages.as_slice();
let v = virt_pages.as_slice();
if p.len() != v.len() {
return Err("Tried to map page slices with unequal sizes");
}
// No work to do for empty slices.
if p.is_empty() {
return Ok(());
}
if p.last().unwrap().as_ptr() >= bsp::memory::mmu::phys_addr_space_end_page() {
return Err("Tried to map outside of physical address space");
}
let iter = p.iter().zip(v.iter());
for (phys_page, virt_page) in iter {
let page_descriptor = self.page_descriptor_from(virt_page.as_ptr())?;
if page_descriptor.is_valid() {
return Err("Virtual page is already mapped");
}
*page_descriptor = PageDescriptor::new(phys_page.as_ptr(), &attr);
}
Ok(())
}
fn next_mmio_virt_page_slice(
&mut self,
num_pages: usize,
) -> Result<PageSliceDescriptor<Virtual>, &'static str> {
assert_eq!(self.initialized, true, "Translation tables not initialized");
if num_pages == 0 {
return Err("num_pages == 0");
}
if (self.cur_l3_mmio_index + num_pages) > 8191 {
return Err("Not enough MMIO space left");
}
let addr = (Self::L2_MMIO_START_INDEX << Granule512MiB::SHIFT)
| (self.cur_l3_mmio_index << Granule64KiB::SHIFT);
self.cur_l3_mmio_index += num_pages;
Ok(PageSliceDescriptor::from_addr(
Address::new(addr),
num_pages,
))
}
fn is_virt_page_slice_mmio(&self, virt_pages: &PageSliceDescriptor<Virtual>) -> bool {
let start_addr = virt_pages.start_addr();
let end_addr_inclusive = virt_pages.end_addr_inclusive();
for i in [start_addr, end_addr_inclusive].iter() {
if (*i >= self.mmio_start_addr()) && (*i <= self.mmio_end_addr_inclusive()) {
return true;
}
}
false
}
}
impl mmu::interface::MMU for MemoryManagementUnit {
unsafe fn enable(
&self,
phys_kernel_table_base_addr: Address<Physical>,
) -> Result<(), &'static str> {
// Fail early if translation granule is not supported. Both RPis support it, though.
if !ID_AA64MMFR0_EL1.matches_all(ID_AA64MMFR0_EL1::TGran64::Supported) {
return Err("Translation granule not supported in HW");
}
// Prepare the memory attribute indirection register.
set_up_mair();
// Set the "Translation Table Base Register".
TTBR0_EL1.set_baddr(phys_kernel_table_base_addr.into_usize() as u64);
configure_translation_control();
// Switch the MMU on.
//
// First, force all previous changes to be seen before the MMU is enabled.
barrier::isb(barrier::SY);
// Enable the MMU and turn on data and instruction caching.
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
// Force MMU init to complete before next instruction.
barrier::isb(barrier::SY);
Ok(())
}
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
pub(in crate::memory::mmu) type MinSizeArchTranslationTable = FixedSizeTranslationTable<1>;
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Check if the size of `struct TableDescriptor` is as expected.
#[kernel_test]
fn size_of_tabledescriptor_equals_64_bit() {
assert_eq!(
core::mem::size_of::<TableDescriptor>(),
core::mem::size_of::<u64>()
);
}
/// Check if the size of `struct PageDescriptor` is as expected.
#[kernel_test]
fn size_of_pagedescriptor_equals_64_bit() {
assert_eq!(
core::mem::size_of::<PageDescriptor>(),
core::mem::size_of::<u64>()
);
}
/// Check if KERNEL_TABLES is in .bss.
#[kernel_test]
fn kernel_tables_in_bss() {
let bss_range = bsp::memory::bss_range();
let kernel_tables_addr = &KERNEL_TABLES as *const _ as usize as *mut u64;
assert!(bss_range.contains(&kernel_tables_addr));
}
}

@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Architectural timer primitives.
use crate::{time, warn};
use core::time::Duration;
use cortex_a::regs::*;
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
const NS_PER_S: u64 = 1_000_000_000;
/// ARMv8 Generic Timer.
struct GenericTimer;
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static TIME_MANAGER: GenericTimer = GenericTimer;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return a reference to the time manager.
pub fn time_manager() -> &'static impl time::interface::TimeManager {
&TIME_MANAGER
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
impl time::interface::TimeManager for GenericTimer {
fn resolution(&self) -> Duration {
Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64))
}
fn uptime(&self) -> Duration {
let frq: u64 = CNTFRQ_EL0.get() as u64;
let current_count: u64 = CNTPCT_EL0.get() * NS_PER_S;
Duration::from_nanos(current_count / frq)
}
fn spin_for(&self, duration: Duration) {
// Instantly return on zero.
if duration.as_nanos() == 0 {
return;
}
// Calculate the register compare value.
let frq = CNTFRQ_EL0.get() as u64;
let x = match frq.checked_mul(duration.as_nanos() as u64) {
None => {
warn!("Spin duration too long, skipping");
return;
}
Some(val) => val,
};
let tval = x / NS_PER_S;
// Check if it is within supported bounds.
let warn: Option<&str> = if tval == 0 {
Some("smaller")
} else if tval > u32::max_value().into() {
Some("bigger")
} else {
None
};
if let Some(w) = warn {
warn!(
"Spin duration {} than architecturally supported, skipping",
w
);
return;
}
// Set the compare value register.
CNTP_TVAL_EL0.set(tval as u32);
// Kick off the counting. // Disable timer interrupt.
CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET);
// ISTATUS will be '1' when cval ticks have passed. Busy-check it.
while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {}
// Disable counting again.
CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR);
}
}

@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Conditional re-exporting of Board Support Packages.
mod device_driver;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod raspberrypi;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
pub use raspberrypi::*;

@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Device driver.
#[cfg(feature = "bsp_rpi4")]
mod arm;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
mod bcm;
mod common;
#[cfg(feature = "bsp_rpi4")]
pub use arm::*;
#[cfg(any(feature = "bsp_rpi3", feature = "bsp_rpi4"))]
pub use bcm::*;

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! ARM driver top level.
pub mod gicv2;
pub use gicv2::*;

@ -0,0 +1,254 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! GICv2 Driver - ARM Generic Interrupt Controller v2.
//!
//! The following is a collection of excerpts with useful information from
//! - `Programmer's Guide for ARMv8-A`
//! - `ARM Generic Interrupt Controller Architecture Specification`
//!
//! # Programmer's Guide - 10.6.1 Configuration
//!
//! The GIC is accessed as a memory-mapped peripheral.
//!
//! All cores can access the common Distributor, but the CPU interface is banked, that is, each core
//! uses the same address to access its own private CPU interface.
//!
//! It is not possible for a core to access the CPU interface of another core.
//!
//! # Architecture Specification - 10.6.2 Initialization
//!
//! Both the Distributor and the CPU interfaces are disabled at reset. The GIC must be initialized
//! after reset before it can deliver interrupts to the core.
//!
//! In the Distributor, software must configure the priority, target, security and enable individual
//! interrupts. The Distributor must subsequently be enabled through its control register
//! (GICD_CTLR). For each CPU interface, software must program the priority mask and preemption
//! settings.
//!
//! Each CPU interface block itself must be enabled through its control register (GICD_CTLR). This
//! prepares the GIC to deliver interrupts to the core.
//!
//! Before interrupts are expected in the core, software prepares the core to take interrupts by
//! setting a valid interrupt vector in the vector table, and clearing interrupt mask bits in
//! PSTATE, and setting the routing controls.
//!
//! The entire interrupt mechanism in the system can be disabled by disabling the Distributor.
//! Interrupt delivery to an individual core can be disabled by disabling its CPU interface.
//! Individual interrupts can also be disabled (or enabled) in the distributor.
//!
//! For an interrupt to reach the core, the individual interrupt, Distributor and CPU interface must
//! all be enabled. The interrupt also needs to be of sufficient priority, that is, higher than the
//! core's priority mask.
//!
//! # Architecture Specification - 1.4.2 Interrupt types
//!
//! - Peripheral interrupt
//! - Private Peripheral Interrupt (PPI)
//! - This is a peripheral interrupt that is specific to a single processor.
//! - Shared Peripheral Interrupt (SPI)
//! - This is a peripheral interrupt that the Distributor can route to any of a specified
//! combination of processors.
//!
//! - Software-generated interrupt (SGI)
//! - This is an interrupt generated by software writing to a GICD_SGIR register in the GIC. The
//! system uses SGIs for interprocessor communication.
//! - An SGI has edge-triggered properties. The software triggering of the interrupt is
//! equivalent to the edge transition of the interrupt request signal.
//! - When an SGI occurs in a multiprocessor implementation, the CPUID field in the Interrupt
//! Acknowledge Register, GICC_IAR, or the Aliased Interrupt Acknowledge Register, GICC_AIAR,
//! identifies the processor that requested the interrupt.
//!
//! # Architecture Specification - 2.2.1 Interrupt IDs
//!
//! Interrupts from sources are identified using ID numbers. Each CPU interface can see up to 1020
//! interrupts. The banking of SPIs and PPIs increases the total number of interrupts supported by
//! the Distributor.
//!
//! The GIC assigns interrupt ID numbers ID0-ID1019 as follows:
//! - Interrupt numbers 32..1019 are used for SPIs.
//! - Interrupt numbers 0..31 are used for interrupts that are private to a CPU interface. These
//! interrupts are banked in the Distributor.
//! - A banked interrupt is one where the Distributor can have multiple interrupts with the
//! same ID. A banked interrupt is identified uniquely by its ID number and its associated
//! CPU interface number. Of the banked interrupt IDs:
//! - 00..15 SGIs
//! - 16..31 PPIs
mod gicc;
mod gicd;
use crate::{
bsp, cpu, driver, exception, memory, memory::mmu::Physical, synchronization,
synchronization::InitStateLock,
};
use core::sync::atomic::{AtomicBool, Ordering};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
type HandlerTable = [Option<exception::asynchronous::IRQDescriptor>; GICv2::NUM_IRQS];
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Used for the associated type of trait [`exception::asynchronous::interface::IRQManager`].
pub type IRQNumber = exception::asynchronous::IRQNumber<{ GICv2::MAX_IRQ_NUMBER }>;
/// Representation of the GIC.
pub struct GICv2 {
gicd_phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
gicc_phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
/// The Distributor.
gicd: gicd::GICD,
/// The CPU Interface.
gicc: gicc::GICC,
/// Have the MMIO regions been remapped yet?
is_mmio_remapped: AtomicBool,
/// Stores registered IRQ handlers. Writable only during kernel init. RO afterwards.
handler_table: InitStateLock<HandlerTable>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl GICv2 {
const MAX_IRQ_NUMBER: usize = 300; // Normally 1019, but keep it lower to save some space.
const NUM_IRQS: usize = Self::MAX_IRQ_NUMBER + 1;
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide correct MMIO descriptors.
pub const unsafe fn new(
gicd_phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
gicc_phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
) -> Self {
Self {
gicd_phys_mmio_descriptor,
gicc_phys_mmio_descriptor,
gicd: gicd::GICD::new(gicd_phys_mmio_descriptor.start_addr().into_usize()),
gicc: gicc::GICC::new(gicc_phys_mmio_descriptor.start_addr().into_usize()),
is_mmio_remapped: AtomicBool::new(false),
handler_table: InitStateLock::new([None; Self::NUM_IRQS]),
}
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use synchronization::interface::ReadWriteEx;
impl driver::interface::DeviceDriver for GICv2 {
fn compatible(&self) -> &'static str {
"GICv2 (ARM Generic Interrupt Controller v2)"
}
unsafe fn init(&self) -> Result<(), &'static str> {
let remapped = self.is_mmio_remapped.load(Ordering::Relaxed);
if !remapped {
let mut virt_addr;
// GICD
virt_addr = memory::mmu::kernel_map_mmio("GICD", &self.gicd_phys_mmio_descriptor)?;
self.gicd.set_mmio(virt_addr.into_usize());
// GICC
virt_addr = memory::mmu::kernel_map_mmio("GICC", &self.gicc_phys_mmio_descriptor)?;
self.gicc.set_mmio(virt_addr.into_usize());
// Conclude remapping.
self.is_mmio_remapped.store(true, Ordering::Relaxed);
}
if cpu::smp::core_id::<usize>() == bsp::cpu::BOOT_CORE_ID {
self.gicd.boot_core_init();
}
self.gicc.priority_accept_all();
self.gicc.enable();
Ok(())
}
}
impl exception::asynchronous::interface::IRQManager for GICv2 {
type IRQNumberType = IRQNumber;
fn register_handler(
&self,
irq_number: Self::IRQNumberType,
descriptor: exception::asynchronous::IRQDescriptor,
) -> Result<(), &'static str> {
let mut r = &self.handler_table;
r.write(|table| {
let irq_number = irq_number.get();
if table[irq_number].is_some() {
return Err("IRQ handler already registered");
}
table[irq_number] = Some(descriptor);
Ok(())
})
}
fn enable(&self, irq_number: Self::IRQNumberType) {
self.gicd.enable(irq_number);
}
fn handle_pending_irqs<'irq_context>(
&'irq_context self,
ic: &exception::asynchronous::IRQContext<'irq_context>,
) {
// Extract the highest priority pending IRQ number from the Interrupt Acknowledge Register
// (IAR).
let irq_number = self.gicc.pending_irq_number(ic);
// Guard against spurious interrupts.
if irq_number > GICv2::MAX_IRQ_NUMBER {
return;
}
// Call the IRQ handler. Panic if there is none.
let mut r = &self.handler_table;
r.read(|table| {
match table[irq_number] {
None => panic!("No handler registered for IRQ {}", irq_number),
Some(descriptor) => {
// Call the IRQ handler. Panics on failure.
descriptor.handler.handle().expect("Error handling IRQ");
}
}
});
// Signal completion of handling.
self.gicc.mark_comleted(irq_number as u32, ic);
}
fn print_handler(&self) {
use crate::info;
info!(" Peripheral handler:");
let mut r = &self.handler_table;
r.read(|table| {
for (i, opt) in table.iter().skip(32).enumerate() {
if let Some(handler) = opt {
info!(" {: >3}. {}", i + 32, handler.name);
}
}
});
}
}

@ -0,0 +1,155 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! GICC Driver - GIC CPU interface.
use crate::{
bsp::device_driver::common::MMIODerefWrapper, exception, synchronization::InitStateLock,
};
use register::{mmio::*, register_bitfields, register_structs};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
register_bitfields! {
u32,
/// CPU Interface Control Register
CTLR [
Enable OFFSET(0) NUMBITS(1) []
],
/// Interrupt Priority Mask Register
PMR [
Priority OFFSET(0) NUMBITS(8) []
],
/// Interrupt Acknowledge Register
IAR [
InterruptID OFFSET(0) NUMBITS(10) []
],
/// End of Interrupt Register
EOIR [
EOIINTID OFFSET(0) NUMBITS(10) []
]
}
register_structs! {
#[allow(non_snake_case)]
pub RegisterBlock {
(0x000 => CTLR: ReadWrite<u32, CTLR::Register>),
(0x004 => PMR: ReadWrite<u32, PMR::Register>),
(0x008 => _reserved1),
(0x00C => IAR: ReadWrite<u32, IAR::Register>),
(0x010 => EOIR: ReadWrite<u32, EOIR::Register>),
(0x014 => @END),
}
}
/// Abstraction for the associated MMIO registers.
type Registers = MMIODerefWrapper<RegisterBlock>;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Representation of the GIC CPU interface.
pub struct GICC {
registers: InitStateLock<Registers>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use crate::synchronization::interface::ReadWriteEx;
impl GICC {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub const unsafe fn new(mmio_start_addr: usize) -> Self {
Self {
registers: InitStateLock::new(Registers::new(mmio_start_addr)),
}
}
pub unsafe fn set_mmio(&self, new_mmio_start_addr: usize) {
let mut r = &self.registers;
r.write(|regs| *regs = Registers::new(new_mmio_start_addr));
}
/// Accept interrupts of any priority.
///
/// Quoting the GICv2 Architecture Specification:
///
/// "Writing 255 to the GICC_PMR always sets it to the largest supported priority field
/// value."
///
/// # Safety
///
/// - GICC MMIO registers are banked per CPU core. It is therefore safe to have `&self` instead
/// of `&mut self`.
pub fn priority_accept_all(&self) {
let mut r = &self.registers;
r.read(|regs| {
regs.PMR.write(PMR::Priority.val(255)); // Comment in arch spec.
});
}
/// Enable the interface - start accepting IRQs.
///
/// # Safety
///
/// - GICC MMIO registers are banked per CPU core. It is therefore safe to have `&self` instead
/// of `&mut self`.
pub fn enable(&self) {
let mut r = &self.registers;
r.read(|regs| {
regs.CTLR.write(CTLR::Enable::SET);
});
}
/// Extract the number of the highest-priority pending IRQ.
///
/// Can only be called from IRQ context, which is ensured by taking an `IRQContext` token.
///
/// # Safety
///
/// - GICC MMIO registers are banked per CPU core. It is therefore safe to have `&self` instead
/// of `&mut self`.
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn pending_irq_number<'irq_context>(
&self,
_ic: &exception::asynchronous::IRQContext<'irq_context>,
) -> usize {
let mut r = &self.registers;
r.read(|regs| regs.IAR.read(IAR::InterruptID) as usize)
}
/// Complete handling of the currently active IRQ.
///
/// Can only be called from IRQ context, which is ensured by taking an `IRQContext` token.
///
/// To be called after `pending_irq_number()`.
///
/// # Safety
///
/// - GICC MMIO registers are banked per CPU core. It is therefore safe to have `&self` instead
/// of `&mut self`.
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn mark_comleted<'irq_context>(
&self,
irq_number: u32,
_ic: &exception::asynchronous::IRQContext<'irq_context>,
) {
let mut r = &self.registers;
r.read(|regs| {
regs.EOIR.write(EOIR::EOIINTID.val(irq_number));
});
}
}

@ -0,0 +1,211 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! GICD Driver - GIC Distributor.
//!
//! # Glossary
//! - SPI - Shared Peripheral Interrupt.
use crate::{
bsp::device_driver::common::MMIODerefWrapper,
state, synchronization,
synchronization::{IRQSafeNullLock, InitStateLock},
};
use register::{mmio::*, register_bitfields, register_structs};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
register_bitfields! {
u32,
/// Distributor Control Register
CTLR [
Enable OFFSET(0) NUMBITS(1) []
],
/// Interrupt Controller Type Register
TYPER [
ITLinesNumber OFFSET(0) NUMBITS(5) []
],
/// Interrupt Processor Targets Registers
ITARGETSR [
Offset3 OFFSET(24) NUMBITS(8) [],
Offset2 OFFSET(16) NUMBITS(8) [],
Offset1 OFFSET(8) NUMBITS(8) [],
Offset0 OFFSET(0) NUMBITS(8) []
]
}
register_structs! {
#[allow(non_snake_case)]
SharedRegisterBlock {
(0x000 => CTLR: ReadWrite<u32, CTLR::Register>),
(0x004 => TYPER: ReadOnly<u32, TYPER::Register>),
(0x008 => _reserved1),
(0x104 => ISENABLER: [ReadWrite<u32>; 31]),
(0x108 => _reserved2),
(0x820 => ITARGETSR: [ReadWrite<u32, ITARGETSR::Register>; 248]),
(0x824 => @END),
}
}
register_structs! {
#[allow(non_snake_case)]
BankedRegisterBlock {
(0x000 => _reserved1),
(0x100 => ISENABLER: ReadWrite<u32>),
(0x104 => _reserved2),
(0x800 => ITARGETSR: [ReadOnly<u32, ITARGETSR::Register>; 8]),
(0x804 => @END),
}
}
/// Abstraction for the non-banked parts of the associated MMIO registers.
type SharedRegisters = MMIODerefWrapper<SharedRegisterBlock>;
/// Abstraction for the banked parts of the associated MMIO registers.
type BankedRegisters = MMIODerefWrapper<BankedRegisterBlock>;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Representation of the GIC Distributor.
pub struct GICD {
/// Access to shared registers is guarded with a lock.
shared_registers: IRQSafeNullLock<SharedRegisters>,
/// Access to banked registers is unguarded.
banked_registers: InitStateLock<BankedRegisters>,
}
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl SharedRegisters {
/// Return the number of IRQs that this HW implements.
#[inline(always)]
fn num_irqs(&mut self) -> usize {
// Query number of implemented IRQs.
//
// Refer to GICv2 Architecture Specification, Section 4.3.2.
((self.TYPER.read(TYPER::ITLinesNumber) as usize) + 1) * 32
}
/// Return a slice of the implemented ITARGETSR.
#[inline(always)]
fn implemented_itargets_slice(&mut self) -> &[ReadWrite<u32, ITARGETSR::Register>] {
assert!(self.num_irqs() >= 36);
// Calculate the max index of the shared ITARGETSR array.
//
// The first 32 IRQs are private, so not included in `shared_registers`. Each ITARGETS
// register has four entries, so shift right by two. Subtract one because we start
// counting at zero.
let spi_itargetsr_max_index = ((self.num_irqs() - 32) >> 2) - 1;
// Rust automatically inserts slice range sanity check, i.e. max >= min.
&self.ITARGETSR[0..spi_itargetsr_max_index]
}
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use crate::synchronization::interface::ReadWriteEx;
use synchronization::interface::Mutex;
impl GICD {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub const unsafe fn new(mmio_start_addr: usize) -> Self {
Self {
shared_registers: IRQSafeNullLock::new(SharedRegisters::new(mmio_start_addr)),
banked_registers: InitStateLock::new(BankedRegisters::new(mmio_start_addr)),
}
}
pub unsafe fn set_mmio(&self, new_mmio_start_addr: usize) {
let mut r = &self.shared_registers;
r.lock(|regs| *regs = SharedRegisters::new(new_mmio_start_addr));
let mut r = &self.banked_registers;
r.write(|regs| *regs = BankedRegisters::new(new_mmio_start_addr));
}
/// Use a banked ITARGETSR to retrieve the executing core's GIC target mask.
///
/// Quoting the GICv2 Architecture Specification:
///
/// "GICD_ITARGETSR0 to GICD_ITARGETSR7 are read-only, and each field returns a value that
/// corresponds only to the processor reading the register."
fn local_gic_target_mask(&self) -> u32 {
let mut r = &self.banked_registers;
r.read(|regs| regs.ITARGETSR[0].read(ITARGETSR::Offset0))
}
/// Route all SPIs to the boot core and enable the distributor.
pub fn boot_core_init(&self) {
assert!(
state::state_manager().is_init(),
"Only allowed during kernel init phase"
);
// Target all SPIs to the boot core only.
let mask = self.local_gic_target_mask();
let mut r = &self.shared_registers;
r.lock(|regs| {
for i in regs.implemented_itargets_slice().iter() {
i.write(
ITARGETSR::Offset3.val(mask)
+ ITARGETSR::Offset2.val(mask)
+ ITARGETSR::Offset1.val(mask)
+ ITARGETSR::Offset0.val(mask),
);
}
regs.CTLR.write(CTLR::Enable::SET);
});
}
/// Enable an interrupt.
pub fn enable(&self, irq_num: super::IRQNumber) {
let irq_num = irq_num.get();
// Each bit in the u32 enable register corresponds to one IRQ number. Shift right by 5
// (division by 32) and arrive at the index for the respective ISENABLER[i].
let enable_reg_index = irq_num >> 5;
let enable_bit: u32 = 1u32 << (irq_num % 32);
// Check if we are handling a private or shared IRQ.
match irq_num {
// Private.
0..=31 => {
let mut r = &self.banked_registers;
r.read(|regs| {
let enable_reg = &regs.ISENABLER;
enable_reg.set(enable_reg.get() | enable_bit);
})
}
// Shared.
_ => {
let enable_reg_index_shared = enable_reg_index - 1;
let mut r = &self.shared_registers;
r.lock(|regs| {
let enable_reg = &regs.ISENABLER[enable_reg_index_shared];
enable_reg.set(enable_reg.get() | enable_bit);
});
}
}
}
}

@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BCM driver top level.
mod bcm2xxx_gpio;
#[cfg(feature = "bsp_rpi3")]
mod bcm2xxx_interrupt_controller;
mod bcm2xxx_pl011_uart;
pub use bcm2xxx_gpio::*;
#[cfg(feature = "bsp_rpi3")]
pub use bcm2xxx_interrupt_controller::*;
pub use bcm2xxx_pl011_uart::*;

@ -0,0 +1,204 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! GPIO Driver.
use crate::{
bsp::device_driver::common::MMIODerefWrapper, cpu, driver, memory, memory::mmu::Physical,
synchronization, synchronization::IRQSafeNullLock,
};
use core::sync::atomic::{AtomicUsize, Ordering};
use register::{mmio::*, register_bitfields, register_structs};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
// 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
]
]
}
register_structs! {
#[allow(non_snake_case)]
RegisterBlock {
(0x00 => GPFSEL0: ReadWrite<u32>),
(0x04 => GPFSEL1: ReadWrite<u32, GPFSEL1::Register>),
(0x08 => GPFSEL2: ReadWrite<u32>),
(0x0C => GPFSEL3: ReadWrite<u32>),
(0x10 => GPFSEL4: ReadWrite<u32>),
(0x14 => GPFSEL5: ReadWrite<u32>),
(0x18 => _reserved1),
(0x94 => GPPUD: ReadWrite<u32>),
(0x98 => GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>),
(0x9C => GPPUDCLK1: ReadWrite<u32>),
(0xA0 => @END),
}
}
/// Abstraction for the associated MMIO registers.
type Registers = MMIODerefWrapper<RegisterBlock>;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
pub struct GPIOInner {
registers: Registers,
}
// Export the inner struct so that BSPs can use it for the panic handler.
pub use GPIOInner as PanicGPIO;
/// Representation of the GPIO HW.
pub struct GPIO {
phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
virt_mmio_start_addr: AtomicUsize,
inner: IRQSafeNullLock<GPIOInner>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl GPIOInner {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub const unsafe fn new(mmio_start_addr: usize) -> Self {
Self {
registers: Registers::new(mmio_start_addr),
}
}
/// Init code.
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub unsafe fn init(&mut self, new_mmio_start_addr: Option<usize>) -> Result<(), &'static str> {
if let Some(addr) = new_mmio_start_addr {
self.registers = Registers::new(addr);
}
Ok(())
}
/// Map PL011 UART as standard output.
///
/// TX to pin 14
/// RX to pin 15
pub fn map_pl011_uart(&mut self) {
// Map to pins.
self.registers
.GPFSEL1
.modify(GPFSEL1::FSEL14::AltFunc0 + GPFSEL1::FSEL15::AltFunc0);
// Enable pins 14 and 15.
self.registers.GPPUD.set(0);
cpu::spin_for_cycles(150);
self.registers
.GPPUDCLK0
.write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock);
cpu::spin_for_cycles(150);
self.registers.GPPUDCLK0.set(0);
}
}
impl GPIO {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide correct MMIO descriptors.
pub const unsafe fn new(phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>) -> Self {
Self {
phys_mmio_descriptor,
virt_mmio_start_addr: AtomicUsize::new(0),
inner: IRQSafeNullLock::new(GPIOInner::new(
phys_mmio_descriptor.start_addr().into_usize(),
)),
}
}
/// Concurrency safe version of `GPIOInner.map_pl011_uart()`
pub fn map_pl011_uart(&self) {
let mut r = &self.inner;
r.lock(|inner| inner.map_pl011_uart())
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use synchronization::interface::Mutex;
impl driver::interface::DeviceDriver for GPIO {
fn compatible(&self) -> &'static str {
"BCM GPIO"
}
unsafe fn init(&self) -> Result<(), &'static str> {
let virt_addr =
memory::mmu::kernel_map_mmio(self.compatible(), &self.phys_mmio_descriptor)?;
let mut r = &self.inner;
r.lock(|inner| inner.init(Some(virt_addr.into_usize())))?;
self.virt_mmio_start_addr
.store(virt_addr.into_usize(), Ordering::Relaxed);
Ok(())
}
fn virt_mmio_start_addr(&self) -> Option<usize> {
let addr = self.virt_mmio_start_addr.load(Ordering::Relaxed);
if addr == 0 {
return None;
}
Some(addr)
}
}

@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Interrupt Controller Driver.
mod peripheral_ic;
use crate::{driver, exception, memory, memory::mmu::Physical};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// Wrapper struct for a bitmask indicating pending IRQ numbers.
struct PendingIRQs {
bitmask: u64,
}
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
pub type LocalIRQ =
exception::asynchronous::IRQNumber<{ InterruptController::MAX_LOCAL_IRQ_NUMBER }>;
pub type PeripheralIRQ =
exception::asynchronous::IRQNumber<{ InterruptController::MAX_PERIPHERAL_IRQ_NUMBER }>;
/// Used for the associated type of trait [`exception::asynchronous::interface::IRQManager`].
#[derive(Copy, Clone)]
pub enum IRQNumber {
Local(LocalIRQ),
Peripheral(PeripheralIRQ),
}
/// Representation of the Interrupt Controller.
pub struct InterruptController {
periph: peripheral_ic::PeripheralIC,
}
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl PendingIRQs {
pub fn new(bitmask: u64) -> Self {
Self { bitmask }
}
}
impl Iterator for PendingIRQs {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
use core::intrinsics::cttz;
let next = cttz(self.bitmask);
if next == 64 {
return None;
}
self.bitmask &= !(1 << next);
Some(next as usize)
}
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl InterruptController {
const MAX_LOCAL_IRQ_NUMBER: usize = 11;
const MAX_PERIPHERAL_IRQ_NUMBER: usize = 63;
const NUM_PERIPHERAL_IRQS: usize = Self::MAX_PERIPHERAL_IRQ_NUMBER + 1;
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide correct MMIO descriptors.
pub const unsafe fn new(
_phys_local_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
phys_periph_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
) -> Self {
Self {
periph: peripheral_ic::PeripheralIC::new(phys_periph_mmio_descriptor),
}
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
impl driver::interface::DeviceDriver for InterruptController {
fn compatible(&self) -> &'static str {
"BCM Interrupt Controller"
}
unsafe fn init(&self) -> Result<(), &'static str> {
self.periph.init()
}
}
impl exception::asynchronous::interface::IRQManager for InterruptController {
type IRQNumberType = IRQNumber;
fn register_handler(
&self,
irq: Self::IRQNumberType,
descriptor: exception::asynchronous::IRQDescriptor,
) -> Result<(), &'static str> {
match irq {
IRQNumber::Local(_) => unimplemented!("Local IRQ controller not implemented."),
IRQNumber::Peripheral(pirq) => self.periph.register_handler(pirq, descriptor),
}
}
fn enable(&self, irq: Self::IRQNumberType) {
match irq {
IRQNumber::Local(_) => unimplemented!("Local IRQ controller not implemented."),
IRQNumber::Peripheral(pirq) => self.periph.enable(pirq),
}
}
fn handle_pending_irqs<'irq_context>(
&'irq_context self,
ic: &exception::asynchronous::IRQContext<'irq_context>,
) {
// It can only be a peripheral IRQ pending because enable() does not support local IRQs yet.
self.periph.handle_pending_irqs(ic)
}
fn print_handler(&self) {
self.periph.print_handler();
}
}

@ -0,0 +1,197 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Peripheral Interrupt Controller Driver.
use super::{InterruptController, PendingIRQs, PeripheralIRQ};
use crate::{
bsp::device_driver::common::MMIODerefWrapper,
driver, exception, memory,
memory::mmu::Physical,
synchronization,
synchronization::{IRQSafeNullLock, InitStateLock},
};
use register::{mmio::*, register_structs};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
register_structs! {
#[allow(non_snake_case)]
WORegisterBlock {
(0x00 => _reserved1),
(0x10 => ENABLE_1: WriteOnly<u32>),
(0x14 => ENABLE_2: WriteOnly<u32>),
(0x24 => @END),
}
}
register_structs! {
#[allow(non_snake_case)]
RORegisterBlock {
(0x00 => _reserved1),
(0x04 => PENDING_1: ReadOnly<u32>),
(0x08 => PENDING_2: ReadOnly<u32>),
(0x0c => @END),
}
}
/// Abstraction for the WriteOnly parts of the associated MMIO registers.
type WriteOnlyRegisters = MMIODerefWrapper<WORegisterBlock>;
/// Abstraction for the ReadOnly parts of the associated MMIO registers.
type ReadOnlyRegisters = MMIODerefWrapper<RORegisterBlock>;
type HandlerTable =
[Option<exception::asynchronous::IRQDescriptor>; InterruptController::NUM_PERIPHERAL_IRQS];
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Representation of the peripheral interrupt controller.
pub struct PeripheralIC {
phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
/// Access to write registers is guarded with a lock.
wo_registers: IRQSafeNullLock<WriteOnlyRegisters>,
/// Register read access is unguarded.
ro_registers: InitStateLock<ReadOnlyRegisters>,
/// Stores registered IRQ handlers. Writable only during kernel init. RO afterwards.
handler_table: InitStateLock<HandlerTable>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl PeripheralIC {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide correct MMIO descriptors.
pub const unsafe fn new(phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>) -> Self {
let addr = phys_mmio_descriptor.start_addr().into_usize();
Self {
phys_mmio_descriptor,
wo_registers: IRQSafeNullLock::new(WriteOnlyRegisters::new(addr)),
ro_registers: InitStateLock::new(ReadOnlyRegisters::new(addr)),
handler_table: InitStateLock::new([None; InterruptController::NUM_PERIPHERAL_IRQS]),
}
}
/// Query the list of pending IRQs.
fn pending_irqs(&self) -> PendingIRQs {
let mut r = &self.ro_registers;
r.read(|regs| {
let pending_mask: u64 =
(u64::from(regs.PENDING_2.get()) << 32) | u64::from(regs.PENDING_1.get());
PendingIRQs::new(pending_mask)
})
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use synchronization::interface::{Mutex, ReadWriteEx};
impl driver::interface::DeviceDriver for PeripheralIC {
fn compatible(&self) -> &'static str {
"BCM Peripheral Interrupt Controller"
}
unsafe fn init(&self) -> Result<(), &'static str> {
let virt_addr =
memory::mmu::kernel_map_mmio(self.compatible(), &self.phys_mmio_descriptor)?
.into_usize();
let mut r = &self.wo_registers;
r.lock(|regs| *regs = WriteOnlyRegisters::new(virt_addr));
let mut r = &self.ro_registers;
r.write(|regs| *regs = ReadOnlyRegisters::new(virt_addr));
Ok(())
}
}
impl exception::asynchronous::interface::IRQManager for PeripheralIC {
type IRQNumberType = PeripheralIRQ;
fn register_handler(
&self,
irq: Self::IRQNumberType,
descriptor: exception::asynchronous::IRQDescriptor,
) -> Result<(), &'static str> {
let mut r = &self.handler_table;
r.write(|table| {
let irq_number = irq.get();
if table[irq_number].is_some() {
return Err("IRQ handler already registered");
}
table[irq_number] = Some(descriptor);
Ok(())
})
}
fn enable(&self, irq: Self::IRQNumberType) {
let mut r = &self.wo_registers;
r.lock(|regs| {
let enable_reg = if irq.get() <= 31 {
&regs.ENABLE_1
} else {
&regs.ENABLE_2
};
let enable_bit: u32 = 1 << (irq.get() % 32);
// Writing a 1 to a bit will set the corresponding IRQ enable bit. All other IRQ enable
// bits are unaffected. So we don't need read and OR'ing here.
enable_reg.set(enable_bit);
});
}
fn handle_pending_irqs<'irq_context>(
&'irq_context self,
_ic: &exception::asynchronous::IRQContext<'irq_context>,
) {
let mut r = &self.handler_table;
r.read(|table| {
for irq_number in self.pending_irqs() {
match table[irq_number] {
None => panic!("No handler registered for IRQ {}", irq_number),
Some(descriptor) => {
// Call the IRQ handler. Panics on failure.
descriptor.handler.handle().expect("Error handling IRQ");
}
}
}
})
}
fn print_handler(&self) {
use crate::info;
info!(" Peripheral handler:");
let mut r = &self.handler_table;
r.read(|table| {
for (i, opt) in table.iter().enumerate() {
if let Some(handler) = opt {
info!(" {: >3}. {}", i, handler.name);
}
}
});
}
}

@ -0,0 +1,477 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! PL011 UART driver.
use crate::{
bsp, bsp::device_driver::common::MMIODerefWrapper, console, cpu, driver, exception, memory,
memory::mmu::Physical, synchronization, synchronization::IRQSafeNullLock,
};
use core::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
};
use register::{mmio::*, register_bitfields, register_structs};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
// 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
]
],
/// Interrupt FIFO Level Select Register
IFLS [
/// Receive interrupt FIFO level select. The trigger points for the receive interrupt are as
/// follows.
RXIFLSEL OFFSET(3) NUMBITS(5) [
OneEigth = 0b000,
OneQuarter = 0b001,
OneHalf = 0b010,
ThreeQuarters = 0b011,
SevenEights = 0b100
]
],
/// Interrupt Mask Set Clear Register
IMSC [
/// Receive timeout interrupt mask. A read returns the current mask for the UARTRTINTR
/// interrupt. On a write of 1, the mask of the interrupt is set. A write of 0 clears the
/// mask.
RTIM OFFSET(6) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Receive interrupt mask. A read returns the current mask for the UARTRXINTR interrupt. On
/// a write of 1, the mask of the interrupt is set. A write of 0 clears the mask.
RXIM OFFSET(4) NUMBITS(1) [
Disabled = 0,
Enabled = 1
]
],
/// Masked Interrupt Status Register
MIS [
/// Receive timeout masked interrupt status. Returns the masked interrupt state of the
/// UARTRTINTR interrupt.
RTMIS OFFSET(6) NUMBITS(1) [],
/// Receive masked interrupt status. Returns the masked interrupt state of the UARTRXINTR
/// interrupt.
RXMIS OFFSET(4) NUMBITS(1) []
],
/// Interrupt Clear Register
ICR [
/// Meta field for all pending interrupts
ALL OFFSET(0) NUMBITS(11) []
]
}
register_structs! {
#[allow(non_snake_case)]
pub RegisterBlock {
(0x00 => DR: ReadWrite<u32>),
(0x04 => _reserved1),
(0x18 => FR: ReadOnly<u32, FR::Register>),
(0x1c => _reserved2),
(0x24 => IBRD: WriteOnly<u32, IBRD::Register>),
(0x28 => FBRD: WriteOnly<u32, FBRD::Register>),
(0x2c => LCRH: WriteOnly<u32, LCRH::Register>),
(0x30 => CR: WriteOnly<u32, CR::Register>),
(0x34 => IFLS: ReadWrite<u32, IFLS::Register>),
(0x38 => IMSC: ReadWrite<u32, IMSC::Register>),
(0x3C => _reserved3),
(0x40 => MIS: ReadOnly<u32, MIS::Register>),
(0x44 => ICR: WriteOnly<u32, ICR::Register>),
(0x48 => @END),
}
}
/// Abstraction for the associated MMIO registers.
type Registers = MMIODerefWrapper<RegisterBlock>;
#[derive(PartialEq)]
enum BlockingMode {
Blocking,
NonBlocking,
}
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
pub struct PL011UartInner {
registers: Registers,
chars_written: usize,
chars_read: usize,
}
// Export the inner struct so that BSPs can use it for the panic handler.
pub use PL011UartInner as PanicUart;
/// Representation of the UART.
pub struct PL011Uart {
phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
virt_mmio_start_addr: AtomicUsize,
inner: IRQSafeNullLock<PL011UartInner>,
irq_number: bsp::device_driver::IRQNumber,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl PL011UartInner {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub const unsafe fn new(mmio_start_addr: usize) -> Self {
Self {
registers: Registers::new(mmio_start_addr),
chars_written: 0,
chars_read: 0,
}
}
/// Set up baud rate and characteristics.
///
/// The calculation for the BRD given a target rate of 2300400 and a clock set to 48 MHz is:
/// `(48_000_000/16)/230400 = 13,02083`. `13` goes to the `IBRD` (integer field). The `FBRD`
/// (fractional field) is only 6 bits so `0,0208*64 = 1,3312 rounded to 1` will give the best
/// approximation we can get. A 5 % error margin is acceptable for UART and we're now at 0,01 %.
///
/// This results in 8N1 and 230400 baud (we set the clock to 48 MHz in config.txt).
///
/// # Safety
///
/// - The user must ensure to provide a correct MMIO start address.
pub unsafe fn init(&mut self, new_mmio_start_addr: Option<usize>) -> Result<(), &'static str> {
if let Some(addr) = new_mmio_start_addr {
self.registers = Registers::new(addr);
}
// Turn it off temporarily.
self.registers.CR.set(0);
self.registers.ICR.write(ICR::ALL::CLEAR);
self.registers.IBRD.write(IBRD::IBRD.val(13));
self.registers.FBRD.write(FBRD::FBRD.val(1));
self.registers
.LCRH
.write(LCRH::WLEN::EightBit + LCRH::FEN::FifosEnabled); // 8N1 + Fifo on
self.registers.IFLS.write(IFLS::RXIFLSEL::OneEigth); // RX FIFO fill level at 1/8
self.registers
.IMSC
.write(IMSC::RXIM::Enabled + IMSC::RTIM::Enabled); // RX IRQ + RX timeout IRQ
self.registers
.CR
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
Ok(())
}
/// Send a character.
fn write_char(&mut self, c: char) {
// Spin while TX FIFO full is set, waiting for an empty slot.
while self.registers.FR.matches_all(FR::TXFF::SET) {
cpu::nop();
}
// Write the character to the buffer.
self.registers.DR.set(c as u32);
self.chars_written += 1;
}
/// Retrieve a character.
fn read_char_converting(&mut self, blocking_mode: BlockingMode) -> Option<char> {
// If RX FIFO is empty,
if self.registers.FR.matches_all(FR::RXFE::SET) {
// immediately return in non-blocking mode.
if blocking_mode == BlockingMode::NonBlocking {
return None;
}
// Otherwise, wait until a char was received.
while self.registers.FR.matches_all(FR::RXFE::SET) {
cpu::nop();
}
}
// Read one character.
let mut ret = self.registers.DR.get() as u8 as char;
// Convert carrige return to newline.
if ret == '\r' {
ret = '\n'
}
// Update statistics.
self.chars_read += 1;
Some(ret)
}
}
/// 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() {
self.write_char(c);
}
Ok(())
}
}
impl PL011Uart {
/// Create an instance.
///
/// # Safety
///
/// - The user must ensure to provide correct MMIO descriptors.
/// - The user must ensure to provide correct IRQ numbers.
pub const unsafe fn new(
phys_mmio_descriptor: memory::mmu::MMIODescriptor<Physical>,
irq_number: bsp::device_driver::IRQNumber,
) -> Self {
Self {
phys_mmio_descriptor,
virt_mmio_start_addr: AtomicUsize::new(0),
inner: IRQSafeNullLock::new(PL011UartInner::new(
phys_mmio_descriptor.start_addr().into_usize(),
)),
irq_number,
}
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use synchronization::interface::Mutex;
impl driver::interface::DeviceDriver for PL011Uart {
fn compatible(&self) -> &'static str {
"BCM PL011 UART"
}
unsafe fn init(&self) -> Result<(), &'static str> {
let virt_addr =
memory::mmu::kernel_map_mmio(self.compatible(), &self.phys_mmio_descriptor)?;
let mut r = &self.inner;
r.lock(|inner| inner.init(Some(virt_addr.into_usize())))?;
self.virt_mmio_start_addr
.store(virt_addr.into_usize(), Ordering::Relaxed);
Ok(())
}
fn register_and_enable_irq_handler(&'static self) -> Result<(), &'static str> {
use bsp::exception::asynchronous::irq_manager;
use exception::asynchronous::{interface::IRQManager, IRQDescriptor};
let descriptor = IRQDescriptor {
name: "BCM PL011 UART",
handler: self,
};
irq_manager().register_handler(self.irq_number, descriptor)?;
irq_manager().enable(self.irq_number);
Ok(())
}
fn virt_mmio_start_addr(&self) -> Option<usize> {
let addr = self.virt_mmio_start_addr.load(Ordering::Relaxed);
if addr == 0 {
return None;
}
Some(addr)
}
}
impl console::interface::Write for PL011Uart {
/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to
/// serialize access.
fn write_char(&self, c: char) {
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) {
// Spin until TX FIFO empty is set.
let mut r = &self.inner;
r.lock(|inner| {
while !inner.registers.FR.matches_all(FR::TXFE::SET) {
cpu::nop();
}
});
}
}
impl console::interface::Read for PL011Uart {
fn read_char(&self) -> char {
let mut r = &self.inner;
r.lock(|inner| inner.read_char_converting(BlockingMode::Blocking).unwrap())
}
fn clear(&self) {
let mut r = &self.inner;
r.lock(|inner| {
// Read from the RX FIFO until it is indicating empty.
while !inner.registers.FR.matches_all(FR::RXFE::SET) {
inner.registers.DR.get();
}
})
}
}
impl console::interface::Statistics for PL011Uart {
fn chars_written(&self) -> usize {
let mut r = &self.inner;
r.lock(|inner| inner.chars_written)
}
fn chars_read(&self) -> usize {
let mut r = &self.inner;
r.lock(|inner| inner.chars_read)
}
}
impl exception::asynchronous::interface::IRQHandler for PL011Uart {
fn handle(&self) -> Result<(), &'static str> {
let mut r = &self.inner;
r.lock(|inner| {
let pending = inner.registers.MIS.extract();
// Clear all pending IRQs.
inner.registers.ICR.write(ICR::ALL::CLEAR);
// Check for any kind of RX interrupt.
if pending.matches_any(MIS::RXMIS::SET + MIS::RTMIS::SET) {
// Echo any received characters.
while let Some(c) = inner.read_char_converting(BlockingMode::NonBlocking) {
inner.write_char(c)
}
}
});
Ok(())
}
}

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Common device driver code.
use core::{marker::PhantomData, ops};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
pub struct MMIODerefWrapper<T> {
start_addr: usize,
phantom: PhantomData<T>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl<T> MMIODerefWrapper<T> {
/// Create an instance.
pub const unsafe fn new(start_addr: usize) -> Self {
Self {
start_addr,
phantom: PhantomData,
}
}
}
impl<T> ops::Deref for MMIODerefWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*(self.start_addr as *const _) }
}
}

@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Top-level BSP file for the Raspberry Pi 3 and 4.
pub mod console;
pub mod cpu;
pub mod driver;
pub mod exception;
pub mod memory;
use super::device_driver;
use crate::memory::mmu::MMIODescriptor;
use memory::map::mmio;
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static GPIO: device_driver::GPIO =
unsafe { device_driver::GPIO::new(MMIODescriptor::new(mmio::GPIO_START, mmio::GPIO_SIZE)) };
static PL011_UART: device_driver::PL011Uart = unsafe {
device_driver::PL011Uart::new(
MMIODescriptor::new(mmio::PL011_UART_START, mmio::PL011_UART_SIZE),
exception::asynchronous::irq_map::PL011_UART,
)
};
#[cfg(feature = "bsp_rpi3")]
static INTERRUPT_CONTROLLER: device_driver::InterruptController = unsafe {
device_driver::InterruptController::new(
MMIODescriptor::new(mmio::LOCAL_IC_START, mmio::LOCAL_IC_SIZE),
MMIODescriptor::new(mmio::PERIPHERAL_IC_START, mmio::PERIPHERAL_IC_SIZE),
)
};
#[cfg(feature = "bsp_rpi4")]
static INTERRUPT_CONTROLLER: device_driver::GICv2 = unsafe {
device_driver::GICv2::new(
MMIODescriptor::new(mmio::GICD_START, mmio::GICD_SIZE),
MMIODescriptor::new(mmio::GICC_START, mmio::GICC_SIZE),
)
};
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Board identification.
pub fn board_name() -> &'static str {
#[cfg(feature = "bsp_rpi3")]
{
"Raspberry Pi 3"
}
#[cfg(feature = "bsp_rpi4")]
{
"Raspberry Pi 4"
}
}

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP console facilities.
use super::memory;
use crate::{bsp::device_driver, console, cpu};
use core::fmt;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// In case of a panic, the panic handler uses this function to take a last shot at printing
/// something before the system is halted.
///
/// We try to init panic-versions of the GPIO and the UART. The panic versions are not protected
/// with synchronization primitives, which increases chances that we get to print something, even
/// when the kernel's default GPIO or UART instances happen to be locked at the time of the panic.
///
/// # Safety
///
/// - Use only for printing during a panic.
pub unsafe fn panic_console_out() -> impl fmt::Write {
use crate::driver::interface::DeviceDriver;
let mut panic_gpio = device_driver::PanicGPIO::new(memory::map::mmio::GPIO_START.into_usize());
let mut panic_uart =
device_driver::PanicUart::new(memory::map::mmio::PL011_UART_START.into_usize());
// If remapping of the driver's MMIO already happened, take the remapped start address.
// Otherwise, take a chance with the default physical address.
let maybe_gpio_mmio_start_addr = super::GPIO.virt_mmio_start_addr();
let maybe_uart_mmio_start_addr = super::PL011_UART.virt_mmio_start_addr();
panic_gpio
.init(maybe_gpio_mmio_start_addr)
.unwrap_or_else(|_| cpu::wait_forever());
panic_gpio.map_pl011_uart();
panic_uart
.init(maybe_uart_mmio_start_addr)
.unwrap_or_else(|_| cpu::wait_forever());
panic_uart
}
/// Return a reference to the console.
pub fn console() -> &'static impl console::interface::All {
&super::PL011_UART
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
/// Minimal code needed to bring up the console in QEMU (for testing only). This is often less steps
/// than on real hardware due to QEMU's abstractions.
///
/// For the RPi, nothing needs to be done.
pub fn qemu_bring_up_console() {}

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

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP driver support.
use crate::driver;
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// Device Driver Manager type.
struct BSPDriverManager {
device_drivers: [&'static (dyn DeviceDriver + Sync); 3],
}
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static BSP_DRIVER_MANAGER: BSPDriverManager = BSPDriverManager {
device_drivers: [
&super::GPIO,
&super::PL011_UART,
&super::INTERRUPT_CONTROLLER,
],
};
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return a reference to the driver manager.
pub fn driver_manager() -> &'static impl driver::interface::DriverManager {
&BSP_DRIVER_MANAGER
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use driver::interface::DeviceDriver;
impl driver::interface::DriverManager for BSPDriverManager {
fn all_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)] {
&self.device_drivers[..]
}
fn early_print_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)] {
&self.device_drivers[0..=1]
}
fn non_early_print_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)] {
&self.device_drivers[2..]
}
fn post_early_print_device_driver_init(&self) {
// Configure PL011Uart's output pins.
super::GPIO.map_pl011_uart();
}
}

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP synchronous and asynchronous exception handling.
pub mod asynchronous;

@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP asynchronous exception handling.
use crate::{bsp, exception};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
#[cfg(feature = "bsp_rpi3")]
pub(in crate::bsp) mod irq_map {
use super::bsp::device_driver::{IRQNumber, PeripheralIRQ};
pub const PL011_UART: IRQNumber = IRQNumber::Peripheral(PeripheralIRQ::new(57));
}
#[cfg(feature = "bsp_rpi4")]
pub(in crate::bsp) mod irq_map {
use super::bsp::device_driver::IRQNumber;
pub const PL011_UART: IRQNumber = IRQNumber::new(153);
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return a reference to the IRQ manager.
pub fn irq_manager() -> &'static impl exception::asynchronous::interface::IRQManager<
IRQNumberType = bsp::device_driver::IRQNumber,
> {
&super::super::INTERRUPT_CONTROLLER
}

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT OR Apache-2.0
*
* Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
*/
SECTIONS
{
/* Set current address to the value from which the RPi starts execution */
. = 0x80000;
__ro_start = .;
.text :
{
*(.text._start) *(.text*)
}
.exception_vectors :
{
*(.exception_vectors*)
}
.rodata :
{
*(.rodata*)
}
. = ALIGN(65536); /* Fill up to 64 KiB */
__ro_end = .;
.data :
{
*(.data*)
}
/* Section is zeroed in u64 chunks, align start and end to 8 bytes */
.bss ALIGN(8):
{
__bss_start = .;
*(.bss*);
. = ALIGN(8);
__bss_end = .;
}
. = ALIGN(65536);
__data_end = .;
__ro_size = __ro_end - __ro_start;
__data_size = __data_end - __ro_end;
/DISCARD/ : { *(.comment*) }
}

@ -0,0 +1,193 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP Memory Management.
//!
//! The physical memory layout after the kernel has been loaded by the Raspberry's firmware, which
//! copies the binary to 0x8_0000:
//!
//! +---------------------------------------------+
//! | | 0x0
//! | Unmapped |
//! | | 0x6_FFFF
//! +---------------------------------------------+
//! | BOOT_CORE_STACK_START | 0x7_0000
//! | | ^
//! | ... | | Stack growth direction
//! | | |
//! | BOOT_CORE_STACK_END_INCLUSIVE | 0x7_FFFF
//! +---------------------------------------------+
//! | RO_START == BOOT_CORE_STACK_END | 0x8_0000
//! | |
//! | |
//! | .text |
//! | .exception_vectors |
//! | .rodata |
//! | |
//! | RO_END_INCLUSIVE | 0x8_0000 + __ro_size - 1
//! +---------------------------------------------+
//! | RO_END == DATA_START | 0x8_0000 + __ro_size
//! | |
//! | .data |
//! | .bss |
//! | |
//! | DATA_END_INCLUSIVE | 0x8_0000 + __ro_size + __data_size - 1
//! +---------------------------------------------+
pub mod mmu;
use crate::memory::mmu::{Address, Physical, Virtual};
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
// Symbols from the linker script.
extern "C" {
static __ro_start: usize;
static __ro_size: usize;
static __bss_start: usize;
static __bss_end: usize;
static __data_size: usize;
}
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// The board's physical memory map.
#[rustfmt::skip]
pub(super) mod map {
use super::*;
pub const BOOT_CORE_STACK_SIZE: usize = 0x1_0000;
/// Physical devices.
#[cfg(feature = "bsp_rpi3")]
pub mod mmio {
use super::*;
pub const PERIPHERAL_IC_START: Address<Physical> = Address::new(0x3F00_B200);
pub const PERIPHERAL_IC_SIZE: usize = 0x24;
pub const GPIO_START: Address<Physical> = Address::new(0x3F20_0000);
pub const GPIO_SIZE: usize = 0xA0;
pub const PL011_UART_START: Address<Physical> = Address::new(0x3F20_1000);
pub const PL011_UART_SIZE: usize = 0x48;
pub const LOCAL_IC_START: Address<Physical> = Address::new(0x4000_0000);
pub const LOCAL_IC_SIZE: usize = 0x100;
pub const END: Address<Physical> = Address::new(0x4001_0000);
}
/// Physical devices.
#[cfg(feature = "bsp_rpi4")]
pub mod mmio {
use super::*;
pub const GPIO_START: Address<Physical> = Address::new(0xFE20_0000);
pub const GPIO_SIZE: usize = 0xA0;
pub const PL011_UART_START: Address<Physical> = Address::new(0xFE20_1000);
pub const PL011_UART_SIZE: usize = 0x48;
pub const GICD_START: Address<Physical> = Address::new(0xFF84_1000);
pub const GICD_SIZE: usize = 0x824;
pub const GICC_START: Address<Physical> = Address::new(0xFF84_2000);
pub const GICC_SIZE: usize = 0x14;
pub const END: Address<Physical> = Address::new(0xFF85_0000);
}
pub const END: Address<Physical> = mmio::END;
}
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
/// Start address of the Read-Only (RO) range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn virt_ro_start() -> Address<Virtual> {
Address::new(unsafe { &__ro_start as *const _ as usize })
}
/// Size of the Read-Only (RO) range of the kernel binary.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn ro_size() -> usize {
unsafe { &__ro_size as *const _ as usize }
}
/// Start address of the data range.
#[inline(always)]
fn virt_data_start() -> Address<Virtual> {
virt_ro_start() + ro_size()
}
/// Size of the data range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn data_size() -> usize {
unsafe { &__data_size as *const _ as usize }
}
/// Start address of the boot core's stack.
#[inline(always)]
fn virt_boot_core_stack_start() -> Address<Virtual> {
virt_ro_start() - map::BOOT_CORE_STACK_SIZE
}
/// Size of the boot core's stack.
#[inline(always)]
fn boot_core_stack_size() -> usize {
map::BOOT_CORE_STACK_SIZE
}
/// Exclusive end address of the physical address space.
#[inline(always)]
fn phys_addr_space_end() -> Address<Physical> {
map::END
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Exclusive end address of the boot core's stack.
#[inline(always)]
pub fn phys_boot_core_stack_end() -> Address<Physical> {
// The binary is still identity mapped, so we don't need to convert here.
let end = virt_boot_core_stack_start().into_usize() + boot_core_stack_size();
Address::new(end)
}
/// Return the range spanning the .bss section.
///
/// # Safety
///
/// - Values are provided by the linker script and must be trusted as-is.
/// - The linker-provided addresses must be u64 aligned.
pub fn bss_range() -> Range<*mut u64> {
unsafe {
Range {
start: &__bss_start as *const _ as *mut u64,
end: &__bss_end as *const _ as *mut u64,
}
}
}

@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! BSP Memory Management Unit.
use crate::{
common,
memory::{
mmu as kernel_mmu,
mmu::{
interface, AccessPermissions, AttributeFields, Granule64KiB, MemAttributes, Page,
PageSliceDescriptor, Physical, Virtual,
},
},
};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// The translation granule chosen by this BSP. This will be used everywhere else in the kernel to
/// derive respective data structures and their sizes. For example, the `crate::memory::mmu::Page`.
pub type KernelGranule = Granule64KiB;
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
use interface::TranslationGranule;
/// Helper function for calculating the number of pages the given parameter spans.
const fn size_to_num_pages(size: usize) -> usize {
assert!(size > 0);
assert!(size % KernelGranule::SIZE == 0);
size >> KernelGranule::SHIFT
}
/// The boot core's stack.
fn virt_stack_page_desc() -> PageSliceDescriptor<Virtual> {
let num_pages = size_to_num_pages(super::boot_core_stack_size());
PageSliceDescriptor::from_addr(super::virt_boot_core_stack_start(), num_pages)
}
/// The Read-Only (RO) pages of the kernel binary.
fn virt_ro_page_desc() -> PageSliceDescriptor<Virtual> {
let num_pages = size_to_num_pages(super::ro_size());
PageSliceDescriptor::from_addr(super::virt_ro_start(), num_pages)
}
/// The data pages of the kernel binary.
fn virt_data_page_desc() -> PageSliceDescriptor<Virtual> {
let num_pages = size_to_num_pages(super::data_size());
PageSliceDescriptor::from_addr(super::virt_data_start(), num_pages)
}
// The binary is still identity mapped, so we don't need to convert in the following.
/// The boot core's stack.
fn phys_stack_page_desc() -> PageSliceDescriptor<Physical> {
virt_stack_page_desc().into()
}
/// The Read-Only (RO) pages of the kernel binary.
fn phys_ro_page_desc() -> PageSliceDescriptor<Physical> {
virt_ro_page_desc().into()
}
/// The data pages of the kernel binary.
fn phys_data_page_desc() -> PageSliceDescriptor<Physical> {
virt_data_page_desc().into()
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Pointer to the last page of the physical address space.
pub fn phys_addr_space_end_page() -> *const Page<Physical> {
common::align_down(
super::phys_addr_space_end().into_usize(),
KernelGranule::SIZE,
) as *const Page<_>
}
/// Map the kernel binary.
///
/// # Safety
///
/// - Any miscalculation or attribute error will likely be fatal. Needs careful manual checking.
pub unsafe fn kernel_map_binary() -> Result<(), &'static str> {
kernel_mmu::kernel_map_pages_at(
"Kernel boot-core stack",
&phys_stack_page_desc(),
&virt_stack_page_desc(),
&AttributeFields {
mem_attributes: MemAttributes::CacheableDRAM,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
)?;
kernel_mmu::kernel_map_pages_at(
"Kernel code and RO data",
&phys_ro_page_desc(),
&virt_ro_page_desc(),
&AttributeFields {
mem_attributes: MemAttributes::CacheableDRAM,
acc_perms: AccessPermissions::ReadOnly,
execute_never: false,
},
)?;
kernel_mmu::kernel_map_pages_at(
"Kernel data and bss",
&phys_data_page_desc(),
&virt_data_page_desc(),
&AttributeFields {
mem_attributes: MemAttributes::CacheableDRAM,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
)?;
Ok(())
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Check alignment of the kernel's virtual memory layout sections.
#[kernel_test]
fn virt_mem_layout_sections_are_64KiB_aligned() {
for i in [virt_stack_page_desc, virt_ro_page_desc, virt_data_page_desc].iter() {
let start: usize = i().start_addr().into_usize();
let end: usize = i().end_addr().into_usize();
assert_eq!(start % KernelGranule::SIZE, 0);
assert_eq!(end % KernelGranule::SIZE, 0);
assert!(end >= start);
}
}
/// Ensure the kernel's virtual memory layout is free of overlaps.
#[kernel_test]
fn virt_mem_layout_has_no_overlaps() {
let layout = [
virt_stack_page_desc().into_usize_range_inclusive(),
virt_ro_page_desc().into_usize_range_inclusive(),
virt_data_page_desc().into_usize_range_inclusive(),
];
for (i, first_range) in layout.iter().enumerate() {
for second_range in layout.iter().skip(i + 1) {
assert!(!first_range.contains(second_range.start()));
assert!(!first_range.contains(second_range.end()));
assert!(!second_range.contains(first_range.start()));
assert!(!second_range.contains(first_range.end()));
}
}
}
}

@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! General purpose code.
/// Check if a value is aligned to a given size.
#[inline(always)]
pub const fn is_aligned(value: usize, alignment: usize) -> bool {
assert!(alignment.is_power_of_two());
(value & (alignment - 1)) == 0
}
/// Align down.
#[inline(always)]
pub const fn align_down(value: usize, alignment: usize) -> usize {
assert!(alignment.is_power_of_two());
value & !(alignment - 1)
}

@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! System console.
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Console interfaces.
pub mod interface {
use core::fmt;
/// Console write functions.
pub trait Write {
/// Write a single character.
fn write_char(&self, c: char);
/// Write a Rust format string.
fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
/// Block execution until the last character has been physically put on the TX wire
/// (draining TX buffers/FIFOs, if any).
fn flush(&self);
}
/// Console read functions.
pub trait Read {
/// Read a single character.
fn read_char(&self) -> char {
' '
}
/// Clear RX buffers, if any.
fn clear(&self);
}
/// Console statistics.
pub trait Statistics {
/// Return the number of characters written.
fn chars_written(&self) -> usize {
0
}
/// Return the number of characters read.
fn chars_read(&self) -> usize {
0
}
}
/// Trait alias for a full-fledged console.
pub trait All = Write + Read + Statistics;
}

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

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

@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Driver support.
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Driver interfaces.
pub mod interface {
/// Device Driver functions.
pub trait DeviceDriver {
/// Return a compatibility string for identifying the driver.
fn compatible(&self) -> &'static str;
/// Called by the kernel to bring up the device.
///
/// # Safety
///
/// - During init, drivers might do stuff with system-wide impact.
unsafe fn init(&self) -> Result<(), &'static str> {
Ok(())
}
/// Called by the kernel to register and enable the device's IRQ handlers, if any.
///
/// Rust's type system will prevent a call to this function unless the calling instance
/// itself has static lifetime.
fn register_and_enable_irq_handler(&'static self) -> Result<(), &'static str> {
Ok(())
}
/// After MMIO remapping, returns the new virtual start address.
///
/// This API assumes a driver has only a single, contiguous MMIO aperture, which will not be
/// the case for more complex devices. This API will likely change in future tutorials.
fn virt_mmio_start_addr(&self) -> Option<usize> {
None
}
}
/// Device driver management functions.
///
/// The `BSP` is supposed to supply one global instance.
pub trait DriverManager {
/// Return a slice of references to all `BSP`-instantiated drivers.
fn all_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)];
/// Return only those drivers needed for the BSP's early printing functionality.
///
/// For example, the default UART.
fn early_print_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)];
/// Return all drivers minus early-print drivers.
fn non_early_print_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)];
/// Initialization code that runs after the early print driver init.
fn post_early_print_device_driver_init(&self);
}
}

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Synchronous and asynchronous exception handling.
#[cfg(target_arch = "aarch64")]
#[path = "_arch/aarch64/exception.rs"]
mod arch_exception;
pub use arch_exception::*;
pub mod asynchronous;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Kernel privilege levels.
#[allow(missing_docs)]
#[derive(PartialEq)]
pub enum PrivilegeLevel {
User,
Kernel,
Hypervisor,
Unknown,
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Libkernel unit tests must execute in kernel mode.
#[kernel_test]
fn test_runner_executes_in_kernel_mode() {
let (level, _) = current_privilege_level();
assert!(level == PrivilegeLevel::Kernel)
}
}

@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Asynchronous exception handling.
#[cfg(target_arch = "aarch64")]
#[path = "../_arch/aarch64/exception/asynchronous.rs"]
mod arch_exception_async;
pub use arch_exception_async::*;
use core::{fmt, marker::PhantomData};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Interrupt descriptor.
#[derive(Copy, Clone)]
pub struct IRQDescriptor {
/// Descriptive name.
pub name: &'static str,
/// Reference to handler trait object.
pub handler: &'static (dyn interface::IRQHandler + Sync),
}
/// IRQContext token.
///
/// An instance of this type indicates that the local core is currently executing in IRQ
/// context, aka executing an interrupt vector or subcalls of it.
///
/// Concept and implementation derived from the `CriticalSection` introduced in
/// https://github.com/rust-embedded/bare-metal
#[derive(Clone, Copy)]
pub struct IRQContext<'irq_context> {
_0: PhantomData<&'irq_context ()>,
}
/// Asynchronous exception handling interfaces.
pub mod interface {
/// Implemented by types that handle IRQs.
pub trait IRQHandler {
/// Called when the corresponding interrupt is asserted.
fn handle(&self) -> Result<(), &'static str>;
}
/// IRQ management functions.
///
/// The `BSP` is supposed to supply one global instance. Typically implemented by the
/// platform's interrupt controller.
pub trait IRQManager {
/// The IRQ number type depends on the implementation.
type IRQNumberType;
/// Register a handler.
fn register_handler(
&self,
irq_number: Self::IRQNumberType,
descriptor: super::IRQDescriptor,
) -> Result<(), &'static str>;
/// Enable an interrupt in the controller.
fn enable(&self, irq_number: Self::IRQNumberType);
/// Handle pending interrupts.
///
/// This function is called directly from the CPU's IRQ exception vector. On AArch64,
/// this means that the respective CPU core has disabled exception handling.
/// This function can therefore not be preempted and runs start to finish.
///
/// Takes an IRQContext token to ensure it can only be called from IRQ context.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn handle_pending_irqs<'irq_context>(
&'irq_context self,
ic: &super::IRQContext<'irq_context>,
);
/// Print list of registered handlers.
fn print_handler(&self);
}
}
/// A wrapper type for IRQ numbers with integrated range sanity check.
#[derive(Copy, Clone)]
pub struct IRQNumber<const MAX_INCLUSIVE: usize>(usize);
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl<'irq_context> IRQContext<'irq_context> {
/// Creates an IRQContext token.
///
/// # Safety
///
/// - This must only be called when the current core is in an interrupt context and will not
/// live beyond the end of it. That is, creation is allowed in interrupt vector functions. For
/// example, in the ARMv8-A case, in `extern "C" fn current_elx_irq()`.
/// - Note that the lifetime `'irq_context` of the returned instance is unconstrained. User code
/// must not be able to influence the lifetime picked for this type, since that might cause it
/// to be inferred to `'static`.
#[inline(always)]
pub unsafe fn new() -> Self {
IRQContext { _0: PhantomData }
}
}
impl<const MAX_INCLUSIVE: usize> IRQNumber<{ MAX_INCLUSIVE }> {
/// Creates a new instance if number <= MAX_INCLUSIVE.
pub const fn new(number: usize) -> Self {
assert!(number <= MAX_INCLUSIVE);
Self { 0: number }
}
/// Return the wrapped number.
pub fn get(self) -> usize {
self.0
}
}
impl<const MAX_INCLUSIVE: usize> fmt::Display for IRQNumber<{ MAX_INCLUSIVE }> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Executes the provided closure while IRQs are masked on the executing core.
///
/// While the function temporarily changes the HW state of the executing core, it restores it to the
/// previous state before returning, so this is deemed safe.
#[inline(always)]
pub fn exec_with_irq_masked<T>(f: impl FnOnce() -> T) -> T {
let ret: T;
unsafe {
let saved = local_irq_mask_save();
ret = f();
local_irq_restore(saved);
}
ret
}

@ -0,0 +1,179 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
// Rust embedded logo for `make doc`.
#![doc(html_logo_url = "https://git.io/JeGIp")]
//! The `kernel` library.
//!
//! Used by `main.rs` to compose the final kernel binary.
//!
//! # TL;DR - Overview of important Kernel entities
//!
//! - [`bsp::console::console()`] - Returns a reference to the kernel's [console interface].
//! - [`bsp::driver::driver_manager()`] - Returns a reference to the kernel's [driver interface].
//! - [`bsp::exception::asynchronous::irq_manager()`] - Returns a reference to the kernel's [IRQ
//! Handling interface].
//! - [`memory::mmu::mmu()`] - Returns a reference to the kernel's [MMU interface].
//! - [`state::state_manager()`] - Returns a reference to the kernel's [state management] instance.
//! - [`time::time_manager()`] - Returns a reference to the kernel's [timer interface].
//!
//! [console interface]: ../libkernel/console/interface/index.html
//! [driver interface]: ../libkernel/driver/interface/trait.DriverManager.html
//! [IRQ Handling interface]: ../libkernel/exception/asynchronous/interface/trait.IRQManager.html
//! [MMU interface]: ../libkernel/memory/mmu/interface/trait.MMU.html
//! [state management]: ../libkernel/state/struct.StateManager.html
//! [timer interface]: ../libkernel/time/interface/trait.TimeManager.html
//!
//! # Code organization and architecture
//!
//! The code is divided into different *modules*, each representing a typical **subsystem** of the
//! `kernel`. Top-level module files of subsystems reside directly in the `src` folder. For example,
//! `src/memory.rs` contains code that is concerned with all things memory management.
//!
//! ## Visibility of processor architecture code
//!
//! Some of the `kernel`'s subsystems depend on low-level code that is specific to the target
//! processor architecture. For each supported processor architecture, there exists a subfolder in
//! `src/_arch`, for example, `src/_arch/aarch64`.
//!
//! The architecture folders mirror the subsystem modules laid out in `src`. For example,
//! architectural code that belongs to the `kernel`'s memory subsystem (`src/memory.rs`) would go
//! into `src/_arch/aarch64/memory.rs`. The latter file is directly included and re-exported in
//! `src/memory.rs`, so that the architectural code parts are transparent with respect to the code's
//! module organization. That means a public function `foo()` defined in
//! `src/_arch/aarch64/memory.rs` would be reachable as `crate::memory::foo()` only.
//!
//! The `_` in `_arch` denotes that this folder is not part of the standard module hierarchy.
//! Rather, it's contents are conditionally pulled into respective files using the `#[path =
//! "_arch/xxx/yyy.rs"]` attribute.
//!
//! ## BSP code
//!
//! `BSP` stands for Board Support Package. `BSP` code is organized under `src/bsp.rs` and contains
//! target board specific definitions and functions. These are things such as the board's memory map
//! or instances of drivers for devices that are featured on the respective board.
//!
//! Just like processor architecture code, the `BSP` code's module structure tries to mirror the
//! `kernel`'s subsystem modules, but there is no transparent re-exporting this time. That means
//! whatever is provided must be called starting from the `bsp` namespace, e.g.
//! `bsp::driver::driver_manager()`.
//!
//! ## Kernel interfaces
//!
//! Both `arch` and `bsp` contain code that is conditionally compiled depending on the actual target
//! and board for which the kernel is compiled. For example, the `interrupt controller` hardware of
//! the `Raspberry Pi 3` and the `Raspberry Pi 4` is different, but we want the rest of the `kernel`
//! code to play nicely with any of the two without much hassle.
//!
//! In order to provide a clean abstraction between `arch`, `bsp` and `generic kernel code`,
//! `interface` traits are provided *whenever possible* and *where it makes sense*. They are defined
//! in the respective subsystem module and help to enforce the idiom of *program to an interface,
//! not an implementation*. For example, there will be a common IRQ handling interface which the two
//! different interrupt controller `drivers` of both Raspberrys will implement, and only export the
//! interface to the rest of the `kernel`.
//!
//! ```
//! +-------------------+
//! | Interface (Trait) |
//! | |
//! +--+-------------+--+
//! ^ ^
//! | |
//! | |
//! +----------+--+ +--+----------+
//! | kernel code | | bsp code |
//! | | | arch code |
//! +-------------+ +-------------+
//! ```
//!
//! # Summary
//!
//! For a logical `kernel` subsystem, corresponding code can be distributed over several physical
//! locations. Here is an example for the **memory** subsystem:
//!
//! - `src/memory.rs` and `src/memory/**/*`
//! - Common code that is agnostic of target processor architecture and `BSP` characteristics.
//! - Example: A function to zero a chunk of memory.
//! - Interfaces for the memory subsystem that are implemented by `arch` or `BSP` code.
//! - Example: An `MMU` interface that defines `MMU` function prototypes.
//! - `src/bsp/__board_name__/memory.rs` and `src/bsp/__board_name__/memory/**/*`
//! - `BSP` specific code.
//! - Example: The board's memory map (physical addresses of DRAM and MMIO devices).
//! - `src/_arch/__arch_name__/memory.rs` and `src/_arch/__arch_name__/memory/**/*`
//! - Processor architecture specific code.
//! - Example: Implementation of the `MMU` interface for the `__arch_name__` processor
//! architecture.
//!
//! From a namespace perspective, **memory** subsystem code lives in:
//!
//! - `crate::memory::*`
//! - `crate::bsp::memory::*`
#![allow(incomplete_features)]
#![feature(asm)]
#![feature(const_fn)]
#![feature(const_generics)]
#![feature(const_panic)]
#![feature(core_intrinsics)]
#![feature(format_args_nl)]
#![feature(global_asm)]
#![feature(linkage)]
#![feature(naked_functions)]
#![feature(panic_info_message)]
#![feature(trait_alias)]
#![no_std]
// Testing
#![cfg_attr(test, no_main)]
#![feature(custom_test_frameworks)]
#![reexport_test_harness_main = "test_main"]
#![test_runner(crate::test_runner)]
// `mod cpu` provides the `_start()` function, the first function to run. `_start()` then calls
// `runtime_init()`, which jumps to `kernel_init()` (defined in `main.rs`).
mod panic_wait;
mod runtime_init;
mod synchronization;
pub mod bsp;
pub mod common;
pub mod console;
pub mod cpu;
pub mod driver;
pub mod exception;
pub mod memory;
pub mod print;
pub mod state;
pub mod time;
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
/// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) {
println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name);
// Run the actual test.
(test.test_func)();
// Failed tests call panic!(). Execution reaches here only if the test has passed.
println!("[ok]")
}
}
/// The `kernel_init()` for unit tests. Called from `runtime_init()`.
#[cfg(test)]
#[no_mangle]
unsafe fn kernel_init() -> ! {
bsp::console::qemu_bring_up_console();
test_main();
cpu::qemu_exit_success()
}

@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
// Rust embedded logo for `make doc`.
#![doc(html_logo_url = "https://git.io/JeGIp")]
//! The `kernel` binary.
#![feature(format_args_nl)]
#![no_main]
#![no_std]
use libkernel::{bsp, cpu, driver, exception, info, memory, state, time, warn};
/// Early init code.
///
/// # Safety
///
/// - Only a single core must be active and running this function.
/// - The init calls in this function must appear in the correct order:
/// - Virtual memory must be activated before the device drivers.
/// - Without it, any atomic operations, e.g. the yet-to-be-introduced spinlocks in the device
/// drivers (which currently employ IRQSafeNullLocks instead of spinlocks), will fail to
/// work on the RPi SoCs.
#[no_mangle]
unsafe fn kernel_init() -> ! {
use driver::interface::DriverManager;
exception::handling_init();
if let Err(string) = memory::mmu::kernel_map_binary_and_enable_mmu() {
panic!("Enabling MMU failed: {}", string);
}
// Printing will silently fail fail from here on, because the driver's MMIO is not remapped yet.
// Bring up the drivers needed for printing first.
for i in bsp::driver::driver_manager()
.early_print_device_drivers()
.iter()
{
// Any encountered errors cannot be printed yet, obviously, so just safely park the CPU.
i.init().unwrap_or_else(|_| cpu::wait_forever());
}
bsp::driver::driver_manager().post_early_print_device_driver_init();
// Printing available again from here on.
// Now bring up the remaining drivers.
for i in bsp::driver::driver_manager()
.non_early_print_device_drivers()
.iter()
{
if let Err(x) = i.init() {
panic!("Error loading driver: {}: {}", i.compatible(), x);
}
}
// Let device drivers register and enable their handlers with the interrupt controller.
for i in bsp::driver::driver_manager().all_device_drivers() {
if let Err(msg) = i.register_and_enable_irq_handler() {
warn!("Error registering IRQ handler: {}", msg);
}
}
// Unmask interrupts on the boot CPU core.
exception::asynchronous::local_irq_unmask();
// Announce conclusion of the kernel_init() phase.
state::state_manager().transition_to_single_core_main();
// Transition from unsafe to safe.
kernel_main()
}
/// The main function running after the early init.
fn kernel_main() -> ! {
use driver::interface::DriverManager;
use exception::asynchronous::interface::IRQManager;
info!("Booting on: {}", bsp::board_name());
info!("MMU online:");
memory::mmu::kernel_print_mappings();
let (_, privilege_level) = exception::current_privilege_level();
info!("Current privilege level: {}", privilege_level);
info!("Exception handling state:");
exception::asynchronous::print_state();
info!(
"Architectural timer resolution: {} ns",
time::time_manager().resolution().as_nanos()
);
info!("Drivers loaded:");
for (i, driver) in bsp::driver::driver_manager()
.all_device_drivers()
.iter()
.enumerate()
{
info!(" {}. {}", i + 1, driver.compatible());
}
info!("Registered IRQ handlers:");
bsp::exception::asynchronous::irq_manager().print_handler();
info!("Echoing input now");
cpu::wait_forever();
}

@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Memory Management.
pub mod mmu;
use core::ops::Range;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Zero out a memory range.
///
/// # Safety
///
/// - `range.start` and `range.end` must be valid.
/// - `range.start` and `range.end` must be `T` aligned.
pub unsafe fn zero_volatile<T>(range: Range<*mut T>)
where
T: From<u8>,
{
let mut ptr = range.start;
while ptr < range.end {
core::ptr::write_volatile(ptr, T::from(0));
ptr = ptr.offset(1);
}
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Check `zero_volatile()`.
#[kernel_test]
fn zero_volatile_works() {
let mut x: [usize; 3] = [10, 11, 12];
let x_range = x.as_mut_ptr_range();
unsafe { zero_volatile(x_range) };
assert_eq!(x, [0, 0, 0]);
}
}

@ -0,0 +1,250 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Memory Management Unit.
#[cfg(target_arch = "aarch64")]
#[path = "../_arch/aarch64/memory/mmu.rs"]
mod arch_mmu;
pub use arch_mmu::*;
mod mapping_record;
mod types;
use crate::{bsp, synchronization, warn};
pub use types::*;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Memory Management interfaces.
pub mod interface {
use super::*;
/// Describes the characteristics of a translation granule.
#[allow(missing_docs)]
pub trait TranslationGranule {
const SIZE: usize;
const MASK: usize = Self::SIZE - 1;
const SHIFT: usize;
}
/// Translation table operations.
pub trait TranslationTable {
/// Anything that needs to run before any of the other provided functions can be used.
///
/// # Safety
///
/// - Implementor must ensure that this function can run only once or is harmless if invoked
/// multiple times.
unsafe fn init(&mut self);
/// The translation table's base address to be used for programming the MMU.
fn phys_base_address(&self) -> Address<Physical>;
/// Map the given physical pages to the given virtual pages.
///
/// # Safety
///
/// - Using wrong attributes can cause multiple issues of different nature in the system.
/// - It is not required that the architectural implementation prevents aliasing. That is,
/// mapping to the same physical memory using multiple virtual addresses, which would
/// break Rust's ownership assumptions. This should be protected against in this module
/// (the kernel's generic MMU code).
unsafe fn map_pages_at(
&mut self,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str>;
/// Obtain a free virtual page slice in the MMIO region.
///
/// The "MMIO region" is a distinct region of the implementor's choice, which allows
/// differentiating MMIO addresses from others. This can speed up debugging efforts.
/// Ideally, those MMIO addresses are also standing out visually so that a human eye can
/// identify them. For example, by allocating them from near the end of the virtual address
/// space.
fn next_mmio_virt_page_slice(
&mut self,
num_pages: usize,
) -> Result<PageSliceDescriptor<Virtual>, &'static str>;
/// Check if a virtual page splice is in the "MMIO region".
fn is_virt_page_slice_mmio(&self, virt_pages: &PageSliceDescriptor<Virtual>) -> bool;
}
/// MMU functions.
pub trait MMU {
/// Turns on the MMU.
///
/// # Safety
///
/// - Must only be called after the kernel translation tables have been init()'ed.
/// - Changes the HW's global state.
unsafe fn enable(
&self,
phys_kernel_table_base_addr: Address<Physical>,
) -> Result<(), &'static str>;
}
}
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
use interface::{TranslationTable, MMU};
use synchronization::interface::ReadWriteEx;
/// Map pages in the kernel's translation tables.
///
/// No input checks done, input is passed through to the architectural implementation.
///
/// # Safety
///
/// - See `map_pages_at()`.
/// - Does not prevent aliasing.
unsafe fn kernel_map_pages_at_unchecked(
name: &'static str,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str> {
arch_mmu::kernel_translation_tables()
.write(|tables| tables.map_pages_at(phys_pages, virt_pages, attr))?;
if let Err(x) = mapping_record::kernel_add(name, phys_pages, virt_pages, attr) {
warn!("{}", x);
}
Ok(())
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use interface::TranslationGranule;
/// Raw mapping of virtual to physical pages in the kernel translation tables.
///
/// Prevents mapping into the MMIO range of the tables.
///
/// # Safety
///
/// - See `kernel_map_pages_at_unchecked()`.
/// - Does not prevent aliasing. Currently, we have to trust the callers.
pub unsafe fn kernel_map_pages_at(
name: &'static str,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str> {
let is_mmio = arch_mmu::kernel_translation_tables()
.read(|tables| tables.is_virt_page_slice_mmio(virt_pages));
if is_mmio {
return Err("Attempt to manually map into MMIO region");
}
kernel_map_pages_at_unchecked(name, phys_pages, virt_pages, attr)?;
Ok(())
}
/// MMIO remapping in the kernel translation tables.
///
/// Typically used by device drivers.
///
/// # Safety
///
/// - Same as `kernel_map_pages_at_unchecked()`, minus the aliasing part.
pub unsafe fn kernel_map_mmio(
name: &'static str,
phys_mmio_descriptor: &MMIODescriptor<Physical>,
) -> Result<Address<Virtual>, &'static str> {
let phys_pages: PageSliceDescriptor<Physical> = phys_mmio_descriptor.clone().into();
let offset_into_start_page =
phys_mmio_descriptor.start_addr().into_usize() & bsp::memory::mmu::KernelGranule::MASK;
// Check if an identical page slice has been mapped for another driver. If so, reuse it.
let virt_addr = if let Some(addr) =
mapping_record::kernel_find_and_insert_mmio_duplicate(phys_mmio_descriptor, name)
{
addr
// Otherwise, allocate a new virtual page slice and map it.
} else {
let virt_pages: PageSliceDescriptor<Virtual> = arch_mmu::kernel_translation_tables()
.write(|tables| tables.next_mmio_virt_page_slice(phys_pages.num_pages()))?;
kernel_map_pages_at_unchecked(
name,
&phys_pages,
&virt_pages,
&AttributeFields {
mem_attributes: MemAttributes::Device,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
)?;
virt_pages.start_addr()
};
Ok(virt_addr + offset_into_start_page)
}
/// Map the kernel's binary and enable the MMU.
///
/// # Safety
///
/// - Crucial function during kernel init. Changes the the complete memory view of the processor.
pub unsafe fn kernel_map_binary_and_enable_mmu() -> Result<(), &'static str> {
let phys_base_addr = arch_mmu::kernel_translation_tables().write(|tables| {
tables.init();
tables.phys_base_address()
});
bsp::memory::mmu::kernel_map_binary()?;
arch_mmu::mmu().enable(phys_base_addr)
}
/// Human-readable print of all recorded kernel mappings.
pub fn kernel_print_mappings() {
mapping_record::kernel_print()
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Sanity checks for the kernel TranslationTable implementation.
#[kernel_test]
fn translationtable_implementation_sanity() {
// Need to take care that `tables` fits into the stack.
let mut tables = MinSizeArchTranslationTable::new();
unsafe { tables.init() };
let x = tables.next_mmio_virt_page_slice(0);
assert!(x.is_err());
let x = tables.next_mmio_virt_page_slice(1_0000_0000);
assert!(x.is_err());
let x = tables.next_mmio_virt_page_slice(2).unwrap();
assert_eq!(x.size(), bsp::memory::mmu::KernelGranule::SIZE * 2);
assert_eq!(tables.is_virt_page_slice_mmio(&x), true);
assert_eq!(
tables.is_virt_page_slice_mmio(&PageSliceDescriptor::from_addr(Address::new(0), 1)),
false
);
}
}

@ -0,0 +1,224 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! A record of mapped pages.
use super::{
AccessPermissions, Address, AttributeFields, MMIODescriptor, MemAttributes,
PageSliceDescriptor, Physical, Virtual,
};
use crate::{info, synchronization, synchronization::InitStateLock, warn};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// Type describing a virtual memory mapping.
#[allow(missing_docs)]
#[derive(Copy, Clone)]
struct MappingRecordEntry {
pub users: [Option<&'static str>; 5],
pub phys_pages: PageSliceDescriptor<Physical>,
pub virt_start_addr: Address<Virtual>,
pub attribute_fields: AttributeFields,
}
struct MappingRecord {
inner: [Option<MappingRecordEntry>; 12],
}
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static KERNEL_MAPPING_RECORD: InitStateLock<MappingRecord> =
InitStateLock::new(MappingRecord::new());
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
impl MappingRecordEntry {
pub fn new(
name: &'static str,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Self {
Self {
users: [Some(name), None, None, None, None],
phys_pages: *phys_pages,
virt_start_addr: virt_pages.start_addr(),
attribute_fields: *attr,
}
}
fn find_next_free_user(&mut self) -> Result<&mut Option<&'static str>, &'static str> {
if let Some(x) = self.users.iter_mut().find(|x| x.is_none()) {
return Ok(x);
};
Err("Storage for user info exhausted")
}
pub fn add_user(&mut self, user: &'static str) -> Result<(), &'static str> {
let x = self.find_next_free_user()?;
*x = Some(user);
Ok(())
}
}
impl MappingRecord {
pub const fn new() -> Self {
Self { inner: [None; 12] }
}
fn find_next_free(&mut self) -> Result<&mut Option<MappingRecordEntry>, &'static str> {
if let Some(x) = self.inner.iter_mut().find(|x| x.is_none()) {
return Ok(x);
}
Err("Storage for mapping info exhausted")
}
fn find_duplicate(
&mut self,
phys_pages: &PageSliceDescriptor<Physical>,
) -> Option<&mut MappingRecordEntry> {
self.inner
.iter_mut()
.filter(|x| x.is_some())
.map(|x| x.as_mut().unwrap())
.filter(|x| x.attribute_fields.mem_attributes == MemAttributes::Device)
.find(|x| x.phys_pages == *phys_pages)
}
pub fn add(
&mut self,
name: &'static str,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str> {
let x = self.find_next_free()?;
*x = Some(MappingRecordEntry::new(name, phys_pages, virt_pages, attr));
Ok(())
}
pub fn print(&self) {
const KIB_RSHIFT: u32 = 10; // log2(1024).
const MIB_RSHIFT: u32 = 20; // log2(1024 * 1024).
info!(" -----------------------------------------------------------------------------------------------------------------");
info!(
" {:^24} {:^24} {:^7} {:^9} {:^35}",
"Virtual", "Physical", "Size", "Attr", "Entity"
);
info!(" -----------------------------------------------------------------------------------------------------------------");
for i in self
.inner
.iter()
.filter(|x| x.is_some())
.map(|x| x.unwrap())
{
let virt_start = i.virt_start_addr.into_usize();
let virt_end_inclusive = virt_start + i.phys_pages.size() - 1;
let phys_start = i.phys_pages.start_addr().into_usize();
let phys_end_inclusive = i.phys_pages.end_addr_inclusive().into_usize();
let size = i.phys_pages.size();
let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
(size >> MIB_RSHIFT, "MiB")
} else if (size >> KIB_RSHIFT) > 0 {
(size >> KIB_RSHIFT, "KiB")
} else {
(size, "Byte")
};
let attr = match i.attribute_fields.mem_attributes {
MemAttributes::CacheableDRAM => "C",
MemAttributes::Device => "Dev",
};
let acc_p = match i.attribute_fields.acc_perms {
AccessPermissions::ReadOnly => "RO",
AccessPermissions::ReadWrite => "RW",
};
let xn = if i.attribute_fields.execute_never {
"XN"
} else {
"X"
};
info!(
" {:#011X}..{:#011X} --> {:#011X}..{:#011X} | \
{: >3} {} | {: <3} {} {: <2} | {}",
virt_start,
virt_end_inclusive,
phys_start,
phys_end_inclusive,
size,
unit,
attr,
acc_p,
xn,
i.users[0].unwrap()
);
for k in i.users[1..].iter() {
if let Some(additional_user) = *k {
info!(
" | {}",
additional_user
);
}
}
}
info!(" -----------------------------------------------------------------------------------------------------------------");
}
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use synchronization::interface::ReadWriteEx;
/// Add an entry to the mapping info record.
pub fn kernel_add(
name: &'static str,
phys_pages: &PageSliceDescriptor<Physical>,
virt_pages: &PageSliceDescriptor<Virtual>,
attr: &AttributeFields,
) -> Result<(), &'static str> {
let mut m = &KERNEL_MAPPING_RECORD;
m.write(|mr| mr.add(name, phys_pages, virt_pages, attr))
}
pub fn kernel_find_and_insert_mmio_duplicate(
phys_mmio_descriptor: &MMIODescriptor<Physical>,
new_user: &'static str,
) -> Option<Address<Virtual>> {
let phys_pages: PageSliceDescriptor<Physical> = phys_mmio_descriptor.clone().into();
let mut m = &KERNEL_MAPPING_RECORD;
m.write(|mr| {
let dup = mr.find_duplicate(&phys_pages)?;
if let Err(x) = dup.add_user(new_user) {
warn!("{}", x);
}
Some(dup.virt_start_addr)
})
}
/// Human-readable print of all recorded kernel mappings.
pub fn kernel_print() {
let mut m = &KERNEL_MAPPING_RECORD;
m.read(|mr| mr.print());
}

@ -0,0 +1,283 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Memory Management Unit Types.
use crate::{bsp, common};
use core::{convert::From, marker::PhantomData, ops::RangeInclusive};
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
use super::interface::TranslationGranule;
/// Metadata trait for marking the type of an address.
pub trait AddressType: Copy + Clone + PartialOrd + PartialEq {}
/// Zero-sized type to mark a physical address.
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub enum Physical {}
/// Zero-sized type to mark a virtual address.
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub enum Virtual {}
/// Generic address type.
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub struct Address<ATYPE: AddressType> {
value: usize,
_address_type: PhantomData<ATYPE>,
}
/// Generic page type.
#[repr(C)]
pub struct Page<ATYPE: AddressType> {
inner: [u8; bsp::memory::mmu::KernelGranule::SIZE],
_address_type: PhantomData<ATYPE>,
}
/// Type describing a slice of pages.
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub struct PageSliceDescriptor<ATYPE: AddressType> {
start: Address<ATYPE>,
num_pages: usize,
}
/// Architecture agnostic memory attributes.
#[allow(missing_docs)]
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub enum MemAttributes {
CacheableDRAM,
Device,
}
/// Architecture agnostic access permissions.
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub enum AccessPermissions {
ReadOnly,
ReadWrite,
}
/// Collection of memory attributes.
#[allow(missing_docs)]
#[derive(Copy, Clone)]
pub struct AttributeFields {
pub mem_attributes: MemAttributes,
pub acc_perms: AccessPermissions,
pub execute_never: bool,
}
/// An MMIO descriptor for use in device drivers.
#[derive(Copy, Clone)]
pub struct MMIODescriptor<ATYPE: AddressType> {
start_addr: Address<ATYPE>,
size: usize,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
impl AddressType for Physical {}
impl AddressType for Virtual {}
//------------------------------------------------------------------------------
// Address
//------------------------------------------------------------------------------
impl<ATYPE: AddressType> Address<ATYPE> {
/// Create an instance.
pub const fn new(value: usize) -> Self {
Self {
value,
_address_type: PhantomData,
}
}
/// Align down.
pub const fn align_down(self, alignment: usize) -> Self {
let aligned = common::align_down(self.value, alignment);
Self {
value: aligned,
_address_type: PhantomData,
}
}
/// Converts `Address` into an usize.
pub const fn into_usize(self) -> usize {
self.value
}
}
impl<ATYPE: AddressType> core::ops::Add<usize> for Address<ATYPE> {
type Output = Self;
fn add(self, other: usize) -> Self {
Self {
value: self.value + other,
_address_type: PhantomData,
}
}
}
impl<ATYPE: AddressType> core::ops::Sub<usize> for Address<ATYPE> {
type Output = Self;
fn sub(self, other: usize) -> Self {
Self {
value: self.value - other,
_address_type: PhantomData,
}
}
}
//------------------------------------------------------------------------------
// Page
//------------------------------------------------------------------------------
impl<ATYPE: AddressType> Page<ATYPE> {
/// Get a pointer to the instance.
pub const fn as_ptr(&self) -> *const Page<ATYPE> {
self as *const _
}
}
//------------------------------------------------------------------------------
// PageSliceDescriptor
//------------------------------------------------------------------------------
impl<ATYPE: AddressType> PageSliceDescriptor<ATYPE> {
/// Create an instance.
pub const fn from_addr(start: Address<ATYPE>, num_pages: usize) -> Self {
assert!(common::is_aligned(
start.into_usize(),
bsp::memory::mmu::KernelGranule::SIZE
));
assert!(num_pages > 0);
Self { start, num_pages }
}
/// Return a pointer to the first page of the described slice.
const fn first_page_ptr(&self) -> *const Page<ATYPE> {
self.start.into_usize() as *const _
}
/// Return the number of Pages the slice describes.
pub const fn num_pages(&self) -> usize {
self.num_pages
}
/// Return the memory size this descriptor spans.
pub const fn size(&self) -> usize {
self.num_pages * bsp::memory::mmu::KernelGranule::SIZE
}
/// Return the start address.
pub const fn start_addr(&self) -> Address<ATYPE> {
self.start
}
/// Return the exclusive end address.
pub fn end_addr(&self) -> Address<ATYPE> {
self.start + self.size()
}
/// Return the inclusive end address.
pub fn end_addr_inclusive(&self) -> Address<ATYPE> {
self.start + (self.size() - 1)
}
/// Return a non-mutable slice of Pages.
///
/// # Safety
///
/// - Same as applies for `core::slice::from_raw_parts`.
pub unsafe fn as_slice(&self) -> &[Page<ATYPE>] {
core::slice::from_raw_parts(self.first_page_ptr(), self.num_pages)
}
/// Return the inclusive address range of the slice.
pub fn into_usize_range_inclusive(self) -> RangeInclusive<usize> {
RangeInclusive::new(
self.start_addr().into_usize(),
self.end_addr_inclusive().into_usize(),
)
}
}
impl From<PageSliceDescriptor<Virtual>> for PageSliceDescriptor<Physical> {
fn from(desc: PageSliceDescriptor<Virtual>) -> Self {
Self {
start: Address::new(desc.start.into_usize()),
num_pages: desc.num_pages,
}
}
}
impl<ATYPE: AddressType> From<MMIODescriptor<ATYPE>> for PageSliceDescriptor<ATYPE> {
fn from(desc: MMIODescriptor<ATYPE>) -> Self {
let start_page_addr = desc
.start_addr
.align_down(bsp::memory::mmu::KernelGranule::SIZE);
let len = ((desc.end_addr_inclusive().into_usize() - start_page_addr.into_usize())
>> bsp::memory::mmu::KernelGranule::SHIFT)
+ 1;
Self {
start: start_page_addr,
num_pages: len,
}
}
}
//------------------------------------------------------------------------------
// MMIODescriptor
//------------------------------------------------------------------------------
impl<ATYPE: AddressType> MMIODescriptor<ATYPE> {
/// Create an instance.
pub const fn new(start_addr: Address<ATYPE>, size: usize) -> Self {
assert!(size > 0);
Self { start_addr, size }
}
/// Return the start address.
pub const fn start_addr(&self) -> Address<ATYPE> {
self.start_addr
}
/// Return the inclusive end address.
pub fn end_addr_inclusive(&self) -> Address<ATYPE> {
self.start_addr + (self.size - 1)
}
/// Return the size.
pub const fn size(&self) -> usize {
self.size
}
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Check if the size of `struct Page` is as expected.
#[kernel_test]
fn size_of_page_equals_granule_size() {
assert_eq!(
core::mem::size_of::<Page<Physical>>(),
bsp::memory::mmu::KernelGranule::SIZE
);
}
}

@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! A panic handler that infinitely waits.
use crate::{bsp, cpu, exception};
use core::{fmt, panic::PanicInfo};
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
fn _panic_print(args: fmt::Arguments) {
use fmt::Write;
unsafe { bsp::console::panic_console_out().write_fmt(args).unwrap() };
}
/// The point of exit for the "standard" (non-testing) `libkernel`.
///
/// This code will be used by the release kernel binary and the `integration tests`. It is linked
/// weakly, so that the integration tests can overload it to exit `QEMU` instead of spinning
/// forever.
///
/// This is one possible approach to solve the problem that `cargo` can not know who the consumer of
/// the library will be:
/// - The release kernel binary that should safely park the paniced core,
/// - or an `integration test` that is executed in QEMU, which should just exit QEMU.
#[cfg(not(test))]
#[linkage = "weak"]
#[no_mangle]
fn _panic_exit() -> ! {
cpu::wait_forever()
}
/// Prints with a newline - only use from the panic handler.
///
/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html
#[macro_export]
macro_rules! panic_println {
($($arg:tt)*) => ({
_panic_print(format_args_nl!($($arg)*));
})
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
unsafe { exception::asynchronous::local_irq_mask() };
if let Some(args) = info.message() {
panic_println!("\nKernel panic: {}", args);
} else {
panic_println!("\nKernel panic!");
}
_panic_exit()
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
/// The point of exit when the library is compiled for testing.
#[cfg(test)]
#[no_mangle]
fn _panic_exit() -> ! {
cpu::qemu_exit_failure()
}

@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Printing facilities.
use crate::{bsp, console};
use core::fmt;
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use console::interface::Write;
bsp::console::console().write_fmt(args).unwrap();
}
/// Prints without a newline.
///
/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*)));
}
/// Prints with a newline.
///
/// Carbon copy from https://doc.rust-lang.org/src/std/macros.rs.html
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ({
$crate::print::_print(format_args_nl!($($arg)*));
})
}
/// Prints an info, with a newline.
#[macro_export]
macro_rules! info {
($string:expr) => ({
#[allow(unused_imports)]
use crate::time::interface::TimeManager;
let timestamp = $crate::time::time_manager().uptime();
let timestamp_subsec_us = timestamp.subsec_micros();
$crate::print::_print(format_args_nl!(
concat!("[ {:>3}.{:03}{:03}] ", $string),
timestamp.as_secs(),
timestamp_subsec_us / 1_000,
timestamp_subsec_us % 1_000
));
});
($format_string:expr, $($arg:tt)*) => ({
#[allow(unused_imports)]
use crate::time::interface::TimeManager;
let timestamp = $crate::time::time_manager().uptime();
let timestamp_subsec_us = timestamp.subsec_micros();
$crate::print::_print(format_args_nl!(
concat!("[ {:>3}.{:03}{:03}] ", $format_string),
timestamp.as_secs(),
timestamp_subsec_us / 1_000,
timestamp_subsec_us % 1_000,
$($arg)*
));
})
}
/// Prints a warning, with a newline.
#[macro_export]
macro_rules! warn {
($string:expr) => ({
#[allow(unused_imports)]
use crate::time::interface::TimeManager;
let timestamp = $crate::time::time_manager().uptime();
let timestamp_subsec_us = timestamp.subsec_micros();
$crate::print::_print(format_args_nl!(
concat!("[W {:>3}.{:03}{:03}] ", $string),
timestamp.as_secs(),
timestamp_subsec_us / 1_000,
timestamp_subsec_us % 1_000
));
});
($format_string:expr, $($arg:tt)*) => ({
#[allow(unused_imports)]
use crate::time::interface::TimeManager;
let timestamp = $crate::time::time_manager().uptime();
let timestamp_subsec_us = timestamp.subsec_micros();
$crate::print::_print(format_args_nl!(
concat!("[W {:>3}.{:03}{:03}] ", $format_string),
timestamp.as_secs(),
timestamp_subsec_us / 1_000,
timestamp_subsec_us % 1_000,
$($arg)*
));
})
}

@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2020 Andre Richter <andre.o.richter@gmail.com>
//! Rust runtime initialization code.
use crate::{bsp, memory};
//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------
/// Zero out the .bss section.
///
/// # Safety
///
/// - Must only be called pre `kernel_init()`.
#[inline(always)]
unsafe fn zero_bss() {
memory::zero_volatile(bsp::memory::bss_range());
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Equivalent to `crt0` or `c0` code in C/C++ world. Clears the `bss` section, then jumps to kernel
/// init code.
///
/// # Safety
///
/// - Only a single core must be active and running this function.
pub unsafe fn runtime_init() -> ! {
extern "Rust" {
fn kernel_init() -> !;
}
zero_bss();
kernel_init()
}
//--------------------------------------------------------------------------------------------------
// Testing
//--------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use test_macros::kernel_test;
/// Check `bss` section layout.
#[kernel_test]
fn bss_section_is_sane() {
use core::mem;
let start = bsp::memory::bss_range().start as *const _ as usize;
let end = bsp::memory::bss_range().end as *const _ as usize;
assert_eq!(start % mem::size_of::<usize>(), 0);
assert_eq!(end % mem::size_of::<usize>(), 0);
assert!(end >= start);
}
}

@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! State information about the kernel itself.
use core::sync::atomic::{AtomicU8, Ordering};
//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------
/// Different stages in the kernel execution.
#[derive(Copy, Clone, Eq, PartialEq)]
enum State {
/// The kernel starts booting in this state.
Init,
/// The kernel transitions to this state when jumping to `kernel_main()` (at the end of
/// `kernel_init()`, after all init calls are done).
SingleCoreMain,
/// The kernel transitions to this state when it boots the secondary cores, aka switches
/// exectution mode to symmetric multiprocessing (SMP).
MultiCoreMain,
}
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Maintains the kernel state and state transitions.
pub struct StateManager(AtomicU8);
//--------------------------------------------------------------------------------------------------
// Global instances
//--------------------------------------------------------------------------------------------------
static STATE_MANAGER: StateManager = StateManager::new();
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// Return a reference to the global StateManager.
pub fn state_manager() -> &'static StateManager {
&STATE_MANAGER
}
impl StateManager {
const INIT: u8 = 0;
const SINGLE_CORE_MAIN: u8 = 1;
const MULTI_CORE_MAIN: u8 = 2;
/// Create a new instance.
pub const fn new() -> Self {
Self(AtomicU8::new(Self::INIT))
}
/// Return the current state.
fn state(&self) -> State {
let state = self.0.load(Ordering::Acquire);
match state {
Self::INIT => State::Init,
Self::SINGLE_CORE_MAIN => State::SingleCoreMain,
Self::MULTI_CORE_MAIN => State::MultiCoreMain,
_ => panic!("Invalid KERNEL_STATE"),
}
}
/// Return if the kernel is still in an init state.
pub fn is_init(&self) -> bool {
self.state() == State::Init
}
/// Transition from Init to SingleCoreMain.
pub fn transition_to_single_core_main(&self) {
if self
.0
.compare_exchange(
Self::INIT,
Self::SINGLE_CORE_MAIN,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_err()
{
panic!("transition_to_single_core_main() called while state != Init");
}
}
}

@ -0,0 +1,151 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Synchronization primitives.
use core::cell::UnsafeCell;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Synchronization interfaces.
pub mod interface {
/// Any object implementing this trait guarantees exclusive access to the data contained within
/// the Mutex for the duration of the provided closure.
///
/// The trait follows the [Rust embedded WG's proposal] and therefore provides some goodness
/// such as [deadlock prevention].
///
/// # 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:
///
/// [Rust embedded WG's proposal]: https://github.com/rust-embedded/wg/blob/master/rfcs/0377-mutex-trait.md
/// [deadlock prevention]: https://github.com/rust-embedded/wg/blob/master/rfcs/0377-mutex-trait.md#design-decisions-and-compatibility
///
/// ```
/// static MUT: Mutex<RefCell<i32>> = Mutex::new(RefCell::new(0));
///
/// fn foo() {
/// let mut r = &MUT; // Note that r is mutable
/// r.lock(|data| *data += 1);
/// }
/// ```
pub trait Mutex {
/// The type of encapsulated data.
type Data;
/// Creates a critical section and grants temporary mutable access to the encapsulated data.
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
}
/// A reader-writer exclusion type.
///
/// The implementing object allows either a number of readers or at most one writer at any point
/// in time.
pub trait ReadWriteEx {
/// The type of encapsulated data.
type Data;
/// Grants temporary mutable access to the encapsulated data.
fn write<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
/// Grants temporary immutable access to the encapsulated data.
fn read<R>(&mut self, f: impl FnOnce(&Self::Data) -> R) -> R;
}
}
/// A pseudo-lock for teaching purposes.
///
/// Used to introduce [interior mutability].
///
/// In contrast to a real Mutex implementation, does not protect against concurrent access from
/// other cores to the contained data. This part is preserved for later lessons.
///
/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is
/// executing on a single core.
///
/// [interior mutability]: https://doc.rust-lang.org/std/cell/index.html
pub struct IRQSafeNullLock<T: ?Sized> {
data: UnsafeCell<T>,
}
/// A pseudo-lock that is RW during the single-core kernel init phase and RO afterwards.
///
/// Intended to encapsulate data that is populated during kernel init when no concurrency exists.
pub struct InitStateLock<T: ?Sized> {
data: UnsafeCell<T>,
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
unsafe impl<T: ?Sized> Sync for IRQSafeNullLock<T> {}
impl<T> IRQSafeNullLock<T> {
/// Wraps `data` into a new `IRQSafeNullLock`.
pub const fn new(data: T) -> Self {
Self {
data: UnsafeCell::new(data),
}
}
}
unsafe impl<T: ?Sized> Sync for InitStateLock<T> {}
impl<T> InitStateLock<T> {
pub const fn new(data: T) -> Self {
Self {
data: UnsafeCell::new(data),
}
}
}
//------------------------------------------------------------------------------
// OS Interface Code
//------------------------------------------------------------------------------
use crate::{exception, state};
impl<T> interface::Mutex for &IRQSafeNullLock<T> {
type Data = T;
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
// In a real lock, there would be code encapsulating this line that ensures that this
// mutable reference will ever only be given out once at a time.
let data = unsafe { &mut *self.data.get() };
// Execute the closure while IRQs are masked.
exception::asynchronous::exec_with_irq_masked(|| f(data))
}
}
impl<T> interface::ReadWriteEx for &InitStateLock<T> {
type Data = T;
fn write<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
assert!(
state::state_manager().is_init(),
"InitStateLock::write called after kernel init phase"
);
assert!(
!exception::asynchronous::is_local_irq_masked(),
"InitStateLock::write called with IRQs unmasked"
);
let data = unsafe { &mut *self.data.get() };
f(data)
}
fn read<R>(&mut self, f: impl FnOnce(&Self::Data) -> R) -> R {
let data = unsafe { &*self.data.get() };
f(data)
}
}

@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2020 Andre Richter <andre.o.richter@gmail.com>
//! Timer primitives.
#[cfg(target_arch = "aarch64")]
#[path = "_arch/aarch64/time.rs"]
mod arch_time;
pub use arch_time::*;
//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------
/// Timekeeping interfaces.
pub mod interface {
use core::time::Duration;
/// Time management functions.
///
/// The `BSP` is supposed to supply one global instance.
pub trait TimeManager {
/// The timer's resolution.
fn resolution(&self) -> Duration;
/// The uptime since power-on of the device.
///
/// This includes time consumed by firmware and bootloaders.
fn uptime(&self) -> Duration;
/// Spin for a given duration.
fn spin_for(&self, duration: Duration);
}
}

@ -0,0 +1,14 @@
[package]
name = "test-macros"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.x"
quote = "1.x"
syn = { version = "1.x", features = ["full"] }
test-types = { path = "../test-types" }

@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2019-2020 Andre Richter <andre.o.richter@gmail.com>
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemFn};
#[proc_macro_attribute]
pub fn kernel_test(_attr: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
let test_name = &format!("{}", f.sig.ident.to_string());
let test_ident = Ident::new(
&format!("{}_TEST_CONTAINER", f.sig.ident.to_string().to_uppercase()),
Span::call_site(),
);
let test_code_block = f.block;
quote!(
#[test_case]
const #test_ident: test_types::UnitTest = test_types::UnitTest {
name: #test_name,
test_func: || #test_code_block,
};
)
.into()
}

@ -0,0 +1,5 @@
[package]
name = "test-types"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
edition = "2018"

@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2019-2020 Andre Richter <andre.o.richter@gmail.com>
//! Types for the `custom_test_frameworks` implementation.
#![no_std]
/// Unit test container.
pub struct UnitTest {
/// Name of the test.
pub name: &'static str,
/// Function pointer to the test.
pub test_func: fn(),
}

@ -0,0 +1,50 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2019-2020 Andre Richter <andre.o.richter@gmail.com>
require 'expect'
TIMEOUT_SECS = 3
# Verify sending and receiving works as expected.
class TxRxHandshake
def name
'Transmit and Receive handshake'
end
def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC')
raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil?
end
end
# Check for correct TX statistics implementation. Depends on test 1 being run first.
class TxStatistics
def name
'Transmit statistics'
end
def run(qemu_out, _qemu_in)
raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil?
end
end
# Check for correct RX statistics implementation. Depends on test 1 being run first.
class RxStatistics
def name
'Receive statistics'
end
def run(qemu_out, _qemu_in)
raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil?
end
end
##--------------------------------------------------------------------------------------------------
## Test registration
##--------------------------------------------------------------------------------------------------
def subtest_collection
[TxRxHandshake.new, TxStatistics.new, RxStatistics.new]
end

@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2019-2020 Andre Richter <andre.o.richter@gmail.com>
//! Console sanity tests - RX, TX and statistics.
#![feature(format_args_nl)]
#![no_main]
#![no_std]
mod panic_exit_failure;
use libkernel::{bsp, console, print};
#[no_mangle]
unsafe fn kernel_init() -> ! {
use bsp::console::{console, qemu_bring_up_console};
use console::interface::*;
qemu_bring_up_console();
// Handshake
assert_eq!(console().read_char(), 'A');
assert_eq!(console().read_char(), 'B');
assert_eq!(console().read_char(), 'C');
print!("OK1234");
// 6
print!("{}", console().chars_written());
// 3
print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness.
loop {}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save