Refactor Tests and Makefile

- Carve out common files for tests
- Add boot tests starting tutorial 3
- Overhaul the Makefile for more structure
pull/117/head
Andre Richter 3 years ago committed by Andre Richter
parent 15a1e717fb
commit de3ba3e871

@ -5,8 +5,6 @@
#
# Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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') ||

@ -2,12 +2,22 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -2,12 +2,22 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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

@ -2,12 +2,22 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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'
```

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Stopping here'

@ -2,12 +2,22 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Stopping here'

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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'
```

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -2,49 +2,63 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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 <andre.o.richter@gmail.com>
+
+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

@ -0,0 +1,80 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -1,80 +0,0 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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 <andre.o.richter@gmail.com>
-
-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

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Spinning for 1 second'

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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
##--------------------------------------------------------------------------------------------------
```

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Spinning for 1 second'

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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'
```

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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'
```

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'lr : 0x'

@ -2,18 +2,32 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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.
<img src="../doc/13_demo.gif" width="880">
<img src="../doc/12_demo.gif" width="880">
## 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 <andre.o.richter@gmail.com>
@ -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 <andre.o.richter@gmail.com>
@ -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 <andre.o.richter@gmail.com>
+
+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

@ -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);

@ -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()
}

@ -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

@ -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();
}

@ -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);

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -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 <andre.o.richter@gmail.com>
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

@ -2,18 +2,32 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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

@ -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);

@ -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()
}

@ -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

@ -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();
}

@ -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);

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -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 <andre.o.richter@gmail.com>
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

@ -2,18 +2,32 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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);

@ -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);

@ -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()
}

@ -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

@ -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();
}

@ -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) => {

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -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 <andre.o.richter@gmail.com>
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

@ -2,18 +2,32 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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) => {

@ -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);

@ -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()
}

@ -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

@ -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();
}

@ -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;

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -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 <andre.o.richter@gmail.com>
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

@ -2,18 +2,32 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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;

@ -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);

@ -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()
}

@ -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

@ -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();
}

@ -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;

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Echoing input now'

@ -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 <andre.o.richter@gmail.com>
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

@ -2,18 +2,25 @@
##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -0,0 +1,3 @@
# frozen_string_literal: true
EXPECTED_PRINT = 'Please connect over JTAG now'

@ -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)

@ -7,8 +7,9 @@
require 'rubygems'
require 'bundler/setup'
require 'io/console'
require 'colorize'
require 'io/console'
require 'serialport'
SERIAL_BAUD = 921_600

@ -0,0 +1,75 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -0,0 +1,48 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2019-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -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 <andre.o.richter@gmail.com>
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

@ -0,0 +1,52 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2019-2021 Andre Richter <andre.o.richter@gmail.com>
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

@ -0,0 +1,82 @@
# frozen_string_literal: true
# SPDX-License-Identifier: MIT OR Apache-2.0
#
# Copyright (c) 2019-2021 Andre Richter <andre.o.richter@gmail.com>
# 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

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

@ -0,0 +1 @@
export PS1="\[\033[01;32m\]rust@osdev\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\] >> "

@ -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') ||

@ -4,6 +4,8 @@
#
# Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
require 'rubygems'
require 'bundler/setup'
require 'colorize'
def copyright_check_files(source_files)

@ -5,6 +5,8 @@
#
# Copyright (c) 2021 Andre Richter <andre.o.richter@gmail.com>
require 'rubygems'
require 'bundler/setup'
require 'date'
files = `git ls-files`.split("\n")

Loading…
Cancel
Save