diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 6636f007..46890643 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -5,8 +5,6 @@ # # Copyright (c) 2018-2021 Andre Richter -require 'rubygems' -require 'bundler/setup' require_relative '../utils/devtool/copyright' def copyright_check(staged_files) @@ -14,6 +12,7 @@ def copyright_check(staged_files) staged_files = staged_files.select do |f| next if f.include?('build.rs') + next if f.include?('boot_test_string.rb') f.include?('Makefile') || f.include?('Dockerfile') || diff --git a/01_wait_forever/Makefile b/01_wait_forever/Makefile index 2e44e7ac..16607fe6 100644 --- a/01_wait_forever/Makefile +++ b/01_wait_forever/Makefile @@ -2,12 +2,22 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# Default to the RPi3. BSP ?= rpi3 -# BSP-specific arguments + + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -53,61 +70,99 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ --section .text \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json diff --git a/02_runtime_init/Makefile b/02_runtime_init/Makefile index a20b283b..ea979232 100644 --- a/02_runtime_init/Makefile +++ b/02_runtime_init/Makefile @@ -2,12 +2,22 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# Default to the RPi3. BSP ?= rpi3 -# BSP-specific arguments + + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -53,51 +70,84 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -106,10 +156,15 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json diff --git a/02_runtime_init/README.md b/02_runtime_init/README.md index 11ad00cc..d37b3c9f 100644 --- a/02_runtime_init/README.md +++ b/02_runtime_init/README.md @@ -53,7 +53,7 @@ diff -uNr 01_wait_forever/Cargo.toml 02_runtime_init/Cargo.toml diff -uNr 01_wait_forever/Makefile 02_runtime_init/Makefile --- 01_wait_forever/Makefile +++ 02_runtime_init/Makefile -@@ -102,6 +102,8 @@ +@@ -152,6 +152,8 @@ $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ --section .text \ @@ -61,7 +61,7 @@ diff -uNr 01_wait_forever/Makefile 02_runtime_init/Makefile + --section .got \ $(KERNEL_ELF) | rustfilt - nm: $(KERNEL_ELF) + ##------------------------------------------------------------------------------ diff -uNr 01_wait_forever/src/_arch/aarch64/cpu/boot.rs 02_runtime_init/src/_arch/aarch64/cpu/boot.rs --- 01_wait_forever/src/_arch/aarch64/cpu/boot.rs diff --git a/03_hacky_hello_world/Makefile b/03_hacky_hello_world/Makefile index 2ed82a5f..19e50b33 100644 --- a/03_hacky_hello_world/Makefile +++ b/03_hacky_hello_world/Makefile @@ -2,12 +2,22 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# Default to the RPi3. BSP ?= rpi3 -# BSP-specific arguments + + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -53,51 +70,87 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -106,10 +159,40 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/03_hacky_hello_world/README.md b/03_hacky_hello_world/README.md index 01bbf54c..ce262bc9 100644 --- a/03_hacky_hello_world/README.md +++ b/03_hacky_hello_world/README.md @@ -12,6 +12,10 @@ - `src/console.rs` introduces interface `Traits` for console commands. - `src/bsp/raspberrypi/console.rs` implements the interface for QEMU's emulated UART. - The panic handler makes use of the new `print!()` to display user error messages. +- There is a new Makefile target, `make test`, intended for automated testing. It boots the compiled + kernel in `QEMU`, and checks for an expected output string produced by the kernel. + - In this tutorial, it checks for the string `Stopping here`, which is emitted by the `panic!()` + at the end of `main.rs`. ## Test it @@ -43,7 +47,7 @@ diff -uNr 02_runtime_init/Cargo.toml 03_hacky_hello_world/Cargo.toml diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile --- 02_runtime_init/Makefile +++ 03_hacky_hello_world/Makefile -@@ -13,7 +13,7 @@ +@@ -23,7 +23,7 @@ KERNEL_BIN = kernel8.img QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE = raspi3 @@ -52,7 +56,7 @@ diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf -@@ -24,7 +24,7 @@ +@@ -34,7 +34,7 @@ KERNEL_BIN = kernel8.img QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE = @@ -61,6 +65,60 @@ diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf +@@ -70,17 +70,20 @@ + --strip-all \ + -O binary + +-EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) ++EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) ++EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb + + ##------------------------------------------------------------------------------ + ## Dockerization + ##------------------------------------------------------------------------------ +-DOCKER_IMAGE = rustembedded/osdev-utils +-DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +-DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i ++DOCKER_IMAGE = rustembedded/osdev-utils ++DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial ++DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i ++DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common + + DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) + DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) ++DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + + + +@@ -168,3 +171,28 @@ + ##------------------------------------------------------------------------------ + check: + @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json ++ ++ ++ ++##-------------------------------------------------------------------------------------------------- ++## Testing targets ++##-------------------------------------------------------------------------------------------------- ++.PHONY: test test_boot ++ ++ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. ++ ++test_boot test : ++ $(call colorecho, "\n$(QEMU_MISSING_STRING)") ++ ++else # QEMU is supported. ++ ++##------------------------------------------------------------------------------ ++## Run boot test ++##------------------------------------------------------------------------------ ++test_boot: $(KERNEL_BIN) ++ $(call colorecho, "\nBoot test - $(BSP)") ++ @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) ++ ++test: test_boot ++ ++endif diff -uNr 02_runtime_init/src/bsp/raspberrypi/console.rs 03_hacky_hello_world/src/bsp/raspberrypi/console.rs --- 02_runtime_init/src/bsp/raspberrypi/console.rs @@ -245,4 +303,12 @@ diff -uNr 02_runtime_init/src/print.rs 03_hacky_hello_world/src/print.rs + }) +} +diff -uNr 02_runtime_init/tests/boot_test_string.rb 03_hacky_hello_world/tests/boot_test_string.rb +--- 02_runtime_init/tests/boot_test_string.rb ++++ 03_hacky_hello_world/tests/boot_test_string.rb +@@ -0,0 +1,3 @@ ++# frozen_string_literal: true ++ ++EXPECTED_PRINT = 'Stopping here' + ``` diff --git a/03_hacky_hello_world/tests/boot_test_string.rb b/03_hacky_hello_world/tests/boot_test_string.rb new file mode 100644 index 00000000..b0c59536 --- /dev/null +++ b/03_hacky_hello_world/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Stopping here' diff --git a/04_safe_globals/Makefile b/04_safe_globals/Makefile index 2ed82a5f..19e50b33 100644 --- a/04_safe_globals/Makefile +++ b/04_safe_globals/Makefile @@ -2,12 +2,22 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# Default to the RPi3. BSP ?= rpi3 -# BSP-specific arguments + + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -53,51 +70,87 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -106,10 +159,40 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/04_safe_globals/tests/boot_test_string.rb b/04_safe_globals/tests/boot_test_string.rb new file mode 100644 index 00000000..b0c59536 --- /dev/null +++ b/04_safe_globals/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Stopping here' diff --git a/05_drivers_gpio_uart/Makefile b/05_drivers_gpio_uart/Makefile index 1dd6cc2b..c38e049e 100644 --- a/05_drivers_gpio_uart/Makefile +++ b/05_drivers_gpio_uart/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -59,64 +73,102 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINITERM = ruby ../common/serial/miniterm.rb -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t -DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils -DOCKER_ARG_DEV = --privileged -v /dev:/dev +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) - DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) + DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -EXEC_MINITERM = ruby ../utils/miniterm.rb + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) endif +##------------------------------------------------------------------------------ +## Connect to the target's serial +##------------------------------------------------------------------------------ miniterm: @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -125,10 +177,40 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/05_drivers_gpio_uart/README.md b/05_drivers_gpio_uart/README.md index 7f0bbda5..2ee89aa6 100644 --- a/05_drivers_gpio_uart/README.md +++ b/05_drivers_gpio_uart/README.md @@ -145,55 +145,64 @@ diff -uNr 04_safe_globals/Cargo.toml 05_drivers_gpio_uart/Cargo.toml diff -uNr 04_safe_globals/Makefile 05_drivers_gpio_uart/Makefile --- 04_safe_globals/Makefile +++ 05_drivers_gpio_uart/Makefile -@@ -7,6 +7,12 @@ - # Default to the RPi3 +@@ -11,6 +11,9 @@ + # 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 -@@ -58,13 +64,23 @@ - DOCKER_IMAGE = rustembedded/osdev-utils - DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial - DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t -+DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils -+DOCKER_ARG_DEV = --privileged -v /dev:/dev + + + ##-------------------------------------------------------------------------------------------------- +@@ -72,6 +75,7 @@ + + EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) + EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb ++EXEC_MINITERM = ruby ../common/serial/miniterm.rb + + ##------------------------------------------------------------------------------ + ## Dockerization +@@ -80,17 +84,25 @@ + DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial + DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i + DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common ++DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) + DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) --EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -+# Dockerize commands that require USB device passthrough only on Linux -+ifeq ($(UNAME_S),Linux) ++# Dockerize commands, which require USB device passthrough, only on Linux. ++ifeq ($(shell uname -s),Linux) + DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) + -+ DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) ++ DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +endif + -+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -+EXEC_MINITERM = ruby ../utils/miniterm.rb + + ##-------------------------------------------------------------------------------------------------- + ## Targets + ##-------------------------------------------------------------------------------------------------- -.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check all: $(KERNEL_BIN) -@@ -88,6 +104,9 @@ - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) +@@ -130,6 +142,12 @@ endif + ##------------------------------------------------------------------------------ ++## Connect to the target's serial ++##------------------------------------------------------------------------------ +miniterm: + @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) + ++##------------------------------------------------------------------------------ + ## Run clippy + ##------------------------------------------------------------------------------ clippy: - @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) - diff -uNr 04_safe_globals/src/_arch/aarch64/cpu.rs 05_drivers_gpio_uart/src/_arch/aarch64/cpu.rs --- 04_safe_globals/src/_arch/aarch64/cpu.rs @@ -1451,4 +1460,13 @@ diff -uNr 04_safe_globals/src/panic_wait.rs 05_drivers_gpio_uart/src/panic_wait. cpu::wait_forever() +diff -uNr 04_safe_globals/tests/boot_test_string.rb 05_drivers_gpio_uart/tests/boot_test_string.rb +--- 04_safe_globals/tests/boot_test_string.rb ++++ 05_drivers_gpio_uart/tests/boot_test_string.rb +@@ -1,3 +1,3 @@ + # frozen_string_literal: true + +-EXPECTED_PRINT = 'Stopping here' ++EXPECTED_PRINT = 'Echoing input now' + ``` diff --git a/05_drivers_gpio_uart/tests/boot_test_string.rb b/05_drivers_gpio_uart/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/05_drivers_gpio_uart/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/06_uart_chainloader/Makefile b/06_uart_chainloader/Makefile index a8d38ffc..3064e67a 100644 --- a/06_uart_chainloader/Makefile +++ b/06_uart_chainloader/Makefile @@ -2,49 +2,63 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 - OBJDUMP_BINARY = aarch64-none-elf-objdump - NM_BINARY = aarch64-none-elf-nm - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 + 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 + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + LINKER_FILE = src/bsp/raspberrypi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img 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 - OBJDUMP_BINARY = aarch64-none-elf-objdump - NM_BINARY = aarch64-none-elf-nm - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN = kernel8.img + QEMU_BINARY = qemu-system-aarch64 + QEMU_MACHINE_TYPE = + QEMU_RELEASE_ARGS = -serial stdio -display none + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + LINKER_FILE = src/bsp/raspberrypi/link.ld + RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -61,49 +75,69 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t -DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils -DOCKER_ARG_DEV = --privileged -v /dev:/dev +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) + DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -EXEC_MINIPUSH = ruby ../utils/minipush.rb -EXEC_QEMU_MINIPUSH = ruby tests/qemu_minipush.rb -.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu qemuasm chainboot clippy clean readelf objdump nm \ - check + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu qemuasm: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @@ -112,26 +146,36 @@ qemuasm: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU with ASM output") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm -test: $(KERNEL_BIN) - $(call colorecho, "\nTesting chainloading - $(BSP)") - @$(DOCKER_TEST) $(EXEC_QEMU_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ - -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) - endif -chainboot: +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ +chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -140,10 +184,41 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ + -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) + +test: test_boot + +endif diff --git a/06_uart_chainloader/README.md b/06_uart_chainloader/README.md index 0f9e4aa6..d606f536 100644 --- a/06_uart_chainloader/README.md +++ b/06_uart_chainloader/README.md @@ -137,57 +137,95 @@ Binary files 05_drivers_gpio_uart/demo_payload_rpi4.img and 06_uart_chainloader/ diff -uNr 05_drivers_gpio_uart/Makefile 06_uart_chainloader/Makefile --- 05_drivers_gpio_uart/Makefile +++ 06_uart_chainloader/Makefile -@@ -25,6 +25,7 @@ - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 +@@ -22,27 +22,29 @@ + + # 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 +- OBJDUMP_BINARY = aarch64-none-elf-objdump +- NM_BINARY = aarch64-none-elf-nm +- READELF_BINARY = aarch64-none-elf-readelf +- LINKER_FILE = src/bsp/raspberrypi/link.ld +- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 ++ 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 ++ OBJDUMP_BINARY = aarch64-none-elf-objdump ++ NM_BINARY = aarch64-none-elf-nm ++ READELF_BINARY = aarch64-none-elf-readelf ++ LINKER_FILE = src/bsp/raspberrypi/link.ld ++ RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 + CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img else ifeq ($(BSP),rpi4) - TARGET = aarch64-unknown-none-softfloat - KERNEL_BIN = kernel8.img -@@ -36,6 +37,7 @@ - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +- TARGET = aarch64-unknown-none-softfloat +- KERNEL_BIN = kernel8.img +- QEMU_BINARY = qemu-system-aarch64 +- QEMU_MACHINE_TYPE = +- QEMU_RELEASE_ARGS = -serial stdio -display none +- OBJDUMP_BINARY = aarch64-none-elf-objdump +- NM_BINARY = aarch64-none-elf-nm +- READELF_BINARY = aarch64-none-elf-readelf +- LINKER_FILE = src/bsp/raspberrypi/link.ld +- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 ++ TARGET = aarch64-unknown-none-softfloat ++ KERNEL_BIN = kernel8.img ++ QEMU_BINARY = qemu-system-aarch64 ++ QEMU_MACHINE_TYPE = ++ QEMU_RELEASE_ARGS = -serial stdio -display none ++ OBJDUMP_BINARY = aarch64-none-elf-objdump ++ NM_BINARY = aarch64-none-elf-nm ++ READELF_BINARY = aarch64-none-elf-readelf ++ LINKER_FILE = src/bsp/raspberrypi/link.ld ++ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 + CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img endif - # Export for build.rs -@@ -68,19 +70,22 @@ - DOCKER_ARG_DEV = --privileged -v /dev:/dev + QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +@@ -74,8 +76,8 @@ + -O binary - DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) -+DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) - DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) + EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +-EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +-EXEC_MINITERM = ruby ../common/serial/miniterm.rb ++EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb ++EXEC_MINIPUSH = ruby ../common/serial/minipush.rb - # Dockerize commands that require USB device passthrough only on Linux - ifeq ($(UNAME_S),Linux) + ##------------------------------------------------------------------------------ + ## Dockerization +@@ -94,7 +96,7 @@ + ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) -- DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) -+ DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) +- DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) ++ DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif --EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) --EXEC_MINITERM = ruby ../utils/miniterm.rb -+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -+EXEC_MINIPUSH = ruby ../utils/minipush.rb -+EXEC_QEMU_MINIPUSH = ruby tests/qemu_minipush.rb +@@ -102,7 +104,7 @@ + ##-------------------------------------------------------------------------------------------------- + ## Targets + ##-------------------------------------------------------------------------------------------------- -.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check -+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu qemuasm chainboot clippy clean readelf objdump nm \ -+ check ++.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) -@@ -96,16 +101,26 @@ - @$(DOC_CMD) --document-private-items --open +@@ -131,7 +133,7 @@ + ##------------------------------------------------------------------------------ + ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. - ifeq ($(QEMU_MACHINE_TYPE),) -qemu: -+qemu test: ++qemu qemuasm: $(call colorecho, "\n$(QEMU_MISSING_STRING)") - else + + else # QEMU is supported. +@@ -139,13 +141,18 @@ qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @@ -195,21 +233,30 @@ diff -uNr 05_drivers_gpio_uart/Makefile 06_uart_chainloader/Makefile +qemuasm: $(KERNEL_BIN) + $(call colorecho, "\nLaunching QEMU with ASM output") + @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm -+ -+test: $(KERNEL_BIN) -+ $(call colorecho, "\nTesting chainloading - $(BSP)") -+ @$(DOCKER_TEST) $(EXEC_QEMU_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ -+ -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) + endif + ##------------------------------------------------------------------------------ +-## Connect to the target's serial ++## Push the kernel to the real HW target + ##------------------------------------------------------------------------------ -miniterm: - @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) -+chainboot: ++chainboot: $(KERNEL_BIN) + @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) - clippy: - @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) + ##------------------------------------------------------------------------------ + ## Run clippy +@@ -209,7 +216,8 @@ + ##------------------------------------------------------------------------------ + test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") +- @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) ++ @$(DOCKER_TEST) $(EXEC_TEST_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ ++ -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) + + test: test_boot + diff -uNr 05_drivers_gpio_uart/src/_arch/aarch64/cpu/boot.s 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s --- 05_drivers_gpio_uart/src/_arch/aarch64/cpu/boot.s @@ -492,9 +539,17 @@ diff -uNr 05_drivers_gpio_uart/src/main.rs 06_uart_chainloader/src/main.rs + kernel() } -diff -uNr 05_drivers_gpio_uart/tests/qemu_minipush.rb 06_uart_chainloader/tests/qemu_minipush.rb ---- 05_drivers_gpio_uart/tests/qemu_minipush.rb -+++ 06_uart_chainloader/tests/qemu_minipush.rb +diff -uNr 05_drivers_gpio_uart/tests/boot_test_string.rb 06_uart_chainloader/tests/boot_test_string.rb +--- 05_drivers_gpio_uart/tests/boot_test_string.rb ++++ 06_uart_chainloader/tests/boot_test_string.rb +@@ -1,3 +0,0 @@ +-# frozen_string_literal: true +- +-EXPECTED_PRINT = 'Echoing input now' + +diff -uNr 05_drivers_gpio_uart/tests/chainboot_test.rb 06_uart_chainloader/tests/chainboot_test.rb +--- 05_drivers_gpio_uart/tests/chainboot_test.rb ++++ 06_uart_chainloader/tests/chainboot_test.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + @@ -502,80 +557,80 @@ diff -uNr 05_drivers_gpio_uart/tests/qemu_minipush.rb 06_uart_chainloader/tests/ +# +# Copyright (c) 2020-2021 Andre Richter + -+require_relative '../../utils/minipush' -+require 'expect' -+require 'timeout' ++require_relative '../../common/serial/minipush' ++require_relative '../../common/tests/boot_test' ++require 'pty' + +# Match for the last print that 'demo_payload_rpiX.img' produces. +EXPECTED_PRINT = 'Echoing input now' + -+# The main class -+class QEMUMiniPush < MiniPush -+ TIMEOUT_SECS = 3 ++# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected ++# to a QEMU instance instead of a real HW. ++class ChainbootTest < BootTest ++ MINIPUSH = '../common/serial/minipush.rb' ++ MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now' + -+ # override -+ def initialize(qemu_cmd, binary_image_path) -+ super(nil, binary_image_path) ++ def initialize(qemu_cmd, payload_path) ++ super(qemu_cmd, EXPECTED_PRINT) ++ ++ @test_name = 'Boot test using Minipush' + -+ @qemu_cmd = qemu_cmd ++ @payload_path = payload_path + end + + private + -+ def quit_qemu_graceful -+ Timeout.timeout(5) do -+ pid = @target_serial.pid -+ Process.kill('TERM', pid) -+ Process.wait(pid) -+ end -+ end -+ + # override -+ def open_serial -+ @target_serial = IO.popen(@qemu_cmd, 'r+', err: '/dev/null') ++ def post_process_and_add_output(output) ++ temp = output.join.split("\r\n") + -+ # Ensure all output is immediately flushed to the device. -+ @target_serial.sync = true ++ # Should a line have solo carriage returns, remove any overridden parts of the string. ++ temp.map! { |x| x.gsub(/.*\r/, '') } + -+ puts "[#{@name_short}] ✅ Serial connected" ++ @test_output += temp + end + -+ # override -+ def terminal -+ result = @target_serial.expect(EXPECTED_PRINT, TIMEOUT_SECS) -+ exit(1) if result.nil? -+ -+ puts result -+ -+ quit_qemu_graceful ++ def wait_for_minipush_power_request(mp_out) ++ output = [] ++ Timeout.timeout(MAX_WAIT_SECS) do ++ loop do ++ output << mp_out.gets ++ break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST) ++ end ++ end ++ rescue Timeout::Error ++ @test_error = 'Timed out waiting for power request' ++ rescue StandardError => e ++ @test_error = e.message ++ ensure ++ post_process_and_add_output(output) + end + + # override -+ def connetion_reset; end ++ def setup ++ pty_main, pty_secondary = PTY.open ++ mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}") + -+ # override -+ def handle_reconnect(error) -+ handle_unexpected(error) ++ # Wait until MiniPush asks for powering the target. ++ wait_for_minipush_power_request(mp_out) ++ ++ # Now is the time to start QEMU with the chainloader binary. QEMU's virtual tty is connected ++ # to the MiniPush instance spawned above, so that the two processes talk to each other. ++ Process.spawn(@qemu_cmd, in: pty_main, out: pty_main) ++ ++ # The remainder of the test is done by the parent class' run_concrete_test, which listens on ++ # @qemu_serial. Hence, point it to MiniPush's output. ++ @qemu_serial = mp_out + end +end + +##-------------------------------------------------------------------------------------------------- +## Execution starts here +##-------------------------------------------------------------------------------------------------- -+puts -+puts 'QEMUMiniPush 1.0'.cyan -+puts -+ -+# CTRL + C handler. Only here to suppress Ruby's default exception print. -+trap('INT') do -+ # The `ensure` block from `QEMUMiniPush::run` will run after exit, restoring console state. -+ exit -+end -+ -+binary_image_path = ARGV.pop ++payload_path = ARGV.pop +qemu_cmd = ARGV.join(' ') + -+QEMUMiniPush.new(qemu_cmd, binary_image_path).run ++ChainbootTest.new(qemu_cmd, payload_path).run diff -uNr 05_drivers_gpio_uart/update.sh 06_uart_chainloader/update.sh --- 05_drivers_gpio_uart/update.sh diff --git a/06_uart_chainloader/tests/chainboot_test.rb b/06_uart_chainloader/tests/chainboot_test.rb new file mode 100644 index 00000000..3dee0f9c --- /dev/null +++ b/06_uart_chainloader/tests/chainboot_test.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2020-2021 Andre Richter + +require_relative '../../common/serial/minipush' +require_relative '../../common/tests/boot_test' +require 'pty' + +# Match for the last print that 'demo_payload_rpiX.img' produces. +EXPECTED_PRINT = 'Echoing input now' + +# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected +# to a QEMU instance instead of a real HW. +class ChainbootTest < BootTest + MINIPUSH = '../common/serial/minipush.rb' + MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now' + + def initialize(qemu_cmd, payload_path) + super(qemu_cmd, EXPECTED_PRINT) + + @test_name = 'Boot test using Minipush' + + @payload_path = payload_path + end + + private + + # override + def post_process_and_add_output(output) + temp = output.join.split("\r\n") + + # Should a line have solo carriage returns, remove any overridden parts of the string. + temp.map! { |x| x.gsub(/.*\r/, '') } + + @test_output += temp + end + + def wait_for_minipush_power_request(mp_out) + output = [] + Timeout.timeout(MAX_WAIT_SECS) do + loop do + output << mp_out.gets + break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST) + end + end + rescue Timeout::Error + @test_error = 'Timed out waiting for power request' + rescue StandardError => e + @test_error = e.message + ensure + post_process_and_add_output(output) + end + + # override + def setup + pty_main, pty_secondary = PTY.open + mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}") + + # Wait until MiniPush asks for powering the target. + wait_for_minipush_power_request(mp_out) + + # Now is the time to start QEMU with the chainloader binary. QEMU's virtual tty is connected + # to the MiniPush instance spawned above, so that the two processes talk to each other. + Process.spawn(@qemu_cmd, in: pty_main, out: pty_main) + + # The remainder of the test is done by the parent class' run_concrete_test, which listens on + # @qemu_serial. Hence, point it to MiniPush's output. + @qemu_serial = mp_out + end +end + +##-------------------------------------------------------------------------------------------------- +## Execution starts here +##-------------------------------------------------------------------------------------------------- +payload_path = ARGV.pop +qemu_cmd = ARGV.join(' ') + +ChainbootTest.new(qemu_cmd, payload_path).run diff --git a/06_uart_chainloader/tests/qemu_minipush.rb b/06_uart_chainloader/tests/qemu_minipush.rb deleted file mode 100644 index 73857cd6..00000000 --- a/06_uart_chainloader/tests/qemu_minipush.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2020-2021 Andre Richter - -require_relative '../../utils/minipush' -require 'expect' -require 'timeout' - -# Match for the last print that 'demo_payload_rpiX.img' produces. -EXPECTED_PRINT = 'Echoing input now' - -# The main class -class QEMUMiniPush < MiniPush - TIMEOUT_SECS = 3 - - # override - def initialize(qemu_cmd, binary_image_path) - super(nil, binary_image_path) - - @qemu_cmd = qemu_cmd - end - - private - - def quit_qemu_graceful - Timeout.timeout(5) do - pid = @target_serial.pid - Process.kill('TERM', pid) - Process.wait(pid) - end - end - - # override - def open_serial - @target_serial = IO.popen(@qemu_cmd, 'r+', err: '/dev/null') - - # Ensure all output is immediately flushed to the device. - @target_serial.sync = true - - puts "[#{@name_short}] ✅ Serial connected" - end - - # override - def terminal - result = @target_serial.expect(EXPECTED_PRINT, TIMEOUT_SECS) - exit(1) if result.nil? - - puts result - - quit_qemu_graceful - end - - # override - def connetion_reset; end - - # override - def handle_reconnect(error) - handle_unexpected(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Execution starts here -##-------------------------------------------------------------------------------------------------- -puts -puts 'QEMUMiniPush 1.0'.cyan -puts - -# CTRL + C handler. Only here to suppress Ruby's default exception print. -trap('INT') do - # The `ensure` block from `QEMUMiniPush::run` will run after exit, restoring console state. - exit -end - -binary_image_path = ARGV.pop -qemu_cmd = ARGV.join(' ') - -QEMUMiniPush.new(qemu_cmd, binary_image_path).run diff --git a/07_timestamps/Makefile b/07_timestamps/Makefile index b5a56d07..8336ccb7 100644 --- a/07_timestamps/Makefile +++ b/07_timestamps/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -59,64 +73,103 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t -DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils -DOCKER_ARG_DEV = --privileged -v /dev:/dev +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) + DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -EXEC_MINIPUSH = ruby ../utils/minipush.rb + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -125,10 +178,40 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/07_timestamps/README.md b/07_timestamps/README.md index 4e146bd0..4fb36ddf 100644 --- a/07_timestamps/README.md +++ b/07_timestamps/README.md @@ -62,76 +62,103 @@ Binary files 06_uart_chainloader/demo_payload_rpi4.img and 07_timestamps/demo_pa diff -uNr 06_uart_chainloader/Makefile 07_timestamps/Makefile --- 06_uart_chainloader/Makefile +++ 07_timestamps/Makefile -@@ -25,7 +25,6 @@ - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 +@@ -22,29 +22,27 @@ + + # 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 +- OBJDUMP_BINARY = aarch64-none-elf-objdump +- NM_BINARY = aarch64-none-elf-nm +- READELF_BINARY = aarch64-none-elf-readelf +- LINKER_FILE = src/bsp/raspberrypi/link.ld +- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 - CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img ++ 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 ++ OBJDUMP_BINARY = aarch64-none-elf-objdump ++ NM_BINARY = aarch64-none-elf-nm ++ READELF_BINARY = aarch64-none-elf-readelf ++ 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 -@@ -37,7 +36,6 @@ - READELF_BINARY = aarch64-none-elf-readelf - LINKER_FILE = src/bsp/raspberrypi/link.ld - RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +- TARGET = aarch64-unknown-none-softfloat +- KERNEL_BIN = kernel8.img +- QEMU_BINARY = qemu-system-aarch64 +- QEMU_MACHINE_TYPE = +- QEMU_RELEASE_ARGS = -serial stdio -display none +- OBJDUMP_BINARY = aarch64-none-elf-objdump +- NM_BINARY = aarch64-none-elf-nm +- READELF_BINARY = aarch64-none-elf-readelf +- LINKER_FILE = src/bsp/raspberrypi/link.ld +- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img ++ TARGET = aarch64-unknown-none-softfloat ++ KERNEL_BIN = kernel8.img ++ QEMU_BINARY = qemu-system-aarch64 ++ QEMU_MACHINE_TYPE = ++ QEMU_RELEASE_ARGS = -serial stdio -display none ++ OBJDUMP_BINARY = aarch64-none-elf-objdump ++ NM_BINARY = aarch64-none-elf-nm ++ READELF_BINARY = aarch64-none-elf-readelf ++ LINKER_FILE = src/bsp/raspberrypi/link.ld ++ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif - # Export for build.rs -@@ -70,7 +68,6 @@ - DOCKER_ARG_DEV = --privileged -v /dev:/dev + QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +@@ -76,7 +74,7 @@ + -O binary - DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) --DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) - DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) + EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +-EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb ++EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb + EXEC_MINIPUSH = ruby ../common/serial/minipush.rb - # Dockerize commands that require USB device passthrough only on Linux -@@ -80,12 +77,10 @@ - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) - endif - --EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) --EXEC_MINIPUSH = ruby ../utils/minipush.rb --EXEC_QEMU_MINIPUSH = ruby tests/qemu_minipush.rb -+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -+EXEC_MINIPUSH = ruby ../utils/minipush.rb - --.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu qemuasm chainboot clippy clean readelf objdump nm \ -- check -+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check - - all: $(KERNEL_BIN) + ##------------------------------------------------------------------------------ +@@ -133,7 +131,7 @@ + ##------------------------------------------------------------------------------ + ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. -@@ -101,26 +96,16 @@ - @$(DOC_CMD) --document-private-items --open - - ifeq ($(QEMU_MACHINE_TYPE),) --qemu test: +-qemu qemuasm: +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") - else - qemu: $(KERNEL_BIN) + + else # QEMU is supported. +@@ -142,17 +140,13 @@ $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -- + -qemuasm: $(KERNEL_BIN) - $(call colorecho, "\nLaunching QEMU with ASM output") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm -- --test: $(KERNEL_BIN) -- $(call colorecho, "\nTesting chainloading - $(BSP)") -- @$(DOCKER_TEST) $(EXEC_QEMU_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ -- -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) - endif --chainboot: + ##------------------------------------------------------------------------------ + ## Push the kernel to the real HW target + ##------------------------------------------------------------------------------ + chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) -+chainboot: $(KERNEL_BIN) + @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) - clippy: - @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) + ##------------------------------------------------------------------------------ + ## Run clippy +@@ -216,8 +210,7 @@ + ##------------------------------------------------------------------------------ + test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") +- @$(DOCKER_TEST) $(EXEC_TEST_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \ +- -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD) ++ @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + + test: test_boot + diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s 07_timestamps/src/_arch/aarch64/cpu/boot.s --- 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s @@ -720,9 +747,17 @@ diff -uNr 06_uart_chainloader/src/time.rs 07_timestamps/src/time.rs + } +} -diff -uNr 06_uart_chainloader/tests/qemu_minipush.rb 07_timestamps/tests/qemu_minipush.rb ---- 06_uart_chainloader/tests/qemu_minipush.rb -+++ 07_timestamps/tests/qemu_minipush.rb +diff -uNr 06_uart_chainloader/tests/boot_test_string.rb 07_timestamps/tests/boot_test_string.rb +--- 06_uart_chainloader/tests/boot_test_string.rb ++++ 07_timestamps/tests/boot_test_string.rb +@@ -0,0 +1,3 @@ ++# frozen_string_literal: true ++ ++EXPECTED_PRINT = 'Spinning for 1 second' + +diff -uNr 06_uart_chainloader/tests/chainboot_test.rb 07_timestamps/tests/chainboot_test.rb +--- 06_uart_chainloader/tests/chainboot_test.rb ++++ 07_timestamps/tests/chainboot_test.rb @@ -1,80 +0,0 @@ -# frozen_string_literal: true - @@ -730,80 +765,80 @@ diff -uNr 06_uart_chainloader/tests/qemu_minipush.rb 07_timestamps/tests/qemu_mi -# -# Copyright (c) 2020-2021 Andre Richter - --require_relative '../../utils/minipush' --require 'expect' --require 'timeout' +-require_relative '../../common/serial/minipush' +-require_relative '../../common/tests/boot_test' +-require 'pty' - -# Match for the last print that 'demo_payload_rpiX.img' produces. -EXPECTED_PRINT = 'Echoing input now' - --# The main class --class QEMUMiniPush < MiniPush -- TIMEOUT_SECS = 3 +-# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected +-# to a QEMU instance instead of a real HW. +-class ChainbootTest < BootTest +- MINIPUSH = '../common/serial/minipush.rb' +- MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now' - -- # override -- def initialize(qemu_cmd, binary_image_path) -- super(nil, binary_image_path) +- def initialize(qemu_cmd, payload_path) +- super(qemu_cmd, EXPECTED_PRINT) - -- @qemu_cmd = qemu_cmd +- @test_name = 'Boot test using Minipush' +- +- @payload_path = payload_path - end - - private - -- def quit_qemu_graceful -- Timeout.timeout(5) do -- pid = @target_serial.pid -- Process.kill('TERM', pid) -- Process.wait(pid) -- end -- end -- - # override -- def open_serial -- @target_serial = IO.popen(@qemu_cmd, 'r+', err: '/dev/null') +- def post_process_and_add_output(output) +- temp = output.join.split("\r\n") - -- # Ensure all output is immediately flushed to the device. -- @target_serial.sync = true +- # Should a line have solo carriage returns, remove any overridden parts of the string. +- temp.map! { |x| x.gsub(/.*\r/, '') } - -- puts "[#{@name_short}] ✅ Serial connected" +- @test_output += temp - end - -- # override -- def terminal -- result = @target_serial.expect(EXPECTED_PRINT, TIMEOUT_SECS) -- exit(1) if result.nil? -- -- puts result -- -- quit_qemu_graceful +- def wait_for_minipush_power_request(mp_out) +- output = [] +- Timeout.timeout(MAX_WAIT_SECS) do +- loop do +- output << mp_out.gets +- break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST) +- end +- end +- rescue Timeout::Error +- @test_error = 'Timed out waiting for power request' +- rescue StandardError => e +- @test_error = e.message +- ensure +- post_process_and_add_output(output) - end - - # override -- def connetion_reset; end +- def setup +- pty_main, pty_secondary = PTY.open +- mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}") - -- # override -- def handle_reconnect(error) -- handle_unexpected(error) +- # Wait until MiniPush asks for powering the target. +- wait_for_minipush_power_request(mp_out) +- +- # Now is the time to start QEMU with the chainloader binary. QEMU's virtual tty is connected +- # to the MiniPush instance spawned above, so that the two processes talk to each other. +- Process.spawn(@qemu_cmd, in: pty_main, out: pty_main) +- +- # The remainder of the test is done by the parent class' run_concrete_test, which listens on +- # @qemu_serial. Hence, point it to MiniPush's output. +- @qemu_serial = mp_out - end -end - -##-------------------------------------------------------------------------------------------------- -## Execution starts here -##-------------------------------------------------------------------------------------------------- --puts --puts 'QEMUMiniPush 1.0'.cyan --puts -- --# CTRL + C handler. Only here to suppress Ruby's default exception print. --trap('INT') do -- # The `ensure` block from `QEMUMiniPush::run` will run after exit, restoring console state. -- exit --end -- --binary_image_path = ARGV.pop +-payload_path = ARGV.pop -qemu_cmd = ARGV.join(' ') - --QEMUMiniPush.new(qemu_cmd, binary_image_path).run +-ChainbootTest.new(qemu_cmd, payload_path).run diff -uNr 06_uart_chainloader/update.sh 07_timestamps/update.sh --- 06_uart_chainloader/update.sh diff --git a/07_timestamps/tests/boot_test_string.rb b/07_timestamps/tests/boot_test_string.rb new file mode 100644 index 00000000..02c3c492 --- /dev/null +++ b/07_timestamps/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Spinning for 1 second' diff --git a/08_hw_debug_JTAG/Makefile b/08_hw_debug_JTAG/Makefile index e3dcaae8..0a2443ac 100644 --- a/08_hw_debug_JTAG/Makefile +++ b/08_hw_debug_JTAG/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 chainboot jtagboot openocd gdb gdb-opt0 clippy \ - clean readelf objdump nm check + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/08_hw_debug_JTAG/README.md b/08_hw_debug_JTAG/README.md index a30a296e..74a942a8 100644 --- a/08_hw_debug_JTAG/README.md +++ b/08_hw_debug_JTAG/README.md @@ -320,7 +320,7 @@ diff -uNr 07_timestamps/Cargo.toml 08_hw_debug_JTAG/Cargo.toml diff -uNr 07_timestamps/Makefile 08_hw_debug_JTAG/Makefile --- 07_timestamps/Makefile +++ 08_hw_debug_JTAG/Makefile -@@ -23,6 +23,8 @@ +@@ -30,6 +30,8 @@ OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf @@ -329,7 +329,7 @@ diff -uNr 07_timestamps/Makefile 08_hw_debug_JTAG/Makefile LINKER_FILE = src/bsp/raspberrypi/link.ld RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 else ifeq ($(BSP),rpi4) -@@ -34,6 +36,8 @@ +@@ -41,6 +43,8 @@ OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf @@ -338,56 +338,66 @@ diff -uNr 07_timestamps/Makefile 08_hw_debug_JTAG/Makefile LINKER_FILE = src/bsp/raspberrypi/link.ld RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -@@ -65,9 +69,12 @@ - DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial - DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +@@ -84,17 +88,24 @@ + DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial + DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i + DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common ++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_INTERACT) $(DOCKER_IMAGE) -+DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) + DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) ++DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) - # Dockerize commands that require USB device passthrough only on Linux -@@ -75,12 +82,17 @@ + # Dockerize commands, which require USB device passthrough, only on Linux. + ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) ++ DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 chainboot clippy clean readelf objdump nm check -+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy \ -+ clean readelf objdump nm check +@@ -193,6 +204,35 @@ - all: $(KERNEL_BIN) -@@ -107,6 +119,19 @@ - chainboot: $(KERNEL_BIN) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) ++##-------------------------------------------------------------------------------------------------- ++## Debugging targets ++##-------------------------------------------------------------------------------------------------- ++.PHONY: jtagboot openocd gdb gdb-opt0 ++ ++##------------------------------------------------------------------------------ ++## Push the JTAG boot image to the real HW target ++##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + ++##------------------------------------------------------------------------------ ++## Start OpenOCD session ++##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + ++##------------------------------------------------------------------------------ ++## Start GDB session ++##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + - clippy: - @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) ++ ++ + ##-------------------------------------------------------------------------------------------------- + ## Testing targets + ##-------------------------------------------------------------------------------------------------- ``` diff --git a/08_hw_debug_JTAG/tests/boot_test_string.rb b/08_hw_debug_JTAG/tests/boot_test_string.rb new file mode 100644 index 00000000..02c3c492 --- /dev/null +++ b/08_hw_debug_JTAG/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Spinning for 1 second' diff --git a/09_privilege_level/Makefile b/09_privilege_level/Makefile index e3dcaae8..0a2443ac 100644 --- a/09_privilege_level/Makefile +++ b/09_privilege_level/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 chainboot jtagboot openocd gdb gdb-opt0 clippy \ - clean readelf objdump nm check + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/09_privilege_level/README.md b/09_privilege_level/README.md index 4b4c538d..645ec210 100644 --- a/09_privilege_level/README.md +++ b/09_privilege_level/README.md @@ -552,4 +552,13 @@ diff -uNr 08_hw_debug_JTAG/src/main.rs 09_privilege_level/src/main.rs } } +diff -uNr 08_hw_debug_JTAG/tests/boot_test_string.rb 09_privilege_level/tests/boot_test_string.rb +--- 08_hw_debug_JTAG/tests/boot_test_string.rb ++++ 09_privilege_level/tests/boot_test_string.rb +@@ -1,3 +1,3 @@ + # frozen_string_literal: true + +-EXPECTED_PRINT = 'Spinning for 1 second' ++EXPECTED_PRINT = 'Echoing input now' + ``` diff --git a/09_privilege_level/tests/boot_test_string.rb b/09_privilege_level/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/09_privilege_level/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/10_virtual_mem_part1_identity_mapping/Makefile b/10_virtual_mem_part1_identity_mapping/Makefile index e3dcaae8..0a2443ac 100644 --- a/10_virtual_mem_part1_identity_mapping/Makefile +++ b/10_virtual_mem_part1_identity_mapping/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 chainboot jtagboot openocd gdb gdb-opt0 clippy \ - clean readelf objdump nm check + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/10_virtual_mem_part1_identity_mapping/tests/boot_test_string.rb b/10_virtual_mem_part1_identity_mapping/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/10_virtual_mem_part1_identity_mapping/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/11_exceptions_part1_groundwork/Makefile b/11_exceptions_part1_groundwork/Makefile index e3dcaae8..0a2443ac 100644 --- a/11_exceptions_part1_groundwork/Makefile +++ b/11_exceptions_part1_groundwork/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 chainboot jtagboot openocd gdb gdb-opt0 clippy \ - clean readelf objdump nm check + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/11_exceptions_part1_groundwork/README.md b/11_exceptions_part1_groundwork/README.md index df8ecd6f..629f5f09 100644 --- a/11_exceptions_part1_groundwork/README.md +++ b/11_exceptions_part1_groundwork/README.md @@ -1016,4 +1016,13 @@ diff -uNr 10_virtual_mem_part1_identity_mapping/src/main.rs 11_exceptions_part1_ // Discard any spurious received characters before going into echo mode. +diff -uNr 10_virtual_mem_part1_identity_mapping/tests/boot_test_string.rb 11_exceptions_part1_groundwork/tests/boot_test_string.rb +--- 10_virtual_mem_part1_identity_mapping/tests/boot_test_string.rb ++++ 11_exceptions_part1_groundwork/tests/boot_test_string.rb +@@ -1,3 +1,3 @@ + # frozen_string_literal: true + +-EXPECTED_PRINT = 'Echoing input now' ++EXPECTED_PRINT = 'lr : 0x' + ``` diff --git a/11_exceptions_part1_groundwork/tests/boot_test_string.rb b/11_exceptions_part1_groundwork/tests/boot_test_string.rb new file mode 100644 index 00000000..200cd971 --- /dev/null +++ b/11_exceptions_part1_groundwork/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'lr : 0x' diff --git a/12_integrated_testing/Makefile b/12_integrated_testing/Makefile index 4c2fb069..08004986 100644 --- a/12_integrated_testing/Makefile +++ b/12_integrated_testing/Makefile @@ -2,18 +2,32 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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) +# Optional integration test name. +ifdef TEST + TEST_ARG = --test $(TEST) +else + TEST_ARG = --test '*' +endif + + -# BSP-specific arguments +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# 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 +KERNEL_ELF = target/$(TARGET)/release/kernel + -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -define KERNEL_TEST_RUNNER - #!/usr/bin/env bash - - TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') - TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') - - $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -endef - -export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -test: - $(call colorecho, "\nCompiling test(s) - $(BSP)") - @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 +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot test_unit test_integration + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + + TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + +##------------------------------------------------------------------------------ +## Run integration test(s) +##------------------------------------------------------------------------------ +test_integration: + $(call colorecho, "\nCompiling integration test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) + +test: test_boot test_unit test_integration + +endif diff --git a/12_integrated_testing/README.md b/12_integrated_testing/README.md index bb1677e9..c25e3047 100644 --- a/12_integrated_testing/README.md +++ b/12_integrated_testing/README.md @@ -2,12 +2,14 @@ ## tl;dr -- We implement our own test framework using `Rust`'s [custom_test_frameworks] feature by enabling - `Unit Tests` and `Integration Tests` using `QEMU`. -- It is also possible to have test automation for the kernel's `console` (provided over `UART` in - our case): Sending strings/characters to the console and expecting specific answers in return. +- We implement our own integrated test framework using `Rust`'s [custom_test_frameworks] feature by + enabling `Unit Tests` and `Integration Tests` using `QEMU`. +- It is also possible to have test automation for I/O with the kernel's `console` (provided over + `UART` in our case). That is, sending strings/characters to the console and expecting specific + answers in return. +- The already existing basic `boot test` remains unchanged. - + ## Table of Contents @@ -40,13 +42,13 @@ functionality. For example: - Stalling execution during boot to test the kernel's timekeeping code by spinning for 1 second. - Willingly causing exceptions to see the exception handler running. -The feature set of the kernel is now rich enough so that it makes sense to introduce proper testing -modeled after Rust's [native testing framework]. This tutorial extends our kernel with three basic -testing facilities: +The feature set of the kernel is now rich enough so that it makes sense to introduce proper +integrated testing modeled after Rust's [native testing framework]. This tutorial extends our single +existing kernel test with three new testing facilities: - Classic `Unit Tests`. - [Integration Tests] (self-contained tests stored in the `$CRATE/tests/` directory). - - `Console Tests`. These are integration tests acting on external stimuli - aka `console` input. - Sending strings/characters to the console and expecting specific answers in return. + - `Console I/O Tests`. These are integration tests acting on external stimuli - aka `console` + input. Sending strings/characters to the console and expecting specific answers in return. [native testing framework]: https://doc.rust-lang.org/book/ch11-00-testing.html @@ -64,7 +66,7 @@ dependencies on the standard library, but comes at the cost of having a reduced of annotating functions with `#[test]`, the `#[test_case]` attribute must be used. Additionally, we need to write a `test_runner` function, which is supposed to execute all the functions annotated with `#[test_case]`. This is barely enough to get `Unit Tests` running, though. There will be some -more challenges that need solving for getting `Integration Tests` running as well. +more challenges that need be solved for getting `Integration Tests` running as well. Please note that for automation purposes, all testing will be done in `QEMU` and not on real hardware. @@ -82,15 +84,23 @@ additional insights. ## Implementation -We introduce a new `Makefile` target: +We introduce two new `Makefile` targets: ```console -$ make test +$ make test_unit +$ make test_integration ``` -In essence, `make test` will execute `cargo test` instead of `cargo rustc`. The details will be -explained in due course. The rest of the tutorial will explain as chronologically as possible what -happens when `make test` aka `cargo test` runs. +In essence, the `make test_*` targets will execute `cargo test` instead of `cargo rustc`. The +details will be explained in due course. The rest of the tutorial will explain as chronologically as +possible what happens when `make test_*` aka `cargo test` runs. + +Please note that the new targets are added to the existing `make test` target, so this is now your +one-stop target to execute all possible tests for the kernel: + +```Makefile +test: test_boot test_unit test_integration +``` ### Test Organization @@ -166,8 +176,9 @@ that we are supposed to provide. This is the one that will be called by the `car ```rust /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); @@ -255,7 +266,7 @@ opportunity to cut down on setup code. [tutorial 03]: ../03_hacky_hello_world As a matter of fact, for the `Raspberrys`, nothing needs to be done, so the function is empy. But -this might be different for other hardware emulated by QEMU, so it makes sense to introduce the +this might be different for other hardware emulated by `QEMU`, so it makes sense to introduce the function now to make it easier in case new `BSPs` are added to the kernel in the future. Next, the reexported `test_main()` is called, which will call our `test_runner()` which finally @@ -265,9 +276,10 @@ prints the unit test names and executes them. Let's recap where we are right now: -We've enabled `custom_test_frameworks` in `lib.rs` to a point where, when using `make test`, the -code gets compiled to a test kernel binary that eventually executes all the (yet-to-be-defined) -`UnitTest` instances by executing all the way from `_start()` to our `test_runner()` function. +We've enabled `custom_test_frameworks` in `lib.rs` to a point where, when using a `make test_unit` +target, the code gets compiled to a test kernel binary that eventually executes all the +(yet-to-be-defined) `UnitTest` instances by executing all the way from `_start()` to our +`test_runner()` function. Through mechanisms that are explained later, `cargo` will now instantiate a `QEMU` process that exectues this test kernel. The question now is: How is test success/failure communicated to `cargo`? @@ -339,30 +351,30 @@ concludes: #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } } ``` -In case none of the unit tests panicked, `lib.rs`'s `kernel_init()` calls `cpu::qemu_exit_success()` -to successfully conclude the unit test run. +In case _none_ of the unit tests panicked, `lib.rs`'s `kernel_init()` calls +`cpu::qemu_exit_success()` to successfully conclude the unit test run. ### Controlling Test Kernel Execution Now is a good time to catch up on how the test kernel binary is actually being executed. Normally, `cargo test` would try to execute the compiled binary as a normal child process. This would fail -horribly because we build a kernel, and not a userspace process. Also, chances are very high that -you sit in front of an `x86` machine, whereas the RPi kernel is `AArch64`. +horribly because we build a kernel, and not a userspace process. Also, chances are high that you sit +in front of an `x86` machine, whereas the RPi kernel is `AArch64`. Therefore, we need to install some hooks that make sure the test kernel gets executed inside `QEMU`, -quite like it is done for the existing `make qemu` target that is in place since tutorial 1. The +quite like it is done for the existing `make qemu` target that is in place since `tutorial 1`. The first step is to add a new file to the project, `.cargo/config.toml`: ```toml @@ -374,10 +386,13 @@ Instead of executing a compilation result directly, the `runner` flag will instr delegate the execution. Using the setting depicted above, `target/kernel_test_runner.sh` will be executed and given the full path to the compiled test kernel as the first command line argument. -The file `kernel_test_runner.sh` does not exist by default. We generate it on demand throguh the -`make test` target: +The file `kernel_test_runner.sh` does not exist by default. We generate it on demand when one of the +`make test_*` targets is called: ```Makefile +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ define KERNEL_TEST_RUNNER #!/usr/bin/env bash @@ -385,16 +400,26 @@ define KERNEL_TEST_RUNNER TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY endef export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -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) + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib ``` It first does the standard `objcopy` step to strip the `ELF` down to a raw binary. Just like in all @@ -403,44 +428,76 @@ provided to it by `cargo`, and finally compiles a `docker` command to execute th reference, here it is fully resolved for an `RPi3 BSP`: ```bash -docker run -i --rm -v /opt/rust-raspberrypi-OS-tutorials/12_integrated_testing:/work/tutorial -w /work/tutorial rustembedded/osdev-utils ruby tests/runner.rb qemu-system-aarch64 -M raspi3 -serial stdio -display none -semihosting -kernel $TEST_BINARY +docker run --rm -v /opt/rust-raspberrypi-OS-tutorials/12_integrated_testing:/work/tutorial -w /work/tutorial -v /opt/rust-raspberrypi-OS-tutorials/12_integrated_testing/../common:/work/common rustembedded/osdev-utils ruby ../common/tests/dispatch.rb qemu-system-aarch64 -M raspi3 -serial stdio -display none -semihosting -kernel $TEST_BINARY ``` -We're still not done with all the redirections. Spotted the `ruby tests/runner.rb` part that gets -excuted inside Docker? +This command is quite similar to the one used in the `make test_boot` target that we have since +`tutorial 3`. However, we never bothered explaining it, so lets take a closer look this time. One of +the key ingredients is that we execute this script: `ruby ../common/tests/dispatch.rb`. #### Wrapping QEMU Test Execution -`runner.rb` is a [Ruby] wrapper script around `QEMU` that, for unit tests, catches the case that a -test gets stuck, e.g. in an unintentional busy loop or a crash. If `runner.rb` does not observe any -output of the test kernel for `5 seconds`, it cancels the execution and reports a failure back to -`cargo`. If `QEMU` exited itself by means of `aarch64::exit_success() / aarch64::exit_failure()`, -the respective exit status code is passed through. The essential part happens here in `class -RawTest`: +`dispatch.rb` is a [Ruby] script which first determines what kind of test is due by inspecting the +`QEMU`-command that was given to it. In case of `unit tests`, we are only interested if they all +executed successfully, which can be checked by inspecting `QEMU`'s exit code. So the script takes +the provided qemu command it got from `ARGV`, and creates and runs an instance of `ExitCodeTest`: ```ruby -def exec - error = 'Timed out waiting for test' +require_relative 'boot_test' +require_relative 'console_io_test' +require_relative 'exit_code_test' + +qemu_cmd = ARGV.join(' ') +binary = ARGV.last +test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] + +case test_name +when 'kernel8.img' + load 'tests/boot_test_string.rb' # provides 'EXPECTED_PRINT' + BootTest.new(qemu_cmd, EXPECTED_PRINT).run # Doesn't return + +when 'libkernel' + ExitCodeTest.new(qemu_cmd, 'Kernel library unit tests').run # Doesn't return +``` + +The easy case is `QEMU` existing by itself by means of `aarch64::exit_success()` or +`aarch64::exit_failure()`. But the script can also catch the case of a test that gets stuck, e.g. in +an unintentional busy loop or a crash. If `ExitCodeTest` does not observe any output of the test +kernel for `MAX_WAIT_SECS`, it cancels the execution and marks the test as failed. Test success or +failure is finally reported back to `cargo`. + +Here is the essential part happening in `class ExitCodeTest` (If `QEMU` exits itself, an `EOFError` +is thrown): + +```ruby +def run_concrete_test io = IO.popen(@qemu_cmd) - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end + Timeout.timeout(MAX_WAIT_SECS) do + @test_output << io.read_nonblock(1024) while IO.select([io]) end +rescue EOFError + io.close + @test_error = $CHILD_STATUS.to_i.zero? ? false : 'QEMU exit status != 0' +rescue Timeout::Error + @test_error = 'Timed out waiting for test' +rescue StandardError => e + @test_error = e.message +ensure + post_process_output +end ``` +Please note that `dispatch.rb` and all its dependencies live in the shared folder +`../common/tests/`. + [Ruby]: https://www.ruby-lang.org/ ### Writing Unit Tests -Alright, that's a wrap for the whole chain from `make test` all the way to reporting the test exit -status back to `cargo test`. It is a lot to digest already, but we haven't even learned to write -`Unit Tests` yet. +Alright, that's a wrap for the whole chain from `make test_unit` all the way to reporting the test +exit status back to `cargo test`. It is a lot to digest already, but we haven't even learned to +write `Unit Tests` yet. In essence, it is almost like in `std` environments, with the difference that `#[test]` can't be used, because it is part of the standard library. The `no_std` replacement attribute provided by @@ -485,9 +542,9 @@ Since this is a bit boiler-platy with the const and name definition, let's write macro] named `#[kernel_test]` to simplify this. It should work this way: 1. Must be put before functions that take no arguments and return nothing. - 2. Automatically constructs a `const UnitTest` from attributed functions like shown above by: + 1. Automatically constructs a `const UnitTest` from attributed functions like shown above by: 1. Converting the function name to the `name` member of the `UnitTest` struct. - 2. Populating the `test_func` member with a closure that executes the body of the attributed + 1. Populating the `test_func` member with a closure that executes the body of the attributed function. For the sake of brevity, we're not going to discuss the macro implementation. [The source is in the @@ -609,12 +666,12 @@ function? This marks the function in `lib.rs` as a [weak symbol]. Let's look at #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } @@ -624,7 +681,7 @@ fn _panic_exit() -> ! { [weak symbol]: https://en.wikipedia.org/wiki/Weak_symbol This enables integration tests in `$CRATE/tests/` to override this function according to their -needs. This is useful because depending on the kind of test, a `panic!` could mean success or +needs. This is useful, because depending on the kind of test, a `panic!` could mean success or failure. For example, `tests/02_exception_sync_page_fault.rs` is intentionally causing a page fault, so the wanted outcome is a `panic!`. Here is the whole test (minus some inline comments): @@ -646,10 +703,10 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); - if let Err(string) = memory::mmu::mmu().init() { + if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { println!("MMU: {}", string); cpu::qemu_exit_failure() } @@ -661,7 +718,6 @@ unsafe fn kernel_init() -> ! { // If execution reaches here, the memory access above did not cause a page fault exception. cpu::qemu_exit_failure() } - ``` The `_panic_exit()` version that makes `QEMU` return `0` (indicating test success) is pulled in by @@ -671,22 +727,21 @@ The `_panic_exit()` version that makes `QEMU` return `0` (indicating test succes As the kernel or OS grows, it will be more and more interesting to test user/kernel interaction through the serial console. That is, sending strings/characters to the console and expecting -specific answers in return. The `runner.rb` wrapper script provides infrastructure to do this with -little overhead. It basically works like this: +specific answers in return. The `dispatch.rb` wrapper script provides infrastructure to recognize +and dispatch console I/O tests with little overhead. It basically works like this: 1. For each integration test, check if a companion file to the `.rs` test file exists. - A companion file has the same name, but ends in `.rb`. - - The companion file contains one or more console subtests. - 2. If it exists, load the file to dynamically import the console subtests. - 3. Spawn `QEMU` and attach to the serial console. - 4. Run the console subtests. + - The companion file contains one or more console I/O subtests. + 1. If it exists, load the file to dynamically import the console subtests. + 1. Create a `ConsoleIOTest` instance and run it. + - This first spawns `QEMU` and attaches to `QEMU`'s serial console emulation. + - Then it runs all console subtests on it. Here is an excerpt from `00_console_sanity.rb` showing a subtest that does a handshake with the kernel over the console: ```ruby -TIMEOUT_SECS = 3 - # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -695,7 +750,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end ``` @@ -727,20 +782,22 @@ unsafe fn kernel_init() -> ! { ## Test it -Believe it or not, that is all. There are three ways you can run tests: +Believe it or not, that is all. There are four ways you can run tests now: - 1. `make test` will run all tests back-to-back. - 2. `TEST=unit make test` will run `libkernel`'s unit tests. - 3. `TEST=TEST_NAME make test` will run a specficic integration test. - - For example, `TEST=01_timer_sanity make test` + 1. `make test` will run all tests back-to-back. That is, the ever existing `boot test` first, then + `unit tests`, then `integration tests`. + 1. `make test_unit` will run `libkernel`'s unit tests. + 1. `make test_integration` will run all integration tests back-to-back. + 1. `TEST=TEST_NAME make test_integration` will run a specficic integration test. + - For example, `TEST=01_timer_sanity make test_integration` ```console $ make test [...] - Running unittests (target/aarch64-unknown-none-softfloat/release/deps/libkernel-836110ac5dd535ba) + Running unittests (target/aarch64-unknown-none-softfloat/release/deps/libkernel-142a8d94bc9c615a) ------------------------------------------------------------------- - 🦀 Running 8 tests + 🦀 Running 6 tests ------------------------------------------------------------------- 1. virt_mem_layout_sections_are_64KiB_aligned................[ok] @@ -749,17 +806,18 @@ $ make test 4. kernel_tables_in_bss......................................[ok] 5. size_of_tabledescriptor_equals_64_bit.....................[ok] 6. size_of_pagedescriptor_equals_64_bit......................[ok] - 7. zero_volatile_works.......................................[ok] - 8. bss_section_is_sane.......................................[ok] ------------------------------------------------------------------- - ✅ Success: libkernel + ✅ Success: Kernel library unit tests ------------------------------------------------------------------- - Running tests/00_console_sanity.rs (target/aarch64-unknown-none-softfloat/release/deps/00_console_sanity-78c12c5472d40df7) + +Compiling integration test(s) - rpi3 + Finished release [optimized] target(s) in 0.00s + Running tests/00_console_sanity.rs (target/aarch64-unknown-none-softfloat/release/deps/00_console_sanity-c06130838f14dbff) ------------------------------------------------------------------- - 🦀 Running 3 console-based tests + 🦀 Running 3 console I/O tests ------------------------------------------------------------------- 1. Transmit and Receive handshake............................[ok] @@ -767,11 +825,11 @@ $ make test 3. Receive statistics........................................[ok] ------------------------------------------------------------------- - ✅ Success: 00_console_sanity + ✅ Success: 00_console_sanity.rs ------------------------------------------------------------------- - Running tests/01_timer_sanity.rs (target/aarch64-unknown-none-softfloat/release/deps/01_timer_sanity-4866734b14c83c9b) + Running tests/01_timer_sanity.rs (target/aarch64-unknown-none-softfloat/release/deps/01_timer_sanity-62a954d22239d1a3) ------------------------------------------------------------------- 🦀 Running 3 tests ------------------------------------------------------------------- @@ -781,11 +839,11 @@ $ make test 3. spin_accuracy_check_1_second..............................[ok] ------------------------------------------------------------------- - ✅ Success: 01_timer_sanity + ✅ Success: 01_timer_sanity.rs ------------------------------------------------------------------- - Running tests/02_exception_sync_page_fault.rs (target/aarch64-unknown-none-softfloat/release/deps/02_exception_sync_page_fault-f2d0885cada1105b) + Running tests/02_exception_sync_page_fault.rs (target/aarch64-unknown-none-softfloat/release/deps/02_exception_sync_page_fault-2d8ec603ef1c4d8e) ------------------------------------------------------------------- 🦀 Testing synchronous exception handling by causing a page fault ------------------------------------------------------------------- @@ -800,7 +858,7 @@ $ make test [...] ------------------------------------------------------------------- - ✅ Success: 02_exception_sync_page_fault + ✅ Success: 02_exception_sync_page_fault.rs ------------------------------------------------------------------- ``` @@ -880,7 +938,21 @@ diff -uNr 11_exceptions_part1_groundwork/Cargo.toml 12_integrated_testing/Cargo. diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile --- 11_exceptions_part1_groundwork/Makefile +++ 12_integrated_testing/Makefile -@@ -20,6 +20,7 @@ +@@ -14,6 +14,13 @@ + # Default to a serial device name that is common in Linux. + DEV_SERIAL ?= /dev/ttyUSB0 + ++# Optional integration test name. ++ifdef TEST ++ TEST_ARG = --test $(TEST) ++else ++ TEST_ARG = --test '*' ++endif ++ + + + ##-------------------------------------------------------------------------------------------------- +@@ -27,6 +34,7 @@ QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE = raspi3 QEMU_RELEASE_ARGS = -serial stdio -display none @@ -888,7 +960,7 @@ diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf -@@ -33,6 +34,7 @@ +@@ -40,6 +48,7 @@ QEMU_BINARY = qemu-system-aarch64 QEMU_MACHINE_TYPE = QEMU_RELEASE_ARGS = -serial stdio -display none @@ -896,23 +968,7 @@ diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile OBJDUMP_BINARY = aarch64-none-elf-objdump NM_BINARY = aarch64-none-elf-nm READELF_BINARY = aarch64-none-elf-readelf -@@ -45,6 +47,15 @@ - # 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) -@@ -59,6 +70,7 @@ +@@ -73,6 +82,7 @@ DOC_CMD = cargo doc $(COMPILER_ARGS) CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) CHECK_CMD = cargo check $(COMPILER_ARGS) @@ -920,37 +976,28 @@ diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -@@ -75,6 +87,7 @@ - - DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) - DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -+DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) - DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) - - # Dockerize commands that require USB device passthrough only on Linux -@@ -91,8 +104,8 @@ - EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) - EXEC_MINIPUSH = ruby ../utils/minipush.rb +@@ -236,11 +246,11 @@ + ##-------------------------------------------------------------------------------------------------- + ## Testing targets + ##-------------------------------------------------------------------------------------------------- +-.PHONY: test test_boot ++.PHONY: test test_boot test_unit test_integration --.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy \ -- clean readelf objdump nm check -+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu test chainboot jtagboot openocd gdb gdb-opt0 \ -+ clippy clean readelf objdump nm check + ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. - all: $(KERNEL_BIN) +-test_boot test : ++test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") -@@ -108,12 +121,31 @@ - @$(DOC_CMD) --document-private-items --open + else # QEMU is supported. +@@ -252,6 +262,45 @@ + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) - ifeq ($(QEMU_MACHINE_TYPE),) --qemu: -+qemu test: - $(call colorecho, "\n$(QEMU_MISSING_STRING)") - else - qemu: $(KERNEL_BIN) - $(call colorecho, "\nLaunching QEMU") - @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -+ +-test: test_boot ++##------------------------------------------------------------------------------ ++## Helpers for unit and integration test targets ++##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + @@ -958,20 +1005,38 @@ diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY -+ $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY ++ $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER -+test: FEATURES += --features test_build -+test: -+ $(call colorecho, "\nCompiling test(s) - $(BSP)") -+ @mkdir -p target -+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh -+ @chmod +x target/kernel_test_runner.sh ++ ++define test_prepare ++ @mkdir -p target ++ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh ++ @chmod +x target/kernel_test_runner.sh ++endef ++ ++test_unit test_integration: FEATURES += --features test_build ++ ++##------------------------------------------------------------------------------ ++## Run unit test(s) ++##------------------------------------------------------------------------------ ++test_unit: ++ $(call colorecho, "\nCompiling unit test(s) - $(BSP)") ++ $(call test_prepare) ++ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib ++ ++##------------------------------------------------------------------------------ ++## Run integration test(s) ++##------------------------------------------------------------------------------ ++test_integration: ++ $(call colorecho, "\nCompiling integration test(s) - $(BSP)") ++ $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) - endif ++ ++test: test_boot test_unit test_integration - chainboot: $(KERNEL_BIN) + endif diff -uNr 11_exceptions_part1_groundwork/src/_arch/aarch64/cpu.rs 12_integrated_testing/src/_arch/aarch64/cpu.rs --- 11_exceptions_part1_groundwork/src/_arch/aarch64/cpu.rs @@ -1216,7 +1281,7 @@ diff -uNr 11_exceptions_part1_groundwork/src/exception.rs 12_integrated_testing/ diff -uNr 11_exceptions_part1_groundwork/src/lib.rs 12_integrated_testing/src/lib.rs --- 11_exceptions_part1_groundwork/src/lib.rs +++ 12_integrated_testing/src/lib.rs -@@ -0,0 +1,186 @@ +@@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2021 Andre Richter @@ -1379,8 +1444,9 @@ diff -uNr 11_exceptions_part1_groundwork/src/lib.rs 12_integrated_testing/src/li + +/// The default runner for unit tests. +pub fn test_runner(tests: &[&test_types::UnitTest]) { ++ // This line will be printed as the test header. + println!("Running {} tests", tests.len()); -+ println!("-------------------------------------------------------------------\n"); ++ + for (i, test) in tests.iter().enumerate() { + print!("{:>3}. {:.<58}", i + 1, test.name); + @@ -1630,12 +1696,12 @@ diff -uNr 11_exceptions_part1_groundwork/src/panic_wait.rs 12_integrated_testing +#[linkage = "weak"] +#[no_mangle] +fn _panic_exit() -> ! { -+ #[cfg(not(test_build))] ++ #[cfg(not(feature = "test_build"))] + { + cpu::wait_forever() + } + -+ #[cfg(test_build)] ++ #[cfg(feature = "test_build")] + { + cpu::qemu_exit_failure() + } @@ -1708,7 +1774,7 @@ diff -uNr 11_exceptions_part1_groundwork/test-macros/src/lib.rs 12_integrated_te diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrated_testing/tests/00_console_sanity.rb --- 11_exceptions_part1_groundwork/tests/00_console_sanity.rb +++ 12_integrated_testing/tests/00_console_sanity.rb -@@ -0,0 +1,50 @@ +@@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 @@ -1719,6 +1785,13 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate + +TIMEOUT_SECS = 3 + ++# Error class for when expect times out. ++class ExpectTimeoutError < StandardError ++ def initialize ++ super('Timeout while expecting string') ++ end ++end ++ +# Verify sending and receiving works as expected. +class TxRxHandshake + def name @@ -1727,7 +1800,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate + + def run(qemu_out, qemu_in) + qemu_in.write_nonblock('ABC') -+ raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? ++ raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + end +end + @@ -1738,7 +1811,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate + end + + def run(qemu_out, _qemu_in) -+ raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? ++ raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? + end +end + @@ -1749,7 +1822,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate + end + + def run(qemu_out, _qemu_in) -+ raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? ++ raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? + end +end + @@ -1763,7 +1836,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rs 12_integrated_testing/tests/00_console_sanity.rs --- 11_exceptions_part1_groundwork/tests/00_console_sanity.rs +++ 12_integrated_testing/tests/00_console_sanity.rs -@@ -0,0 +1,42 @@ +@@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2019-2021 Andre Richter @@ -1797,14 +1870,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rs 12_integrate + print!("{}", console().chars_read()); + + // The QEMU process running this test will be closed by the I/O test harness. -+ // cpu::wait_forever(); -+ -+ // For some reason, in this test, rustc or the linker produces an empty binary when -+ // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time -+ // being, the following lines are just a workaround to fix this compiler/linker weirdness. -+ use libkernel::time::interface::TimeManager; -+ libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); -+ cpu::qemu_exit_success() ++ cpu::wait_forever(); +} diff -uNr 11_exceptions_part1_groundwork/tests/01_timer_sanity.rs 12_integrated_testing/tests/01_timer_sanity.rs @@ -1893,8 +1959,8 @@ diff -uNr 11_exceptions_part1_groundwork/tests/02_exception_sync_page_fault.rs 1 + exception::handling_init(); + bsp::console::qemu_bring_up_console(); + ++ // This line will be printed as the test header. + println!("Testing synchronous exception handling by causing a page fault"); -+ println!("-------------------------------------------------------------------\n"); + + if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { + println!("MMU: {}", string); @@ -1909,6 +1975,15 @@ diff -uNr 11_exceptions_part1_groundwork/tests/02_exception_sync_page_fault.rs 1 + cpu::qemu_exit_failure() +} +diff -uNr 11_exceptions_part1_groundwork/tests/boot_test_string.rb 12_integrated_testing/tests/boot_test_string.rb +--- 11_exceptions_part1_groundwork/tests/boot_test_string.rb ++++ 12_integrated_testing/tests/boot_test_string.rb +@@ -1,3 +1,3 @@ + # frozen_string_literal: true + +-EXPECTED_PRINT = 'lr : 0x' ++EXPECTED_PRINT = 'Echoing input now' + diff -uNr 11_exceptions_part1_groundwork/tests/panic_exit_success/mod.rs 12_integrated_testing/tests/panic_exit_success/mod.rs --- 11_exceptions_part1_groundwork/tests/panic_exit_success/mod.rs +++ 12_integrated_testing/tests/panic_exit_success/mod.rs @@ -1923,154 +1998,6 @@ diff -uNr 11_exceptions_part1_groundwork/tests/panic_exit_success/mod.rs 12_inte + libkernel::cpu::qemu_exit_success() +} -diff -uNr 11_exceptions_part1_groundwork/tests/runner.rb 12_integrated_testing/tests/runner.rb ---- 11_exceptions_part1_groundwork/tests/runner.rb -+++ 12_integrated_testing/tests/runner.rb -@@ -0,0 +1,143 @@ -+#!/usr/bin/env ruby -+# frozen_string_literal: true -+ -+# SPDX-License-Identifier: MIT OR Apache-2.0 -+# -+# Copyright (c) 2019-2021 Andre Richter -+ -+require 'English' -+require 'pty' -+ -+# Test base class. -+class Test -+ INDENT = ' ' -+ -+ def print_border(status) -+ puts -+ puts "#{INDENT}-------------------------------------------------------------------" -+ puts status -+ puts "#{INDENT}-------------------------------------------------------------------\n\n\n" -+ end -+ -+ def print_error(error) -+ puts -+ print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") -+ end -+ -+ def print_success -+ print_border("#{INDENT}✅ Success: #{@test_name}") -+ end -+ -+ def print_output -+ puts "#{INDENT}-------------------------------------------------------------------" -+ print INDENT -+ print '🦀 ' -+ print @output.join.gsub("\n", "\n#{INDENT}") -+ end -+ -+ def finish(error) -+ print_output -+ -+ exit_code = if error -+ print_error(error) -+ false -+ else -+ print_success -+ true -+ end -+ -+ exit(exit_code) -+ end -+end -+ -+# Executes tests with console I/O. -+class ConsoleTest < Test -+ def initialize(binary, qemu_cmd, test_name, console_subtests) -+ super() -+ -+ @binary = binary -+ @qemu_cmd = qemu_cmd -+ @test_name = test_name -+ @console_subtests = console_subtests -+ @cur_subtest = 1 -+ @output = ["Running #{@console_subtests.length} console-based tests\n", -+ "-------------------------------------------------------------------\n\n"] -+ end -+ -+ def format_test_name(number, name) -+ formatted_name = "#{number.to_s.rjust(3)}. #{name}" -+ formatted_name.ljust(63, '.') -+ end -+ -+ def run_subtest(subtest, qemu_out, qemu_in) -+ @output << format_test_name(@cur_subtest, subtest.name) -+ -+ subtest.run(qemu_out, qemu_in) -+ -+ @output << "[ok]\n" -+ @cur_subtest += 1 -+ end -+ -+ def exec -+ error = false -+ -+ PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| -+ begin -+ @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } -+ rescue StandardError => e -+ error = e.message -+ end -+ -+ finish(error) -+ end -+ end -+end -+ -+# A wrapper around the bare QEMU invocation. -+class RawTest < Test -+ MAX_WAIT_SECS = 5 -+ -+ def initialize(binary, qemu_cmd, test_name) -+ super() -+ -+ @binary = binary -+ @qemu_cmd = qemu_cmd -+ @test_name = test_name -+ @output = [] -+ end -+ -+ def exec -+ error = 'Timed out waiting for test' -+ io = IO.popen(@qemu_cmd) -+ -+ while IO.select([io], nil, nil, MAX_WAIT_SECS) -+ begin -+ @output << io.read_nonblock(1024) -+ rescue EOFError -+ io.close -+ error = $CHILD_STATUS.to_i != 0 -+ break -+ end -+ end -+ -+ finish(error) -+ end -+end -+ -+##-------------------------------------------------------------------------------------------------- -+## Script entry point -+##-------------------------------------------------------------------------------------------------- -+binary = ARGV.last -+test_name = binary.gsub(modulor{.*deps/}, '').split('-')[0] -+console_test_file = "tests/#{test_name}.rb" -+qemu_cmd = ARGV.join(' ') -+ -+test_runner = if File.exist?(console_test_file) -+ load console_test_file -+ # subtest_collection is provided by console_test_file -+ ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) -+ else -+ RawTest.new(binary, qemu_cmd, test_name) -+ end -+ -+test_runner.exec - diff -uNr 11_exceptions_part1_groundwork/test-types/Cargo.toml 12_integrated_testing/test-types/Cargo.toml --- 11_exceptions_part1_groundwork/test-types/Cargo.toml +++ 12_integrated_testing/test-types/Cargo.toml diff --git a/12_integrated_testing/src/lib.rs b/12_integrated_testing/src/lib.rs index 9890351f..b8370a54 100644 --- a/12_integrated_testing/src/lib.rs +++ b/12_integrated_testing/src/lib.rs @@ -160,8 +160,9 @@ extern "Rust" { /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); diff --git a/12_integrated_testing/src/panic_wait.rs b/12_integrated_testing/src/panic_wait.rs index 20493a91..d272a197 100644 --- a/12_integrated_testing/src/panic_wait.rs +++ b/12_integrated_testing/src/panic_wait.rs @@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) { #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } diff --git a/12_integrated_testing/tests/00_console_sanity.rb b/12_integrated_testing/tests/00_console_sanity.rb index dfd6b16e..16fb6c79 100644 --- a/12_integrated_testing/tests/00_console_sanity.rb +++ b/12_integrated_testing/tests/00_console_sanity.rb @@ -8,6 +8,13 @@ require 'expect' TIMEOUT_SECS = 3 +# Error class for when expect times out. +class ExpectTimeoutError < StandardError + def initialize + super('Timeout while expecting string') + end +end + # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -16,7 +23,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end @@ -27,7 +34,7 @@ class TxStatistics end def run(qemu_out, _qemu_in) - raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? end end @@ -38,7 +45,7 @@ class RxStatistics end def run(qemu_out, _qemu_in) - raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? end end diff --git a/12_integrated_testing/tests/00_console_sanity.rs b/12_integrated_testing/tests/00_console_sanity.rs index 84b74479..03058f5e 100644 --- a/12_integrated_testing/tests/00_console_sanity.rs +++ b/12_integrated_testing/tests/00_console_sanity.rs @@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! { print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. - // cpu::wait_forever(); - - // For some reason, in this test, rustc or the linker produces an empty binary when - // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time - // being, the following lines are just a workaround to fix this compiler/linker weirdness. - use libkernel::time::interface::TimeManager; - libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); - cpu::qemu_exit_success() + cpu::wait_forever(); } diff --git a/12_integrated_testing/tests/02_exception_sync_page_fault.rs b/12_integrated_testing/tests/02_exception_sync_page_fault.rs index f1535d34..8febacd1 100644 --- a/12_integrated_testing/tests/02_exception_sync_page_fault.rs +++ b/12_integrated_testing/tests/02_exception_sync_page_fault.rs @@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { println!("MMU: {}", string); diff --git a/12_integrated_testing/tests/boot_test_string.rb b/12_integrated_testing/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/12_integrated_testing/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/12_integrated_testing/tests/runner.rb b/12_integrated_testing/tests/runner.rb deleted file mode 100755 index 53116e08..00000000 --- a/12_integrated_testing/tests/runner.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2019-2021 Andre Richter - -require 'English' -require 'pty' - -# Test base class. -class Test - INDENT = ' ' - - def print_border(status) - puts - puts "#{INDENT}-------------------------------------------------------------------" - puts status - puts "#{INDENT}-------------------------------------------------------------------\n\n\n" - end - - def print_error(error) - puts - print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") - end - - def print_success - print_border("#{INDENT}✅ Success: #{@test_name}") - end - - def print_output - puts "#{INDENT}-------------------------------------------------------------------" - print INDENT - print '🦀 ' - print @output.join.gsub("\n", "\n#{INDENT}") - end - - def finish(error) - print_output - - exit_code = if error - print_error(error) - false - else - print_success - true - end - - exit(exit_code) - end -end - -# Executes tests with console I/O. -class ConsoleTest < Test - def initialize(binary, qemu_cmd, test_name, console_subtests) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @console_subtests = console_subtests - @cur_subtest = 1 - @output = ["Running #{@console_subtests.length} console-based tests\n", - "-------------------------------------------------------------------\n\n"] - end - - def format_test_name(number, name) - formatted_name = "#{number.to_s.rjust(3)}. #{name}" - formatted_name.ljust(63, '.') - end - - def run_subtest(subtest, qemu_out, qemu_in) - @output << format_test_name(@cur_subtest, subtest.name) - - subtest.run(qemu_out, qemu_in) - - @output << "[ok]\n" - @cur_subtest += 1 - end - - def exec - error = false - - PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| - begin - @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } - rescue StandardError => e - error = e.message - end - - finish(error) - end - end -end - -# A wrapper around the bare QEMU invocation. -class RawTest < Test - MAX_WAIT_SECS = 5 - - def initialize(binary, qemu_cmd, test_name) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @output = [] - end - - def exec - error = 'Timed out waiting for test' - io = IO.popen(@qemu_cmd) - - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end - end - - finish(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Script entry point -##-------------------------------------------------------------------------------------------------- -binary = ARGV.last -test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] -console_test_file = "tests/#{test_name}.rb" -qemu_cmd = ARGV.join(' ') - -test_runner = if File.exist?(console_test_file) - load console_test_file - # subtest_collection is provided by console_test_file - ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) - else - RawTest.new(binary, qemu_cmd, test_name) - end - -test_runner.exec diff --git a/13_exceptions_part2_peripheral_IRQs/Makefile b/13_exceptions_part2_peripheral_IRQs/Makefile index 4c2fb069..e860f00d 100644 --- a/13_exceptions_part2_peripheral_IRQs/Makefile +++ b/13_exceptions_part2_peripheral_IRQs/Makefile @@ -2,18 +2,32 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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) +# Optional integration test name. +ifdef TEST + TEST_ARG = --test $(TEST) +else + TEST_ARG = --test '*' +endif + + -# BSP-specific arguments +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# 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 +KERNEL_ELF = target/$(TARGET)/release/kernel + -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -define KERNEL_TEST_RUNNER - #!/usr/bin/env bash - - TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') - TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') - - $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -endef - -export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -test: - $(call colorecho, "\nCompiling test(s) - $(BSP)") - @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 +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot test_unit test_integration + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + + TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + +##------------------------------------------------------------------------------ +## Run integration test(s) +##------------------------------------------------------------------------------ +test_integration: + $(call colorecho, "\nCompiling integration test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) + +test: test_boot test_unit test_integration + +endif diff --git a/13_exceptions_part2_peripheral_IRQs/README.md b/13_exceptions_part2_peripheral_IRQs/README.md index 02341a12..b8cf351c 100644 --- a/13_exceptions_part2_peripheral_IRQs/README.md +++ b/13_exceptions_part2_peripheral_IRQs/README.md @@ -758,6 +758,19 @@ diff -uNr 12_integrated_testing/Cargo.toml 13_exceptions_part2_peripheral_IRQs/C edition = "2018" +diff -uNr 12_integrated_testing/Makefile 13_exceptions_part2_peripheral_IRQs/Makefile +--- 12_integrated_testing/Makefile ++++ 13_exceptions_part2_peripheral_IRQs/Makefile +@@ -291,7 +291,7 @@ + test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) +- RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib ++ @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + + ##------------------------------------------------------------------------------ + ## Run integration test(s) + diff -uNr 12_integrated_testing/src/_arch/aarch64/cpu/smp.rs 13_exceptions_part2_peripheral_IRQs/src/_arch/aarch64/cpu/smp.rs --- 12_integrated_testing/src/_arch/aarch64/cpu/smp.rs +++ 13_exceptions_part2_peripheral_IRQs/src/_arch/aarch64/cpu/smp.rs diff --git a/13_exceptions_part2_peripheral_IRQs/src/lib.rs b/13_exceptions_part2_peripheral_IRQs/src/lib.rs index 4ca9f03e..9c67f1ed 100644 --- a/13_exceptions_part2_peripheral_IRQs/src/lib.rs +++ b/13_exceptions_part2_peripheral_IRQs/src/lib.rs @@ -163,8 +163,9 @@ extern "Rust" { /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); diff --git a/13_exceptions_part2_peripheral_IRQs/src/panic_wait.rs b/13_exceptions_part2_peripheral_IRQs/src/panic_wait.rs index e3a9ed8a..130e952b 100644 --- a/13_exceptions_part2_peripheral_IRQs/src/panic_wait.rs +++ b/13_exceptions_part2_peripheral_IRQs/src/panic_wait.rs @@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) { #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } diff --git a/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rb b/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rb index dfd6b16e..16fb6c79 100644 --- a/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rb +++ b/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rb @@ -8,6 +8,13 @@ require 'expect' TIMEOUT_SECS = 3 +# Error class for when expect times out. +class ExpectTimeoutError < StandardError + def initialize + super('Timeout while expecting string') + end +end + # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -16,7 +23,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end @@ -27,7 +34,7 @@ class TxStatistics end def run(qemu_out, _qemu_in) - raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? end end @@ -38,7 +45,7 @@ class RxStatistics end def run(qemu_out, _qemu_in) - raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? end end diff --git a/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rs b/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rs index 84b74479..03058f5e 100644 --- a/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rs +++ b/13_exceptions_part2_peripheral_IRQs/tests/00_console_sanity.rs @@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! { print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. - // cpu::wait_forever(); - - // For some reason, in this test, rustc or the linker produces an empty binary when - // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time - // being, the following lines are just a workaround to fix this compiler/linker weirdness. - use libkernel::time::interface::TimeManager; - libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); - cpu::qemu_exit_success() + cpu::wait_forever(); } diff --git a/13_exceptions_part2_peripheral_IRQs/tests/02_exception_sync_page_fault.rs b/13_exceptions_part2_peripheral_IRQs/tests/02_exception_sync_page_fault.rs index f1535d34..8febacd1 100644 --- a/13_exceptions_part2_peripheral_IRQs/tests/02_exception_sync_page_fault.rs +++ b/13_exceptions_part2_peripheral_IRQs/tests/02_exception_sync_page_fault.rs @@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { println!("MMU: {}", string); diff --git a/13_exceptions_part2_peripheral_IRQs/tests/boot_test_string.rb b/13_exceptions_part2_peripheral_IRQs/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/13_exceptions_part2_peripheral_IRQs/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/13_exceptions_part2_peripheral_IRQs/tests/runner.rb b/13_exceptions_part2_peripheral_IRQs/tests/runner.rb deleted file mode 100755 index 53116e08..00000000 --- a/13_exceptions_part2_peripheral_IRQs/tests/runner.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2019-2021 Andre Richter - -require 'English' -require 'pty' - -# Test base class. -class Test - INDENT = ' ' - - def print_border(status) - puts - puts "#{INDENT}-------------------------------------------------------------------" - puts status - puts "#{INDENT}-------------------------------------------------------------------\n\n\n" - end - - def print_error(error) - puts - print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") - end - - def print_success - print_border("#{INDENT}✅ Success: #{@test_name}") - end - - def print_output - puts "#{INDENT}-------------------------------------------------------------------" - print INDENT - print '🦀 ' - print @output.join.gsub("\n", "\n#{INDENT}") - end - - def finish(error) - print_output - - exit_code = if error - print_error(error) - false - else - print_success - true - end - - exit(exit_code) - end -end - -# Executes tests with console I/O. -class ConsoleTest < Test - def initialize(binary, qemu_cmd, test_name, console_subtests) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @console_subtests = console_subtests - @cur_subtest = 1 - @output = ["Running #{@console_subtests.length} console-based tests\n", - "-------------------------------------------------------------------\n\n"] - end - - def format_test_name(number, name) - formatted_name = "#{number.to_s.rjust(3)}. #{name}" - formatted_name.ljust(63, '.') - end - - def run_subtest(subtest, qemu_out, qemu_in) - @output << format_test_name(@cur_subtest, subtest.name) - - subtest.run(qemu_out, qemu_in) - - @output << "[ok]\n" - @cur_subtest += 1 - end - - def exec - error = false - - PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| - begin - @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } - rescue StandardError => e - error = e.message - end - - finish(error) - end - end -end - -# A wrapper around the bare QEMU invocation. -class RawTest < Test - MAX_WAIT_SECS = 5 - - def initialize(binary, qemu_cmd, test_name) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @output = [] - end - - def exec - error = 'Timed out waiting for test' - io = IO.popen(@qemu_cmd) - - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end - end - - finish(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Script entry point -##-------------------------------------------------------------------------------------------------- -binary = ARGV.last -test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] -console_test_file = "tests/#{test_name}.rb" -qemu_cmd = ARGV.join(' ') - -test_runner = if File.exist?(console_test_file) - load console_test_file - # subtest_collection is provided by console_test_file - ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) - else - RawTest.new(binary, qemu_cmd, test_name) - end - -test_runner.exec diff --git a/14_virtual_mem_part2_mmio_remap/Makefile b/14_virtual_mem_part2_mmio_remap/Makefile index 4c2fb069..e860f00d 100644 --- a/14_virtual_mem_part2_mmio_remap/Makefile +++ b/14_virtual_mem_part2_mmio_remap/Makefile @@ -2,18 +2,32 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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) +# Optional integration test name. +ifdef TEST + TEST_ARG = --test $(TEST) +else + TEST_ARG = --test '*' +endif + + -# BSP-specific arguments +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# 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 +KERNEL_ELF = target/$(TARGET)/release/kernel + -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -define KERNEL_TEST_RUNNER - #!/usr/bin/env bash - - TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') - TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') - - $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -endef - -export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -test: - $(call colorecho, "\nCompiling test(s) - $(BSP)") - @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 +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot test_unit test_integration + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + + TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + +##------------------------------------------------------------------------------ +## Run integration test(s) +##------------------------------------------------------------------------------ +test_integration: + $(call colorecho, "\nCompiling integration test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) + +test: test_boot test_unit test_integration + +endif diff --git a/14_virtual_mem_part2_mmio_remap/README.md b/14_virtual_mem_part2_mmio_remap/README.md index 6cb7e16f..d178e9c1 100644 --- a/14_virtual_mem_part2_mmio_remap/README.md +++ b/14_virtual_mem_part2_mmio_remap/README.md @@ -3296,8 +3296,8 @@ diff -uNr 13_exceptions_part2_peripheral_IRQs/tests/02_exception_sync_page_fault exception::handling_init(); bsp::console::qemu_bring_up_console(); @@ -29,10 +29,30 @@ + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); - if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { - println!("MMU: {}", string); diff --git a/14_virtual_mem_part2_mmio_remap/src/lib.rs b/14_virtual_mem_part2_mmio_remap/src/lib.rs index 8a029738..002e14b9 100644 --- a/14_virtual_mem_part2_mmio_remap/src/lib.rs +++ b/14_virtual_mem_part2_mmio_remap/src/lib.rs @@ -165,8 +165,9 @@ extern "Rust" { /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); diff --git a/14_virtual_mem_part2_mmio_remap/src/panic_wait.rs b/14_virtual_mem_part2_mmio_remap/src/panic_wait.rs index e3a9ed8a..130e952b 100644 --- a/14_virtual_mem_part2_mmio_remap/src/panic_wait.rs +++ b/14_virtual_mem_part2_mmio_remap/src/panic_wait.rs @@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) { #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } diff --git a/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rb b/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rb index dfd6b16e..16fb6c79 100644 --- a/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rb +++ b/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rb @@ -8,6 +8,13 @@ require 'expect' TIMEOUT_SECS = 3 +# Error class for when expect times out. +class ExpectTimeoutError < StandardError + def initialize + super('Timeout while expecting string') + end +end + # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -16,7 +23,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end @@ -27,7 +34,7 @@ class TxStatistics end def run(qemu_out, _qemu_in) - raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? end end @@ -38,7 +45,7 @@ class RxStatistics end def run(qemu_out, _qemu_in) - raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? end end diff --git a/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rs b/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rs index 84b74479..03058f5e 100644 --- a/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rs +++ b/14_virtual_mem_part2_mmio_remap/tests/00_console_sanity.rs @@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! { print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. - // cpu::wait_forever(); - - // For some reason, in this test, rustc or the linker produces an empty binary when - // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time - // being, the following lines are just a workaround to fix this compiler/linker weirdness. - use libkernel::time::interface::TimeManager; - libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); - cpu::qemu_exit_success() + cpu::wait_forever(); } diff --git a/14_virtual_mem_part2_mmio_remap/tests/02_exception_sync_page_fault.rs b/14_virtual_mem_part2_mmio_remap/tests/02_exception_sync_page_fault.rs index 940866a0..7b8bba35 100644 --- a/14_virtual_mem_part2_mmio_remap/tests/02_exception_sync_page_fault.rs +++ b/14_virtual_mem_part2_mmio_remap/tests/02_exception_sync_page_fault.rs @@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { Err(string) => { diff --git a/14_virtual_mem_part2_mmio_remap/tests/boot_test_string.rb b/14_virtual_mem_part2_mmio_remap/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/14_virtual_mem_part2_mmio_remap/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/14_virtual_mem_part2_mmio_remap/tests/runner.rb b/14_virtual_mem_part2_mmio_remap/tests/runner.rb deleted file mode 100755 index 53116e08..00000000 --- a/14_virtual_mem_part2_mmio_remap/tests/runner.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2019-2021 Andre Richter - -require 'English' -require 'pty' - -# Test base class. -class Test - INDENT = ' ' - - def print_border(status) - puts - puts "#{INDENT}-------------------------------------------------------------------" - puts status - puts "#{INDENT}-------------------------------------------------------------------\n\n\n" - end - - def print_error(error) - puts - print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") - end - - def print_success - print_border("#{INDENT}✅ Success: #{@test_name}") - end - - def print_output - puts "#{INDENT}-------------------------------------------------------------------" - print INDENT - print '🦀 ' - print @output.join.gsub("\n", "\n#{INDENT}") - end - - def finish(error) - print_output - - exit_code = if error - print_error(error) - false - else - print_success - true - end - - exit(exit_code) - end -end - -# Executes tests with console I/O. -class ConsoleTest < Test - def initialize(binary, qemu_cmd, test_name, console_subtests) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @console_subtests = console_subtests - @cur_subtest = 1 - @output = ["Running #{@console_subtests.length} console-based tests\n", - "-------------------------------------------------------------------\n\n"] - end - - def format_test_name(number, name) - formatted_name = "#{number.to_s.rjust(3)}. #{name}" - formatted_name.ljust(63, '.') - end - - def run_subtest(subtest, qemu_out, qemu_in) - @output << format_test_name(@cur_subtest, subtest.name) - - subtest.run(qemu_out, qemu_in) - - @output << "[ok]\n" - @cur_subtest += 1 - end - - def exec - error = false - - PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| - begin - @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } - rescue StandardError => e - error = e.message - end - - finish(error) - end - end -end - -# A wrapper around the bare QEMU invocation. -class RawTest < Test - MAX_WAIT_SECS = 5 - - def initialize(binary, qemu_cmd, test_name) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @output = [] - end - - def exec - error = 'Timed out waiting for test' - io = IO.popen(@qemu_cmd) - - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end - end - - finish(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Script entry point -##-------------------------------------------------------------------------------------------------- -binary = ARGV.last -test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] -console_test_file = "tests/#{test_name}.rb" -qemu_cmd = ARGV.join(' ') - -test_runner = if File.exist?(console_test_file) - load console_test_file - # subtest_collection is provided by console_test_file - ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) - else - RawTest.new(binary, qemu_cmd, test_name) - end - -test_runner.exec diff --git a/15_virtual_mem_part3_precomputed_tables/Makefile b/15_virtual_mem_part3_precomputed_tables/Makefile index feb65cef..86a64ee5 100644 --- a/15_virtual_mem_part3_precomputed_tables/Makefile +++ b/15_virtual_mem_part3_precomputed_tables/Makefile @@ -2,18 +2,32 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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) +# Optional integration test name. +ifdef TEST + TEST_ARG = --test $(TEST) +else + TEST_ARG = --test '*' +endif + + -# BSP-specific arguments +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# 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 +KERNEL_ELF = target/$(TARGET)/release/kernel + -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -75,107 +87,112 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TT_TOOL = ruby translation_table_tool/main.rb +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) - @$(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $(KERNEL_ELF) + @$(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -define KERNEL_TEST_RUNNER - #!/usr/bin/env bash - - TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') - TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') - - $(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $$TEST_ELF > /dev/null - $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -endef - -export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -test: - $(call colorecho, "\nCompiling test(s) - $(BSP)") - @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 +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -184,10 +201,109 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot test_unit test_integration + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + + TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $$TEST_ELF > /dev/null + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + +##------------------------------------------------------------------------------ +## Run integration test(s) +##------------------------------------------------------------------------------ +test_integration: + $(call colorecho, "\nCompiling integration test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) + +test: test_boot test_unit test_integration + +endif diff --git a/15_virtual_mem_part3_precomputed_tables/README.md b/15_virtual_mem_part3_precomputed_tables/README.md index 581dcefc..b6a2b5b6 100644 --- a/15_virtual_mem_part3_precomputed_tables/README.md +++ b/15_virtual_mem_part3_precomputed_tables/README.md @@ -776,21 +776,29 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/Cargo.toml 15_virtual_mem_part3_precom diff -uNr 14_virtual_mem_part2_mmio_remap/Makefile 15_virtual_mem_part3_precomputed_tables/Makefile --- 14_virtual_mem_part2_mmio_remap/Makefile +++ 15_virtual_mem_part3_precomputed_tables/Makefile -@@ -112,6 +112,7 @@ +@@ -88,6 +88,7 @@ + -O binary + + EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) ++EXEC_TT_TOOL = ruby translation_table_tool/main.rb + EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb + EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +@@ -133,6 +134,7 @@ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) -+ @$(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $(KERNEL_ELF) ++ @$(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $(KERNEL_ELF) - $(KERNEL_BIN): $(KERNEL_ELF) - @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) -@@ -134,6 +135,7 @@ + ##------------------------------------------------------------------------------ + ## Build the stripped kernel binary +@@ -271,6 +273,7 @@ TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') -+ $(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $$TEST_ELF > /dev/null ++ $(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $$TEST_ELF > /dev/null $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY endef diff -uNr 14_virtual_mem_part2_mmio_remap/src/_arch/aarch64/cpu/boot.rs 15_virtual_mem_part3_precomputed_tables/src/_arch/aarch64/cpu/boot.rs @@ -1555,8 +1563,8 @@ diff -uNr 14_virtual_mem_part2_mmio_remap/tests/02_exception_sync_page_fault.rs exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); - let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { - Err(string) => { diff --git a/15_virtual_mem_part3_precomputed_tables/src/lib.rs b/15_virtual_mem_part3_precomputed_tables/src/lib.rs index 8a029738..002e14b9 100644 --- a/15_virtual_mem_part3_precomputed_tables/src/lib.rs +++ b/15_virtual_mem_part3_precomputed_tables/src/lib.rs @@ -165,8 +165,9 @@ extern "Rust" { /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); diff --git a/15_virtual_mem_part3_precomputed_tables/src/panic_wait.rs b/15_virtual_mem_part3_precomputed_tables/src/panic_wait.rs index e3a9ed8a..130e952b 100644 --- a/15_virtual_mem_part3_precomputed_tables/src/panic_wait.rs +++ b/15_virtual_mem_part3_precomputed_tables/src/panic_wait.rs @@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) { #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } diff --git a/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rb b/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rb index dfd6b16e..16fb6c79 100644 --- a/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rb +++ b/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rb @@ -8,6 +8,13 @@ require 'expect' TIMEOUT_SECS = 3 +# Error class for when expect times out. +class ExpectTimeoutError < StandardError + def initialize + super('Timeout while expecting string') + end +end + # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -16,7 +23,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end @@ -27,7 +34,7 @@ class TxStatistics end def run(qemu_out, _qemu_in) - raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? end end @@ -38,7 +45,7 @@ class RxStatistics end def run(qemu_out, _qemu_in) - raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? end end diff --git a/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rs b/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rs index 84b74479..03058f5e 100644 --- a/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rs +++ b/15_virtual_mem_part3_precomputed_tables/tests/00_console_sanity.rs @@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! { print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. - // cpu::wait_forever(); - - // For some reason, in this test, rustc or the linker produces an empty binary when - // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time - // being, the following lines are just a workaround to fix this compiler/linker weirdness. - use libkernel::time::interface::TimeManager; - libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); - cpu::qemu_exit_success() + cpu::wait_forever(); } diff --git a/15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_fault.rs b/15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_fault.rs index 6a0c11f3..e7fa8800 100644 --- a/15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_fault.rs +++ b/15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_fault.rs @@ -24,8 +24,8 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); println!("Writing beyond mapped area to address 9 GiB..."); let big_addr: u64 = 9 * 1024 * 1024 * 1024; diff --git a/15_virtual_mem_part3_precomputed_tables/tests/boot_test_string.rb b/15_virtual_mem_part3_precomputed_tables/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/15_virtual_mem_part3_precomputed_tables/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/15_virtual_mem_part3_precomputed_tables/tests/runner.rb b/15_virtual_mem_part3_precomputed_tables/tests/runner.rb deleted file mode 100755 index 53116e08..00000000 --- a/15_virtual_mem_part3_precomputed_tables/tests/runner.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2019-2021 Andre Richter - -require 'English' -require 'pty' - -# Test base class. -class Test - INDENT = ' ' - - def print_border(status) - puts - puts "#{INDENT}-------------------------------------------------------------------" - puts status - puts "#{INDENT}-------------------------------------------------------------------\n\n\n" - end - - def print_error(error) - puts - print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") - end - - def print_success - print_border("#{INDENT}✅ Success: #{@test_name}") - end - - def print_output - puts "#{INDENT}-------------------------------------------------------------------" - print INDENT - print '🦀 ' - print @output.join.gsub("\n", "\n#{INDENT}") - end - - def finish(error) - print_output - - exit_code = if error - print_error(error) - false - else - print_success - true - end - - exit(exit_code) - end -end - -# Executes tests with console I/O. -class ConsoleTest < Test - def initialize(binary, qemu_cmd, test_name, console_subtests) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @console_subtests = console_subtests - @cur_subtest = 1 - @output = ["Running #{@console_subtests.length} console-based tests\n", - "-------------------------------------------------------------------\n\n"] - end - - def format_test_name(number, name) - formatted_name = "#{number.to_s.rjust(3)}. #{name}" - formatted_name.ljust(63, '.') - end - - def run_subtest(subtest, qemu_out, qemu_in) - @output << format_test_name(@cur_subtest, subtest.name) - - subtest.run(qemu_out, qemu_in) - - @output << "[ok]\n" - @cur_subtest += 1 - end - - def exec - error = false - - PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| - begin - @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } - rescue StandardError => e - error = e.message - end - - finish(error) - end - end -end - -# A wrapper around the bare QEMU invocation. -class RawTest < Test - MAX_WAIT_SECS = 5 - - def initialize(binary, qemu_cmd, test_name) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @output = [] - end - - def exec - error = 'Timed out waiting for test' - io = IO.popen(@qemu_cmd) - - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end - end - - finish(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Script entry point -##-------------------------------------------------------------------------------------------------- -binary = ARGV.last -test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] -console_test_file = "tests/#{test_name}.rb" -qemu_cmd = ARGV.join(' ') - -test_runner = if File.exist?(console_test_file) - load console_test_file - # subtest_collection is provided by console_test_file - ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) - else - RawTest.new(binary, qemu_cmd, test_name) - end - -test_runner.exec diff --git a/16_virtual_mem_part4_higher_half_kernel/Makefile b/16_virtual_mem_part4_higher_half_kernel/Makefile index feb65cef..86a64ee5 100644 --- a/16_virtual_mem_part4_higher_half_kernel/Makefile +++ b/16_virtual_mem_part4_higher_half_kernel/Makefile @@ -2,18 +2,32 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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) +# Optional integration test name. +ifdef TEST + TEST_ARG = --test $(TEST) +else + TEST_ARG = --test '*' +endif + + -# BSP-specific arguments +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# 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 +KERNEL_ELF = target/$(TARGET)/release/kernel + -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -75,107 +87,112 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel - -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -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 +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TT_TOOL = ruby translation_table_tool/main.rb +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +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_INTERACT) $(DOCKER_IMAGE) -DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(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_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(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 + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) - @$(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $(KERNEL_ELF) + @$(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) -qemu test: +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -define KERNEL_TEST_RUNNER - #!/usr/bin/env bash - - TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') - TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') - - $(DOCKER_TOOLS) ruby translation_table_tool/main.rb $(TARGET) $(BSP) $$TEST_ELF > /dev/null - $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY - $(DOCKER_TEST) ruby tests/runner.rb $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY -endef - -export KERNEL_TEST_RUNNER -test: FEATURES += --features test_build -test: - $(call colorecho, "\nCompiling test(s) - $(BSP)") - @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 +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) -jtagboot: - @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) - -openocd: - $(call colorecho, "\nLaunching OpenOCD") - @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) - -gdb: RUSTC_MISC_ARGS += -C debuginfo=2 -gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 -gdb gdb-opt0: $(KERNEL_ELF) - $(call colorecho, "\nLaunching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) - +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -184,10 +201,109 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call colorecho, "\nLaunching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb gdb-opt0: $(KERNEL_ELF) + $(call colorecho, "\nLaunching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot test_unit test_integration + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test_unit test_integration test: + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Helpers for unit and integration test targets +##------------------------------------------------------------------------------ +define KERNEL_TEST_RUNNER + #!/usr/bin/env bash + + TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') + + $(DOCKER_TOOLS) $(EXEC_TT_TOOL) $(TARGET) $(BSP) $$TEST_ELF > /dev/null + $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_TEST_ARGS) -kernel $$TEST_BINARY +endef + +export KERNEL_TEST_RUNNER + +define test_prepare + @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x target/kernel_test_runner.sh +endef + +test_unit test_integration: FEATURES += --features test_build + +##------------------------------------------------------------------------------ +## Run unit test(s) +##------------------------------------------------------------------------------ +test_unit: + $(call colorecho, "\nCompiling unit test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) --lib + +##------------------------------------------------------------------------------ +## Run integration test(s) +##------------------------------------------------------------------------------ +test_integration: + $(call colorecho, "\nCompiling integration test(s) - $(BSP)") + $(call test_prepare) + @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) + +test: test_boot test_unit test_integration + +endif diff --git a/16_virtual_mem_part4_higher_half_kernel/README.md b/16_virtual_mem_part4_higher_half_kernel/README.md index fa594703..15629a2a 100644 --- a/16_virtual_mem_part4_higher_half_kernel/README.md +++ b/16_virtual_mem_part4_higher_half_kernel/README.md @@ -617,8 +617,8 @@ diff -uNr 15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_f --- 15_virtual_mem_part3_precomputed_tables/tests/02_exception_sync_page_fault.rs +++ 16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs @@ -27,8 +27,8 @@ + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); - println!("Writing beyond mapped area to address 9 GiB..."); - let big_addr: u64 = 9 * 1024 * 1024 * 1024; diff --git a/16_virtual_mem_part4_higher_half_kernel/src/lib.rs b/16_virtual_mem_part4_higher_half_kernel/src/lib.rs index 7b5cdb09..c75a96ea 100644 --- a/16_virtual_mem_part4_higher_half_kernel/src/lib.rs +++ b/16_virtual_mem_part4_higher_half_kernel/src/lib.rs @@ -160,8 +160,9 @@ pub fn version() -> &'static str { /// The default runner for unit tests. pub fn test_runner(tests: &[&test_types::UnitTest]) { + // This line will be printed as the test header. println!("Running {} tests", tests.len()); - println!("-------------------------------------------------------------------\n"); + for (i, test) in tests.iter().enumerate() { print!("{:>3}. {:.<58}", i + 1, test.name); diff --git a/16_virtual_mem_part4_higher_half_kernel/src/panic_wait.rs b/16_virtual_mem_part4_higher_half_kernel/src/panic_wait.rs index e3a9ed8a..130e952b 100644 --- a/16_virtual_mem_part4_higher_half_kernel/src/panic_wait.rs +++ b/16_virtual_mem_part4_higher_half_kernel/src/panic_wait.rs @@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) { #[linkage = "weak"] #[no_mangle] fn _panic_exit() -> ! { - #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))] { cpu::wait_forever() } - #[cfg(test_build)] + #[cfg(feature = "test_build")] { cpu::qemu_exit_failure() } diff --git a/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rb b/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rb index dfd6b16e..16fb6c79 100644 --- a/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rb +++ b/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rb @@ -8,6 +8,13 @@ require 'expect' TIMEOUT_SECS = 3 +# Error class for when expect times out. +class ExpectTimeoutError < StandardError + def initialize + super('Timeout while expecting string') + end +end + # Verify sending and receiving works as expected. class TxRxHandshake def name @@ -16,7 +23,7 @@ class TxRxHandshake def run(qemu_out, qemu_in) qemu_in.write_nonblock('ABC') - raise('TX/RX test failed') if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('OK1234', TIMEOUT_SECS).nil? end end @@ -27,7 +34,7 @@ class TxStatistics end def run(qemu_out, _qemu_in) - raise('chars_written reported wrong') if qemu_out.expect('6', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('6', TIMEOUT_SECS).nil? end end @@ -38,7 +45,7 @@ class RxStatistics end def run(qemu_out, _qemu_in) - raise('chars_read reported wrong') if qemu_out.expect('3', TIMEOUT_SECS).nil? + raise ExpectTimeoutError if qemu_out.expect('3', TIMEOUT_SECS).nil? end end diff --git a/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rs b/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rs index 84b74479..03058f5e 100644 --- a/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rs +++ b/16_virtual_mem_part4_higher_half_kernel/tests/00_console_sanity.rs @@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! { print!("{}", console().chars_read()); // The QEMU process running this test will be closed by the I/O test harness. - // cpu::wait_forever(); - - // For some reason, in this test, rustc or the linker produces an empty binary when - // wait_forever() is used. Calling qemu_exit_success() fixes this behavior. So for the time - // being, the following lines are just a workaround to fix this compiler/linker weirdness. - use libkernel::time::interface::TimeManager; - libkernel::time::time_manager().spin_for(core::time::Duration::from_secs(3600)); - cpu::qemu_exit_success() + cpu::wait_forever(); } diff --git a/16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs b/16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs index 30d420a7..47ef31fa 100644 --- a/16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs +++ b/16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs @@ -24,8 +24,8 @@ unsafe fn kernel_init() -> ! { exception::handling_init(); bsp::console::qemu_bring_up_console(); + // This line will be printed as the test header. println!("Testing synchronous exception handling by causing a page fault"); - println!("-------------------------------------------------------------------\n"); println!("Writing to bottom of address space to address 1 GiB..."); let big_addr: u64 = 1 * 1024 * 1024 * 1024; diff --git a/16_virtual_mem_part4_higher_half_kernel/tests/boot_test_string.rb b/16_virtual_mem_part4_higher_half_kernel/tests/boot_test_string.rb new file mode 100644 index 00000000..f778b3d8 --- /dev/null +++ b/16_virtual_mem_part4_higher_half_kernel/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Echoing input now' diff --git a/16_virtual_mem_part4_higher_half_kernel/tests/runner.rb b/16_virtual_mem_part4_higher_half_kernel/tests/runner.rb deleted file mode 100755 index 53116e08..00000000 --- a/16_virtual_mem_part4_higher_half_kernel/tests/runner.rb +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# SPDX-License-Identifier: MIT OR Apache-2.0 -# -# Copyright (c) 2019-2021 Andre Richter - -require 'English' -require 'pty' - -# Test base class. -class Test - INDENT = ' ' - - def print_border(status) - puts - puts "#{INDENT}-------------------------------------------------------------------" - puts status - puts "#{INDENT}-------------------------------------------------------------------\n\n\n" - end - - def print_error(error) - puts - print_border("#{INDENT}❌ Failure: #{error}: #{@test_name}") - end - - def print_success - print_border("#{INDENT}✅ Success: #{@test_name}") - end - - def print_output - puts "#{INDENT}-------------------------------------------------------------------" - print INDENT - print '🦀 ' - print @output.join.gsub("\n", "\n#{INDENT}") - end - - def finish(error) - print_output - - exit_code = if error - print_error(error) - false - else - print_success - true - end - - exit(exit_code) - end -end - -# Executes tests with console I/O. -class ConsoleTest < Test - def initialize(binary, qemu_cmd, test_name, console_subtests) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @console_subtests = console_subtests - @cur_subtest = 1 - @output = ["Running #{@console_subtests.length} console-based tests\n", - "-------------------------------------------------------------------\n\n"] - end - - def format_test_name(number, name) - formatted_name = "#{number.to_s.rjust(3)}. #{name}" - formatted_name.ljust(63, '.') - end - - def run_subtest(subtest, qemu_out, qemu_in) - @output << format_test_name(@cur_subtest, subtest.name) - - subtest.run(qemu_out, qemu_in) - - @output << "[ok]\n" - @cur_subtest += 1 - end - - def exec - error = false - - PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| - begin - @console_subtests.each { |t| run_subtest(t, qemu_out, qemu_in) } - rescue StandardError => e - error = e.message - end - - finish(error) - end - end -end - -# A wrapper around the bare QEMU invocation. -class RawTest < Test - MAX_WAIT_SECS = 5 - - def initialize(binary, qemu_cmd, test_name) - super() - - @binary = binary - @qemu_cmd = qemu_cmd - @test_name = test_name - @output = [] - end - - def exec - error = 'Timed out waiting for test' - io = IO.popen(@qemu_cmd) - - while IO.select([io], nil, nil, MAX_WAIT_SECS) - begin - @output << io.read_nonblock(1024) - rescue EOFError - io.close - error = $CHILD_STATUS.to_i != 0 - break - end - end - - finish(error) - end -end - -##-------------------------------------------------------------------------------------------------- -## Script entry point -##-------------------------------------------------------------------------------------------------- -binary = ARGV.last -test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] -console_test_file = "tests/#{test_name}.rb" -qemu_cmd = ARGV.join(' ') - -test_runner = if File.exist?(console_test_file) - load console_test_file - # subtest_collection is provided by console_test_file - ConsoleTest.new(binary, qemu_cmd, test_name, subtest_collection) - else - RawTest.new(binary, qemu_cmd, test_name) - end - -test_runner.exec diff --git a/X1_JTAG_boot/Makefile b/X1_JTAG_boot/Makefile index b5a56d07..8336ccb7 100644 --- a/X1_JTAG_boot/Makefile +++ b/X1_JTAG_boot/Makefile @@ -2,18 +2,25 @@ ## ## Copyright (c) 2018-2021 Andre Richter -include ../utils/color.mk.in +include ../common/color.mk.in -# Default to the RPi3 +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# 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 + +##-------------------------------------------------------------------------------------------------- +## Hardcoded configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP-specific arguments. ifeq ($(BSP),rpi3) TARGET = aarch64-unknown-none-softfloat KERNEL_BIN = kernel8.img @@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +# Export for build.rs. export LINKER_FILE -QEMU_MISSING_STRING = "This board is not yet supported for QEMU." +KERNEL_ELF = target/$(TARGET)/release/kernel + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs @@ -59,64 +73,103 @@ OBJCOPY_CMD = rust-objcopy \ --strip-all \ -O binary -KERNEL_ELF = target/$(TARGET)/release/kernel +EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) +EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb +EXEC_MINIPUSH = ruby ../common/serial/minipush.rb -DOCKER_IMAGE = rustembedded/osdev-utils -DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial -DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t -DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils -DOCKER_ARG_DEV = --privileged -v /dev:/dev +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_IMAGE = rustembedded/osdev-utils +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/../common:/work/common +DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) -# Dockerize commands that require USB device passthrough only on Linux -ifeq ($(UNAME_S),Linux) +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) - DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) + DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif -EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE) -EXEC_MINIPUSH = ruby ../utils/minipush.rb + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check all: $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the kernel ELF +##------------------------------------------------------------------------------ $(KERNEL_ELF): $(call colorecho, "\nCompiling kernel - $(BSP)") @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) +##------------------------------------------------------------------------------ +## Build the stripped kernel binary +##------------------------------------------------------------------------------ $(KERNEL_BIN): $(KERNEL_ELF) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Build the documentation +##------------------------------------------------------------------------------ doc: $(call colorecho, "\nGenerating docs") @$(DOC_CMD) --document-private-items --open -ifeq ($(QEMU_MACHINE_TYPE),) +##------------------------------------------------------------------------------ +## Run the kernel in QEMU +##------------------------------------------------------------------------------ +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)") -else + +else # QEMU is supported. + qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + endif +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run clippy +##------------------------------------------------------------------------------ clippy: @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ clean: rm -rf target $(KERNEL_BIN) +##------------------------------------------------------------------------------ +## Run readelf +##------------------------------------------------------------------------------ readelf: $(KERNEL_ELF) $(call colorecho, "\nLaunching readelf") @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) +##------------------------------------------------------------------------------ +## Run objdump +##------------------------------------------------------------------------------ objdump: $(KERNEL_ELF) $(call colorecho, "\nLaunching objdump") @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @@ -125,10 +178,40 @@ objdump: $(KERNEL_ELF) --section .got \ $(KERNEL_ELF) | rustfilt +##------------------------------------------------------------------------------ +## Run nm +##------------------------------------------------------------------------------ nm: $(KERNEL_ELF) $(call colorecho, "\nLaunching nm") @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt -# For rust-analyzer +##------------------------------------------------------------------------------ +## Helper target for rust-analyzer +##------------------------------------------------------------------------------ check: @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json + + + +##-------------------------------------------------------------------------------------------------- +## Testing targets +##-------------------------------------------------------------------------------------------------- +.PHONY: test test_boot + +ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board. + +test_boot test : + $(call colorecho, "\n$(QEMU_MISSING_STRING)") + +else # QEMU is supported. + +##------------------------------------------------------------------------------ +## Run boot test +##------------------------------------------------------------------------------ +test_boot: $(KERNEL_BIN) + $(call colorecho, "\nBoot test - $(BSP)") + @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) + +test: test_boot + +endif diff --git a/X1_JTAG_boot/tests/boot_test_string.rb b/X1_JTAG_boot/tests/boot_test_string.rb new file mode 100644 index 00000000..029dbd06 --- /dev/null +++ b/X1_JTAG_boot/tests/boot_test_string.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +EXPECTED_PRINT = 'Please connect over JTAG now' diff --git a/utils/color.mk.in b/common/color.mk.in similarity index 100% rename from utils/color.mk.in rename to common/color.mk.in diff --git a/utils/minipush.rb b/common/serial/minipush.rb similarity index 83% rename from utils/minipush.rb rename to common/serial/minipush.rb index a9ee9bb2..b18c4983 100755 --- a/utils/minipush.rb +++ b/common/serial/minipush.rb @@ -14,19 +14,19 @@ class ProtocolError < StandardError; end # The main class class MiniPush < MiniTerm - def initialize(serial_name, binary_image_path) + def initialize(serial_name, payload_path) super(serial_name) @name_short = 'MP' # override - @binary_image_path = binary_image_path - @binary_size = nil - @binary_image = nil + @payload_path = payload_path + @payload_size = nil + @payload_data = nil end private # The three characters signaling the request token form the consecutive sequence "\x03\x03\x03". - def wait_for_binary_request + def wait_for_payload_request puts "[#{@name_short}] 🔌 Please power the target now" # Timeout for the request token starts after the first sign of life was received. @@ -54,27 +54,28 @@ class MiniPush < MiniTerm end end - def load_binary - @binary_size = File.size(@binary_image_path) - @binary_image = File.binread(@binary_image_path) + def load_payload + @payload_size = File.size(@payload_path) + @payload_data = File.binread(@payload_path) end def send_size - @target_serial.print([@binary_size].pack('L<')) + @target_serial.print([@payload_size].pack('L<')) raise ProtocolError if @target_serial.read(2) != 'OK' end - def send_binary + def send_payload pb = ProgressBar.create( - total: @binary_size, + total: @payload_size, format: "[#{@name_short}] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a", rate_scale: ->(rate) { rate / 1024 }, - length: 92 + length: 92, + output: $stdout ) # Send in 512 byte chunks. while pb.progress < pb.total - part = @binary_image.slice(pb.progress, 512) + part = @payload_data.slice(pb.progress, 512) pb.progress += @target_serial.write(part) end end @@ -95,10 +96,10 @@ class MiniPush < MiniTerm # override def run open_serial - wait_for_binary_request - load_binary + wait_for_payload_request + load_payload send_size - send_binary + send_payload terminal rescue ConnectionError, EOFError, Errno::EIO, ProtocolError, Timeout::Error => e handle_reconnect(e) diff --git a/utils/minipush/progressbar_patch.rb b/common/serial/minipush/progressbar_patch.rb similarity index 100% rename from utils/minipush/progressbar_patch.rb rename to common/serial/minipush/progressbar_patch.rb diff --git a/utils/miniterm.rb b/common/serial/miniterm.rb similarity index 99% rename from utils/miniterm.rb rename to common/serial/miniterm.rb index 10cc4bc5..06e7efd1 100755 --- a/utils/miniterm.rb +++ b/common/serial/miniterm.rb @@ -7,8 +7,9 @@ require 'rubygems' require 'bundler/setup' -require 'io/console' + require 'colorize' +require 'io/console' require 'serialport' SERIAL_BAUD = 921_600 diff --git a/common/tests/boot_test.rb b/common/tests/boot_test.rb new file mode 100644 index 00000000..1f5301f9 --- /dev/null +++ b/common/tests/boot_test.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2021 Andre Richter + +require_relative 'test' +require 'timeout' + +# Check for an expected string when booting the kernel in QEMU. +class BootTest < Test + MAX_WAIT_SECS = 5 + + def initialize(qemu_cmd, expected_print) + super() + + @qemu_cmd = qemu_cmd + @expected_print = expected_print + + @test_name = 'Boot test' + @test_description = "Checking for the string: '#{@expected_print}'" + @test_output = [] + @test_error = nil + end + + private + + def expected_string_observed?(qemu_output) + qemu_output.join.include?(@expected_print) + end + + # Convert the recorded output to an array of lines. + def post_process_and_add_output(qemu_output) + @test_output += qemu_output.join.split("\n") + end + + # override + def setup + @qemu_serial = IO.popen(@qemu_cmd, err: '/dev/null') + @qemu_pid = @qemu_serial.pid + end + + # override + def cleanup + Timeout.timeout(MAX_WAIT_SECS) do + Process.kill('TERM', @qemu_pid) + Process.wait + end + rescue StandardError => e + puts 'QEMU graceful shutdown didn\'t work. Skipping it.' + puts e + end + + def run_concrete_test + qemu_output = [] + Timeout.timeout(MAX_WAIT_SECS) do + while IO.select([@qemu_serial]) + qemu_output << @qemu_serial.read_nonblock(1024) + + if expected_string_observed?(qemu_output) + @test_error = false + break + end + end + end + rescue EOFError + @test_error = 'QEMU quit unexpectedly' + rescue Timeout::Error + @test_error = 'Timed out waiting for magic string' + rescue StandardError => e + @test_error = e.message + ensure + post_process_and_add_output(qemu_output) + end +end diff --git a/common/tests/console_io_test.rb b/common/tests/console_io_test.rb new file mode 100644 index 00000000..66822a4e --- /dev/null +++ b/common/tests/console_io_test.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2019-2021 Andre Richter + +require 'pty' +require_relative 'test' + +# A test doing console I/O with the QEMU binary. +class ConsoleIOTest < Test + def initialize(qemu_cmd, test_name, console_subtests) + super() + + @qemu_cmd = qemu_cmd + @console_subtests = console_subtests + + @test_name = test_name + @test_description = "Running #{@console_subtests.length} console I/O tests" + @test_output = [] + @test_error = nil + end + + private + + def format_test_name(number, name) + formatted_name = "#{number.to_s.rjust(3)}. #{name}" + formatted_name.ljust(63, '.') + end + + def run_subtest(subtest, test_id, qemu_out, qemu_in) + @test_output << format_test_name(test_id, subtest.name) + subtest.run(qemu_out, qemu_in) + @test_output.last.concat('[ok]') + end + + def run_concrete_test + @test_error = false + + PTY.spawn(@qemu_cmd) do |qemu_out, qemu_in| + @console_subtests.each_with_index do |t, i| + run_subtest(t, i + 1, qemu_out, qemu_in) + end + rescue StandardError => e + @test_error = e.message + end + end +end diff --git a/common/tests/dispatch.rb b/common/tests/dispatch.rb new file mode 100755 index 00000000..14827265 --- /dev/null +++ b/common/tests/dispatch.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2019-2021 Andre Richter + +require_relative 'boot_test' +require_relative 'console_io_test' +require_relative 'exit_code_test' + +qemu_cmd = ARGV.join(' ') +binary = ARGV.last +test_name = binary.gsub(%r{.*deps/}, '').split('-')[0] + +case test_name +when 'kernel8.img' + load 'tests/boot_test_string.rb' # provides 'EXPECTED_PRINT' + BootTest.new(qemu_cmd, EXPECTED_PRINT).run # Doesn't return + +when 'libkernel' + ExitCodeTest.new(qemu_cmd, 'Kernel library unit tests').run # Doesn't return + +else + console_test_file = "tests/#{test_name}.rb" + test_name.concat('.rs') + test = if File.exist?(console_test_file) + load console_test_file # provides 'subtest_collection' + ConsoleIOTest.new(qemu_cmd, test_name, subtest_collection) + else + ExitCodeTest.new(qemu_cmd, test_name) + end + + test.run # Doesn't return +end diff --git a/common/tests/exit_code_test.rb b/common/tests/exit_code_test.rb new file mode 100644 index 00000000..2f14ab78 --- /dev/null +++ b/common/tests/exit_code_test.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2019-2021 Andre Richter + +require 'English' +require_relative 'test' + +# A test that only inspects the exit code of the QEMU binary. +class ExitCodeTest < Test + MAX_WAIT_SECS = 5 + + def initialize(qemu_cmd, test_name) + super() + + @qemu_cmd = qemu_cmd + + @test_name = test_name + @test_description = nil + @test_output = [] + @test_error = nil + end + + private + + # Convert the recorded output to an array of lines, and extract the test description. + def post_process_output + @test_output = @test_output.join.split("\n") + @test_description = @test_output.shift + end + + # override + def setup + @qemu_serial = IO.popen(@qemu_cmd) + end + + def run_concrete_test + Timeout.timeout(MAX_WAIT_SECS) do + @test_output << @qemu_serial.read_nonblock(1024) while IO.select([@qemu_serial]) + end + rescue EOFError + @qemu_serial.close + @test_error = $CHILD_STATUS.to_i.zero? ? false : 'QEMU exit status != 0' + rescue Timeout::Error + @test_error = 'Timed out waiting for test' + rescue StandardError => e + @test_error = e.message + ensure + post_process_output + end +end diff --git a/common/tests/test.rb b/common/tests/test.rb new file mode 100644 index 00000000..b0f67f3a --- /dev/null +++ b/common/tests/test.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2019-2021 Andre Richter + +# Test base class. +class Test + INDENT = ' ' + + def initialize + # Template instance variables. + # @test_name + # @test_description + # @test_output + # @test_error + end + + private + + def print_border(content) + puts "#{INDENT}-------------------------------------------------------------------" + puts content + puts "#{INDENT}-------------------------------------------------------------------" + end + + def print_header + print_border("#{INDENT}🦀 #{@test_description}") + puts + end + + def print_footer_error(error) + puts + print_border("#{INDENT}❌ Failure: #{@test_name}: #{error}") + puts + puts + end + + def print_footer_success + puts + print_border("#{INDENT}✅ Success: #{@test_name}") + puts + puts + end + + # Expects @test_output the be an array of lines, without '\n' + def print_output + @test_output.each { |x| print "#{INDENT}#{x}\n" } + end + + # Template method. + def setup; end + + # Template method. + def cleanup; end + + # Template method. + def run_concrete_test + raise('Not implemented') + end + + public + + def run + setup + run_concrete_test + cleanup + + print_header + print_output + + exit_code = if @test_error + print_footer_error(@test_error) + false + else + print_footer_success + true + end + + exit(exit_code) + end +end diff --git a/devtool_completion.bash b/devtool_completion.bash index 3831425c..ce4c67b0 100755 --- a/devtool_completion.bash +++ b/devtool_completion.bash @@ -1,3 +1,3 @@ #!/usr/bin/env bash -complete -W "clean clippy copyright diff fmt fmt_check make make_xtra misspell ready_for_publish ready_for_publish_no_rust rubocop test_integration test_unit test_xtra update" devtool +complete -W "clean clippy copyright diff fmt fmt_check make make_xtra misspell ready_for_publish ready_for_publish_no_rust rubocop test test_boot test_integration test_unit test_xtra update" devtool diff --git a/doc/12_demo.gif b/doc/12_demo.gif new file mode 100644 index 00000000..8afa23c1 Binary files /dev/null and b/doc/12_demo.gif differ diff --git a/doc/13_demo.gif b/doc/13_demo.gif deleted file mode 100644 index 959e35ab..00000000 Binary files a/doc/13_demo.gif and /dev/null differ diff --git a/doc/demo_PS1.txt b/doc/demo_PS1.txt new file mode 100644 index 00000000..f83e206e --- /dev/null +++ b/doc/demo_PS1.txt @@ -0,0 +1 @@ +export PS1="\[\033[01;32m\]rust@osdev\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\] >> " diff --git a/utils/devtool.rb b/utils/devtool.rb index 7639a1b4..270866d7 100755 --- a/utils/devtool.rb +++ b/utils/devtool.rb @@ -11,7 +11,7 @@ require 'colorize' require 'fileutils' require_relative 'devtool/copyright' -# Actions for tutorial folders +# Actions for tutorial folders. class TutorialCrate attr_reader :folder @@ -26,16 +26,19 @@ class TutorialCrate def clean puts 'Cleaning '.light_blue + @folder - Dir.chdir(@folder) { system('make clean') } + # No output needed. + Dir.chdir(@folder) { `make clean` } end def update + puts "\n\n" puts 'Updating '.light_blue + @folder Dir.chdir(@folder) { system('cargo update') } end def clippy(bsp) + puts "\n\n" puts "Clippy #{@folder} - BSP: #{bsp}".light_blue Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make clippy") } @@ -46,40 +49,60 @@ class TutorialCrate end def make(bsp) + puts "\n\n" puts "Make #{@folder} - BSP: #{bsp}".light_blue Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make") } end - def test_unit - return unless kernel_tests? + def test(bsp) + return unless boot_test? - puts "Unit Tests #{@folder}".light_blue + puts "\n\n" + puts "Test #{@folder} - BSP: #{bsp}".light_blue - Dir.chdir(@folder) { exit(1) unless system('TEST=unit make test') } + Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make test") } end - def test_integration - return unless kernel_tests? + def test_boot(bsp) + return unless boot_test? - puts "Integration Tests #{@folder}".light_blue + puts "\n\n" + puts "Test Boot #{@folder} - BSP: #{bsp}".light_blue - Dir.chdir(@folder) do - Dir['tests/*.rs'].sort.each do |t| - t = t.delete_prefix('tests/').delete_suffix('.rs') - exit(1) unless system("TEST=#{t} make test") - end - end + Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make test_boot") } + end + + def test_unit(bsp) + return unless unit_integration_tests? + + puts "\n\n" + puts "Test Unit #{@folder} - BSP: #{bsp}".light_blue + + Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make test_unit") } + end + + def test_integration(bsp) + return unless unit_integration_tests? + + puts "\n\n" + puts "Test Integration #{@folder} - BSP: #{bsp}".light_blue + + Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make test_integration") } end private - def kernel_tests? - File.exist?("#{@folder}/tests/runner.rb") + def boot_test? + Dir.exist?("#{@folder}/tests") + end + + def unit_integration_tests? + !Dir.glob("#{@folder}/tests/00_*.rs").empty? end end -# Forks commands to all applicable receivers +# Forks commands to all applicable receivers. class DevTool def initialize @user_has_supplied_crates = false @@ -100,11 +123,7 @@ class DevTool def clippy(bsp = nil) bsp ||= @bsp - @crates.each do |c| - c.clippy(bsp) - puts - puts - end + @crates.each { |c| c.clippy(bsp) } end def diff @@ -132,34 +151,40 @@ class DevTool def make(bsp = nil) bsp ||= @bsp - @crates.each do |c| - c.make(bsp) - puts - puts - end + @crates.each { |c| c.make(bsp) } end def make_xtra return if @user_has_supplied_crates + puts "\n\n" puts 'Make Xtra stuff'.light_blue system('cd *_uart_chainloader && bash update.sh') system('cd X1_JTAG_boot && bash update.sh') end - def test_xtra - return if @user_has_supplied_crates + def test(bsp = nil) + bsp ||= @bsp - puts 'Test Xtra stuff'.light_blue - exit(1) unless system('cd *_uart_chainloader && make test') + @crates.each { |c| c.test(bsp) } end - def test_unit - @crates.each(&:test_unit) + def test_boot(bsp = nil) + bsp ||= @bsp + + @crates.each { |c| c.test_boot(bsp) } end - def test_integration - @crates.each(&:test_integration) + def test_unit(bsp = nil) + bsp ||= @bsp + + @crates.each { |c| c.test_unit(bsp) } + end + + def test_integration(bsp = nil) + bsp ||= @bsp + + @crates.each { |c| c.test_integration(bsp) } end def copyright @@ -176,31 +201,25 @@ class DevTool exit(1) unless system('bundle exec rubocop') end - def ready_for_publish + def ready_for_publish_no_rust clean fmt - misspell rubocop - clippy('rpi4') - clippy('rpi3') copyright diff - - clean - make_xtra - test_xtra - test_unit - test_integration + misspell clean end - def ready_for_publish_no_rust - clean - fmt - misspell - rubocop - copyright - diff + def ready_for_publish + ready_for_publish_no_rust + + make_xtra + clippy('rpi4') + clippy('rpi3') + test_boot('rpi3') + test_unit('rpi3') + test_integration('rpi3') clean end @@ -288,6 +307,7 @@ class DevTool tracked_files.select do |f| next unless File.exist?(f) next if f.include?('build.rs') + next if f.include?('boot_test_string.rb') f.include?('Makefile') || f.include?('Dockerfile') || diff --git a/utils/devtool/copyright.rb b/utils/devtool/copyright.rb index 956efce1..37aba399 100644 --- a/utils/devtool/copyright.rb +++ b/utils/devtool/copyright.rb @@ -4,6 +4,8 @@ # # Copyright (c) 2018-2021 Andre Richter +require 'rubygems' +require 'bundler/setup' require 'colorize' def copyright_check_files(source_files) diff --git a/utils/update_copyright.rb b/utils/update_copyright.rb index c86d8ac3..927bc93c 100755 --- a/utils/update_copyright.rb +++ b/utils/update_copyright.rb @@ -5,6 +5,8 @@ # # Copyright (c) 2021 Andre Richter +require 'rubygems' +require 'bundler/setup' require 'date' files = `git ls-files`.split("\n")