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> # Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>
require 'rubygems'
require 'bundler/setup'
require_relative '../utils/devtool/copyright' require_relative '../utils/devtool/copyright'
def copyright_check(staged_files) def copyright_check(staged_files)
@ -14,6 +12,7 @@ def copyright_check(staged_files)
staged_files = staged_files.select do |f| staged_files = staged_files.select do |f|
next if f.include?('build.rs') next if f.include?('build.rs')
next if f.include?('boot_test_string.rb')
f.include?('Makefile') || f.include?('Makefile') ||
f.include?('Dockerfile') || f.include?('Dockerfile') ||

@ -2,12 +2,22 @@
## ##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> ## 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 ?= rpi3
# BSP-specific arguments
##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -53,61 +70,99 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
--section .text \ --section .text \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json

@ -2,12 +2,22 @@
## ##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> ## 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 ?= rpi3
# BSP-specific arguments
##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -53,51 +70,84 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -106,10 +156,15 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 01_wait_forever/Makefile 02_runtime_init/Makefile
--- 01_wait_forever/Makefile --- 01_wait_forever/Makefile
+++ 02_runtime_init/Makefile +++ 02_runtime_init/Makefile
@@ -102,6 +102,8 @@ @@ -152,6 +152,8 @@
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
--section .text \ --section .text \
@ -61,7 +61,7 @@ diff -uNr 01_wait_forever/Makefile 02_runtime_init/Makefile
+ --section .got \ + --section .got \
$(KERNEL_ELF) | rustfilt $(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 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 --- 01_wait_forever/src/_arch/aarch64/cpu/boot.rs

@ -2,12 +2,22 @@
## ##
## Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> ## 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 ?= rpi3
# BSP-specific arguments
##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -53,51 +70,87 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -106,10 +159,40 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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/console.rs` introduces interface `Traits` for console commands.
- `src/bsp/raspberrypi/console.rs` implements the interface for QEMU's emulated UART. - `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. - 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 ## 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 diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile
--- 02_runtime_init/Makefile --- 02_runtime_init/Makefile
+++ 03_hacky_hello_world/Makefile +++ 03_hacky_hello_world/Makefile
@@ -13,7 +13,7 @@ @@ -23,7 +23,7 @@
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64 QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3 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 OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf READELF_BINARY = aarch64-none-elf-readelf
@@ -24,7 +24,7 @@ @@ -34,7 +34,7 @@
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64 QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = QEMU_MACHINE_TYPE =
@ -61,6 +65,60 @@ diff -uNr 02_runtime_init/Makefile 03_hacky_hello_world/Makefile
OBJDUMP_BINARY = aarch64-none-elf-objdump OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf 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 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 --- 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> ## 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 ?= rpi3
# BSP-specific arguments
##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -32,11 +42,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -53,51 +70,87 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -106,10 +159,40 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -59,64 +73,102 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_DEV = --privileged -v /dev:/dev 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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 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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Connect to the target's serial
##------------------------------------------------------------------------------
miniterm: miniterm:
@$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -125,10 +177,40 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 04_safe_globals/Makefile 05_drivers_gpio_uart/Makefile
--- 04_safe_globals/Makefile --- 04_safe_globals/Makefile
+++ 05_drivers_gpio_uart/Makefile +++ 05_drivers_gpio_uart/Makefile
@@ -7,6 +7,12 @@ @@ -11,6 +11,9 @@
# Default to the RPi3 # Default to the RPi3.
BSP ?= rpi3 BSP ?= rpi3
+# Default to a serial device name that is common in Linux. +# Default to a serial device name that is common in Linux.
+DEV_SERIAL ?= /dev/ttyUSB0 +DEV_SERIAL ?= /dev/ttyUSB0
+ +
+# Query the host system's kernel name
+UNAME_S = $(shell uname -s)
+ ##--------------------------------------------------------------------------------------------------
# BSP-specific arguments @@ -72,6 +75,7 @@
ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
@@ -58,13 +64,23 @@ EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
DOCKER_IMAGE = rustembedded/osdev-utils +EXEC_MINITERM = ruby ../common/serial/miniterm.rb
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 ## Dockerization
+DOCKER_ARG_DEV = --privileged -v /dev:/dev @@ -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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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, which require USB device passthrough, only on Linux.
+# Dockerize commands that require USB device passthrough only on Linux +ifeq ($(shell uname -s),Linux)
+ifeq ($(UNAME_S),Linux)
+ DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) + 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 +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 clippy clean readelf objdump nm check
+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
@@ -88,6 +104,9 @@ @@ -130,6 +142,12 @@
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
+## Connect to the target's serial
+##------------------------------------------------------------------------------
+miniterm: +miniterm:
+ @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) + @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
+ +
+##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
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 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 --- 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() 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64 QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3 QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none QEMU_RELEASE_ARGS = -serial stdio -display none
OBJDUMP_BINARY = aarch64-none-elf-objdump OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf READELF_BINARY = aarch64-none-elf-readelf
LINKER_FILE = src/bsp/raspberrypi/link.ld LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img
else ifeq ($(BSP),rpi4) else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
QEMU_BINARY = qemu-system-aarch64 QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = QEMU_MACHINE_TYPE =
QEMU_RELEASE_ARGS = -serial stdio -display none QEMU_RELEASE_ARGS = -serial stdio -display none
OBJDUMP_BINARY = aarch64-none-elf-objdump OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf READELF_BINARY = aarch64-none-elf-readelf
LINKER_FILE = src/bsp/raspberrypi/link.ld LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -61,49 +75,69 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -O binary
KERNEL_ELF = target/$(TARGET)/release/kernel EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb
DOCKER_IMAGE = rustembedded/osdev-utils EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DEV = --privileged -v /dev:/dev ##------------------------------------------------------------------------------
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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
@ -112,26 +146,36 @@ qemuasm: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU with ASM output") $(call colorecho, "\nLaunching QEMU with ASM output")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm @$(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 endif
chainboot: ##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD)
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -140,10 +184,41 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 05_drivers_gpio_uart/Makefile 06_uart_chainloader/Makefile
--- 05_drivers_gpio_uart/Makefile --- 05_drivers_gpio_uart/Makefile
+++ 06_uart_chainloader/Makefile +++ 06_uart_chainloader/Makefile
@@ -25,6 +25,7 @@ @@ -22,27 +22,29 @@
READELF_BINARY = aarch64-none-elf-readelf
LINKER_FILE = src/bsp/raspberrypi/link.ld # BSP-specific arguments.
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 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 + CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img
else ifeq ($(BSP),rpi4) else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat - TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img - KERNEL_BIN = kernel8.img
@@ -36,6 +37,7 @@ - QEMU_BINARY = qemu-system-aarch64
READELF_BINARY = aarch64-none-elf-readelf - QEMU_MACHINE_TYPE =
LINKER_FILE = src/bsp/raspberrypi/link.ld - QEMU_RELEASE_ARGS = -serial stdio -display none
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - 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 + CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
@@ -68,19 +70,22 @@ @@ -74,8 +76,8 @@
DOCKER_ARG_DEV = --privileged -v /dev:/dev -O binary
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) -EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) -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_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)
+ DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) + DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE)
endif 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 miniterm clippy clean readelf objdump nm check
+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu qemuasm chainboot clippy clean readelf objdump nm \ +.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
+ check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
@@ -96,16 +101,26 @@ @@ -131,7 +133,7 @@
@$(DOC_CMD) --document-private-items --open ##------------------------------------------------------------------------------
ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.
ifeq ($(QEMU_MACHINE_TYPE),)
-qemu: -qemu:
+qemu test: +qemu qemuasm:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
@@ -139,13 +141,18 @@
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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) +qemuasm: $(KERNEL_BIN)
+ $(call colorecho, "\nLaunching QEMU with ASM output") + $(call colorecho, "\nLaunching QEMU with ASM output")
+ @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm + @$(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 endif
##------------------------------------------------------------------------------
-## Connect to the target's serial
+## Push the kernel to the real HW target
##------------------------------------------------------------------------------
-miniterm: -miniterm:
- @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL) - @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
+chainboot: +chainboot: $(KERNEL_BIN)
+ @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) + @$(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 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 --- 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() + kernel()
} }
diff -uNr 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/qemu_minipush.rb --- 05_drivers_gpio_uart/tests/boot_test_string.rb
+++ 06_uart_chainloader/tests/qemu_minipush.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 @@ @@ -0,0 +1,80 @@
+# frozen_string_literal: true +# 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> +# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>
+ +
+require_relative '../../utils/minipush' +require_relative '../../common/serial/minipush'
+require 'expect' +require_relative '../../common/tests/boot_test'
+require 'timeout' +require 'pty'
+ +
+# Match for the last print that 'demo_payload_rpiX.img' produces. +# Match for the last print that 'demo_payload_rpiX.img' produces.
+EXPECTED_PRINT = 'Echoing input now' +EXPECTED_PRINT = 'Echoing input now'
+ +
+# The main class +# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected
+class QEMUMiniPush < MiniPush +# to a QEMU instance instead of a real HW.
+ TIMEOUT_SECS = 3 +class ChainbootTest < BootTest
+ MINIPUSH = '../common/serial/minipush.rb'
+ MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now'
+ +
+ # override + def initialize(qemu_cmd, payload_path)
+ def initialize(qemu_cmd, binary_image_path) + super(qemu_cmd, EXPECTED_PRINT)
+ super(nil, binary_image_path) +
+ @test_name = 'Boot test using Minipush'
+ +
+ @qemu_cmd = qemu_cmd + @payload_path = payload_path
+ end + end
+ +
+ private + private
+ +
+ def quit_qemu_graceful
+ Timeout.timeout(5) do
+ pid = @target_serial.pid
+ Process.kill('TERM', pid)
+ Process.wait(pid)
+ end
+ end
+
+ # override + # override
+ def open_serial + def post_process_and_add_output(output)
+ @target_serial = IO.popen(@qemu_cmd, 'r+', err: '/dev/null') + temp = output.join.split("\r\n")
+ +
+ # Ensure all output is immediately flushed to the device. + # Should a line have solo carriage returns, remove any overridden parts of the string.
+ @target_serial.sync = true + temp.map! { |x| x.gsub(/.*\r/, '') }
+ +
+ puts "[#{@name_short}] ✅ Serial connected" + @test_output += temp
+ end + end
+ +
+ # override + def wait_for_minipush_power_request(mp_out)
+ def terminal + output = []
+ result = @target_serial.expect(EXPECTED_PRINT, TIMEOUT_SECS) + Timeout.timeout(MAX_WAIT_SECS) do
+ exit(1) if result.nil? + loop do
+ + output << mp_out.gets
+ puts result + break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST)
+ + end
+ quit_qemu_graceful + 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 + end
+ +
+ # override + # 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 + # Wait until MiniPush asks for powering the target.
+ def handle_reconnect(error) + wait_for_minipush_power_request(mp_out)
+ handle_unexpected(error) +
+ # 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
+end +end
+ +
+##-------------------------------------------------------------------------------------------------- +##--------------------------------------------------------------------------------------------------
+## Execution starts here +## Execution starts here
+##-------------------------------------------------------------------------------------------------- +##--------------------------------------------------------------------------------------------------
+puts +payload_path = ARGV.pop
+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(' ') +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 diff -uNr 05_drivers_gpio_uart/update.sh 06_uart_chainloader/update.sh
--- 05_drivers_gpio_uart/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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -59,64 +73,103 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_DEV = --privileged -v /dev:/dev 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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 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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -125,10 +178,40 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 06_uart_chainloader/Makefile 07_timestamps/Makefile
--- 06_uart_chainloader/Makefile --- 06_uart_chainloader/Makefile
+++ 07_timestamps/Makefile +++ 07_timestamps/Makefile
@@ -25,7 +25,6 @@ @@ -22,29 +22,27 @@
READELF_BINARY = aarch64-none-elf-readelf
LINKER_FILE = src/bsp/raspberrypi/link.ld # BSP-specific arguments.
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 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 - 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) else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat - TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img - KERNEL_BIN = kernel8.img
@@ -37,7 +36,6 @@ - QEMU_BINARY = qemu-system-aarch64
READELF_BINARY = aarch64-none-elf-readelf - QEMU_MACHINE_TYPE =
LINKER_FILE = src/bsp/raspberrypi/link.ld - QEMU_RELEASE_ARGS = -serial stdio -display none
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 - 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 - 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 endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
@@ -70,7 +68,6 @@ @@ -76,7 +74,7 @@
DOCKER_ARG_DEV = --privileged -v /dev:/dev -O binary
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
-DOCKER_TEST = $(DOCKER_CMD) -t $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) -EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb
DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +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 @@ @@ -133,7 +131,7 @@
DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE) ##------------------------------------------------------------------------------
endif ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.
-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)
@@ -101,26 +96,16 @@ -qemu qemuasm:
@$(DOC_CMD) --document-private-items --open
ifeq ($(QEMU_MACHINE_TYPE),)
-qemu test:
+qemu: +qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
qemu: $(KERNEL_BIN) else # QEMU is supported.
@@ -142,17 +140,13 @@
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
-
-qemuasm: $(KERNEL_BIN) -qemuasm: $(KERNEL_BIN)
- $(call colorecho, "\nLaunching QEMU with ASM output") - $(call colorecho, "\nLaunching QEMU with ASM output")
- @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm - @$(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 endif
-chainboot: ##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN)
- @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD) - @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD)
+chainboot: $(KERNEL_BIN)
+ @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(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 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 --- 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 diff -uNr 06_uart_chainloader/tests/boot_test_string.rb 07_timestamps/tests/boot_test_string.rb
--- 06_uart_chainloader/tests/qemu_minipush.rb --- 06_uart_chainloader/tests/boot_test_string.rb
+++ 07_timestamps/tests/qemu_minipush.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 @@ @@ -1,80 +0,0 @@
-# frozen_string_literal: true -# 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> -# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>
- -
-require_relative '../../utils/minipush' -require_relative '../../common/serial/minipush'
-require 'expect' -require_relative '../../common/tests/boot_test'
-require 'timeout' -require 'pty'
- -
-# Match for the last print that 'demo_payload_rpiX.img' produces. -# Match for the last print that 'demo_payload_rpiX.img' produces.
-EXPECTED_PRINT = 'Echoing input now' -EXPECTED_PRINT = 'Echoing input now'
- -
-# The main class -# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected
-class QEMUMiniPush < MiniPush -# to a QEMU instance instead of a real HW.
- TIMEOUT_SECS = 3 -class ChainbootTest < BootTest
- MINIPUSH = '../common/serial/minipush.rb'
- MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now'
- -
- # override - def initialize(qemu_cmd, payload_path)
- def initialize(qemu_cmd, binary_image_path) - super(qemu_cmd, EXPECTED_PRINT)
- super(nil, binary_image_path)
- -
- @qemu_cmd = qemu_cmd - @test_name = 'Boot test using Minipush'
-
- @payload_path = payload_path
- end - end
- -
- private - private
- -
- def quit_qemu_graceful
- Timeout.timeout(5) do
- pid = @target_serial.pid
- Process.kill('TERM', pid)
- Process.wait(pid)
- end
- end
-
- # override - # override
- def open_serial - def post_process_and_add_output(output)
- @target_serial = IO.popen(@qemu_cmd, 'r+', err: '/dev/null') - temp = output.join.split("\r\n")
- -
- # Ensure all output is immediately flushed to the device. - # Should a line have solo carriage returns, remove any overridden parts of the string.
- @target_serial.sync = true - temp.map! { |x| x.gsub(/.*\r/, '') }
- -
- puts "[#{@name_short}] ✅ Serial connected" - @test_output += temp
- end - end
- -
- # override - def wait_for_minipush_power_request(mp_out)
- def terminal - output = []
- result = @target_serial.expect(EXPECTED_PRINT, TIMEOUT_SECS) - Timeout.timeout(MAX_WAIT_SECS) do
- exit(1) if result.nil? - loop do
- - output << mp_out.gets
- puts result - break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST)
- - end
- quit_qemu_graceful - 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 - end
- -
- # override - # 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 - # Wait until MiniPush asks for powering the target.
- def handle_reconnect(error) - wait_for_minipush_power_request(mp_out)
- handle_unexpected(error) -
- # 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
-end -end
- -
-##-------------------------------------------------------------------------------------------------- -##--------------------------------------------------------------------------------------------------
-## Execution starts here -## Execution starts here
-##-------------------------------------------------------------------------------------------------- -##--------------------------------------------------------------------------------------------------
-puts -payload_path = ARGV.pop
-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(' ') -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 diff -uNr 06_uart_chainloader/update.sh 07_timestamps/update.sh
--- 06_uart_chainloader/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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 07_timestamps/Makefile 08_hw_debug_JTAG/Makefile
--- 07_timestamps/Makefile --- 07_timestamps/Makefile
+++ 08_hw_debug_JTAG/Makefile +++ 08_hw_debug_JTAG/Makefile
@@ -23,6 +23,8 @@ @@ -30,6 +30,8 @@
OBJDUMP_BINARY = aarch64-none-elf-objdump OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf 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 LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 RUSTC_MISC_ARGS = -C target-cpu=cortex-a53
else ifeq ($(BSP),rpi4) else ifeq ($(BSP),rpi4)
@@ -34,6 +36,8 @@ @@ -41,6 +43,8 @@
OBJDUMP_BINARY = aarch64-none-elf-objdump OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf 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 LINKER_FILE = src/bsp/raspberrypi/link.ld
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
@@ -65,9 +69,12 @@ @@ -84,17 +88,24 @@
DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils 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_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_ARG_DEV = --privileged -v /dev:/dev
+DOCKER_ARG_NET = --network host +DOCKER_ARG_NET = --network host
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
+DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
@@ -75,12 +82,17 @@ ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
+ DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) + DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
+else +else
+ DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# + DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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 @@ -193,6 +204,35 @@
+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy \
+ clean readelf objdump nm check
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: +jtagboot:
+ @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE)
+ +
+##------------------------------------------------------------------------------
+## Start OpenOCD session
+##------------------------------------------------------------------------------
+openocd: +openocd:
+ $(call colorecho, "\nLaunching OpenOCD") + $(call colorecho, "\nLaunching OpenOCD")
+ @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG)
+ +
+##------------------------------------------------------------------------------
+## Start GDB session
+##------------------------------------------------------------------------------
+gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb: RUSTC_MISC_ARGS += -C debuginfo=2
+gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0 +gdb-opt0: RUSTC_MISC_ARGS += -C debuginfo=2 -C opt-level=0
+gdb gdb-opt0: $(KERNEL_ELF) +gdb gdb-opt0: $(KERNEL_ELF)
+ $(call colorecho, "\nLaunching GDB") + $(call colorecho, "\nLaunching GDB")
+ @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) + @$(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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -42,11 +49,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -63,85 +77,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -150,10 +189,69 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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. // 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name # Optional integration test name.
UNAME_S = $(shell uname -s) ifdef TEST
TEST_ARG = --test $(TEST)
else
TEST_ARG = --test '*'
endif
# BSP-specific arguments ##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE export LINKER_FILE
# Testing-specific arguments KERNEL_ELF = target/$(TARGET)/release/kernel
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."
##--------------------------------------------------------------------------------------------------
## Command building blocks
##--------------------------------------------------------------------------------------------------
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_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_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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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 endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 ## tl;dr
- We implement our own test framework using `Rust`'s [custom_test_frameworks] feature by enabling - We implement our own integrated test framework using `Rust`'s [custom_test_frameworks] feature by
`Unit Tests` and `Integration Tests` using `QEMU`. 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 - It is also possible to have test automation for I/O with the kernel's `console` (provided over
our case): Sending strings/characters to the console and expecting specific answers in return. `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 ## 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. - 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. - 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 The feature set of the kernel is now rich enough so that it makes sense to introduce proper
modeled after Rust's [native testing framework]. This tutorial extends our kernel with three basic integrated testing modeled after Rust's [native testing framework]. This tutorial extends our single
testing facilities: existing kernel test with three new testing facilities:
- Classic `Unit Tests`. - Classic `Unit Tests`.
- [Integration Tests] (self-contained tests stored in the `$CRATE/tests/` directory). - [Integration Tests] (self-contained tests stored in the `$CRATE/tests/` directory).
- `Console Tests`. These are integration tests acting on external stimuli - aka `console` input. - `Console I/O Tests`. These are integration tests acting on external stimuli - aka `console`
Sending strings/characters to the console and expecting specific answers in return. 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 [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 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 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 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 Please note that for automation purposes, all testing will be done in `QEMU` and not on real
hardware. hardware.
@ -82,15 +84,23 @@ additional insights.
## Implementation ## Implementation
We introduce a new `Makefile` target: We introduce two new `Makefile` targets:
```console ```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 In essence, the `make test_*` targets will execute `cargo test` instead of `cargo rustc`. The
explained in due course. The rest of the tutorial will explain as chronologically as possible what details will be explained in due course. The rest of the tutorial will explain as chronologically as
happens when `make test` aka `cargo test` runs. 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 ### Test Organization
@ -166,8 +176,9 @@ that we are supposed to provide. This is the one that will be called by the `car
```rust ```rust
/// The default runner for unit tests. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);
@ -255,7 +266,7 @@ opportunity to cut down on setup code.
[tutorial 03]: ../03_hacky_hello_world [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 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. 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 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: 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 We've enabled `custom_test_frameworks` in `lib.rs` to a point where, when using a `make test_unit`
code gets compiled to a test kernel binary that eventually executes all the (yet-to-be-defined) target, the code gets compiled to a test kernel binary that eventually executes all the
`UnitTest` instances by executing all the way from `_start()` to our `test_runner()` function. (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 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`? exectues this test kernel. The question now is: How is test success/failure communicated to `cargo`?
@ -339,30 +351,30 @@ concludes:
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }
} }
``` ```
In case none of the unit tests panicked, `lib.rs`'s `kernel_init()` calls `cpu::qemu_exit_success()` In case _none_ of the unit tests panicked, `lib.rs`'s `kernel_init()` calls
to successfully conclude the unit test run. `cpu::qemu_exit_success()` to successfully conclude the unit test run.
### Controlling Test Kernel Execution ### Controlling Test Kernel Execution
Now is a good time to catch up on how the test kernel binary is actually being executed. Normally, 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 `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 horribly because we build a kernel, and not a userspace process. Also, chances are high that you sit
you sit in front of an `x86` machine, whereas the RPi kernel is `AArch64`. 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`, 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`: first step is to add a new file to the project, `.cargo/config.toml`:
```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 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. 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 The file `kernel_test_runner.sh` does not exist by default. We generate it on demand when one of the
`make test` target: `make test_*` targets is called:
```Makefile ```Makefile
##------------------------------------------------------------------------------
## Helpers for unit and integration test targets
##------------------------------------------------------------------------------
define KERNEL_TEST_RUNNER define KERNEL_TEST_RUNNER
#!/usr/bin/env bash #!/usr/bin/env bash
@ -385,16 +400,26 @@ define KERNEL_TEST_RUNNER
TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g') TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g')
$(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY $(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 endef
export KERNEL_TEST_RUNNER export KERNEL_TEST_RUNNER
test: FEATURES += --features test_build
test: define test_prepare
@mkdir -p target @mkdir -p target
@echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
@chmod +x target/kernel_test_runner.sh @chmod +x target/kernel_test_runner.sh
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG) 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 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`: reference, here it is fully resolved for an `RPi3 BSP`:
```bash ```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 This command is quite similar to the one used in the `make test_boot` target that we have since
excuted inside Docker? `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 #### Wrapping QEMU Test Execution
`runner.rb` is a [Ruby] wrapper script around `QEMU` that, for unit tests, catches the case that a `dispatch.rb` is a [Ruby] script which first determines what kind of test is due by inspecting the
test gets stuck, e.g. in an unintentional busy loop or a crash. If `runner.rb` does not observe any `QEMU`-command that was given to it. In case of `unit tests`, we are only interested if they all
output of the test kernel for `5 seconds`, it cancels the execution and reports a failure back to executed successfully, which can be checked by inspecting `QEMU`'s exit code. So the script takes
`cargo`. If `QEMU` exited itself by means of `aarch64::exit_success() / aarch64::exit_failure()`, the provided qemu command it got from `ARGV`, and creates and runs an instance of `ExitCodeTest`:
the respective exit status code is passed through. The essential part happens here in `class
RawTest`:
```ruby ```ruby
def exec require_relative 'boot_test'
error = 'Timed out waiting for 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) io = IO.popen(@qemu_cmd)
while IO.select([io], nil, nil, MAX_WAIT_SECS) Timeout.timeout(MAX_WAIT_SECS) do
begin @test_output << io.read_nonblock(1024) while IO.select([io])
@output << io.read_nonblock(1024)
rescue EOFError
io.close
error = $CHILD_STATUS.to_i != 0
break
end
end 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/ [Ruby]: https://www.ruby-lang.org/
### Writing Unit Tests ### Writing Unit Tests
Alright, that's a wrap for the whole chain from `make test` all the way to reporting the test exit Alright, that's a wrap for the whole chain from `make test_unit` all the way to reporting the test
status back to `cargo test`. It is a lot to digest already, but we haven't even learned to write exit status back to `cargo test`. It is a lot to digest already, but we haven't even learned to
`Unit Tests` yet. write `Unit Tests` yet.
In essence, it is almost like in `std` environments, with the difference that `#[test]` can't be 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 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: 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. 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. 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. function.
For the sake of brevity, we're not going to discuss the macro implementation. [The source is in the 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"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }
@ -624,7 +681,7 @@ fn _panic_exit() -> ! {
[weak symbol]: https://en.wikipedia.org/wiki/Weak_symbol [weak symbol]: https://en.wikipedia.org/wiki/Weak_symbol
This enables integration tests in `$CRATE/tests/` to override this function according to their 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, 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): 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(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("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); println!("MMU: {}", string);
cpu::qemu_exit_failure() 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. // If execution reaches here, the memory access above did not cause a page fault exception.
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }
``` ```
The `_panic_exit()` version that makes `QEMU` return `0` (indicating test success) is pulled in by 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 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 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 specific answers in return. The `dispatch.rb` wrapper script provides infrastructure to recognize
little overhead. It basically works like this: 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. 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`. - A companion file has the same name, but ends in `.rb`.
- The companion file contains one or more console subtests. - The companion file contains one or more console I/O subtests.
2. If it exists, load the file to dynamically import the console subtests. 1. If it exists, load the file to dynamically import the console subtests.
3. Spawn `QEMU` and attach to the serial console. 1. Create a `ConsoleIOTest` instance and run it.
4. Run the console subtests. - 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 Here is an excerpt from `00_console_sanity.rb` showing a subtest that does a handshake with the
kernel over the console: kernel over the console:
```ruby ```ruby
TIMEOUT_SECS = 3
# Verify sending and receiving works as expected. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -695,7 +750,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
``` ```
@ -727,20 +782,22 @@ unsafe fn kernel_init() -> ! {
## Test it ## 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. 1. `make test` will run all tests back-to-back. That is, the ever existing `boot test` first, then
2. `TEST=unit make test` will run `libkernel`'s unit tests. `unit tests`, then `integration tests`.
3. `TEST=TEST_NAME make test` will run a specficic integration test. 1. `make test_unit` will run `libkernel`'s unit tests.
- For example, `TEST=01_timer_sanity make test` 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 ```console
$ make test $ 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] 1. virt_mem_layout_sections_are_64KiB_aligned................[ok]
@ -749,17 +806,18 @@ $ make test
4. kernel_tables_in_bss......................................[ok] 4. kernel_tables_in_bss......................................[ok]
5. size_of_tabledescriptor_equals_64_bit.....................[ok] 5. size_of_tabledescriptor_equals_64_bit.....................[ok]
6. size_of_pagedescriptor_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] 1. Transmit and Receive handshake............................[ok]
@ -767,11 +825,11 @@ $ make test
3. Receive statistics........................................[ok] 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 🦀 Running 3 tests
------------------------------------------------------------------- -------------------------------------------------------------------
@ -781,11 +839,11 @@ $ make test
3. spin_accuracy_check_1_second..............................[ok] 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 🦀 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 diff -uNr 11_exceptions_part1_groundwork/Makefile 12_integrated_testing/Makefile
--- 11_exceptions_part1_groundwork/Makefile --- 11_exceptions_part1_groundwork/Makefile
+++ 12_integrated_testing/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_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = raspi3 QEMU_MACHINE_TYPE = raspi3
QEMU_RELEASE_ARGS = -serial stdio -display none 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 OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf READELF_BINARY = aarch64-none-elf-readelf
@@ -33,6 +34,7 @@ @@ -40,6 +48,7 @@
QEMU_BINARY = qemu-system-aarch64 QEMU_BINARY = qemu-system-aarch64
QEMU_MACHINE_TYPE = QEMU_MACHINE_TYPE =
QEMU_RELEASE_ARGS = -serial stdio -display none 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 OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm NM_BINARY = aarch64-none-elf-nm
READELF_BINARY = aarch64-none-elf-readelf READELF_BINARY = aarch64-none-elf-readelf
@@ -45,6 +47,15 @@ @@ -73,6 +82,7 @@
# 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 @@
DOC_CMD = cargo doc $(COMPILER_ARGS) DOC_CMD = cargo doc $(COMPILER_ARGS)
CLIPPY_CMD = cargo clippy $(COMPILER_ARGS) CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
CHECK_CMD = cargo check $(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 \ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -O binary
@@ -75,6 +87,7 @@ @@ -236,11 +246,11 @@
##--------------------------------------------------------------------------------------------------
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE) ## Testing targets
DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) ##--------------------------------------------------------------------------------------------------
+DOCKER_TEST = $(DOCKER_CMD) $(DOCKER_IMAGE) -.PHONY: test test_boot
DOCKER_TOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE) +.PHONY: test test_boot test_unit test_integration
# 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
-.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot jtagboot openocd gdb gdb-opt0 clippy \ ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.
- 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
all: $(KERNEL_BIN) -test_boot test :
+test_boot test_unit test_integration test:
$(call colorecho, "\n$(QEMU_MISSING_STRING)")
@@ -108,12 +121,31 @@ else # QEMU is supported.
@$(DOC_CMD) --document-private-items --open @@ -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),) -test: test_boot
-qemu: +##------------------------------------------------------------------------------
+qemu test: +## Helpers for unit and integration test targets
$(call colorecho, "\n$(QEMU_MISSING_STRING)") +##------------------------------------------------------------------------------
else
qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
+
+define KERNEL_TEST_RUNNER +define KERNEL_TEST_RUNNER
+ #!/usr/bin/env bash + #!/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') + TEST_BINARY=$$(echo $$1.img | sed -e 's/.*target/target/g')
+ +
+ $(OBJCOPY_CMD) $$TEST_ELF $$TEST_BINARY + $(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 +endef
+ +
+export KERNEL_TEST_RUNNER +export KERNEL_TEST_RUNNER
+test: FEATURES += --features test_build +
+test: +define test_prepare
+ $(call colorecho, "\nCompiling test(s) - $(BSP)") + @mkdir -p target
+ @mkdir -p target + @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh + @chmod +x 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) + @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 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 --- 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 diff -uNr 11_exceptions_part1_groundwork/src/lib.rs 12_integrated_testing/src/lib.rs
--- 11_exceptions_part1_groundwork/src/lib.rs --- 11_exceptions_part1_groundwork/src/lib.rs
+++ 12_integrated_testing/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 +// SPDX-License-Identifier: MIT OR Apache-2.0
+// +//
+// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> +// 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. +/// The default runner for unit tests.
+pub fn test_runner(tests: &[&test_types::UnitTest]) { +pub fn test_runner(tests: &[&test_types::UnitTest]) {
+ // This line will be printed as the test header.
+ println!("Running {} tests", tests.len()); + println!("Running {} tests", tests.len());
+ println!("-------------------------------------------------------------------\n"); +
+ for (i, test) in tests.iter().enumerate() { + for (i, test) in tests.iter().enumerate() {
+ print!("{:>3}. {:.<58}", i + 1, test.name); + 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"] +#[linkage = "weak"]
+#[no_mangle] +#[no_mangle]
+fn _panic_exit() -> ! { +fn _panic_exit() -> ! {
+ #[cfg(not(test_build))] + #[cfg(not(feature = "test_build"))]
+ { + {
+ cpu::wait_forever() + cpu::wait_forever()
+ } + }
+ +
+ #[cfg(test_build)] + #[cfg(feature = "test_build")]
+ { + {
+ cpu::qemu_exit_failure() + 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 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 --- 11_exceptions_part1_groundwork/tests/00_console_sanity.rb
+++ 12_integrated_testing/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 +# frozen_string_literal: true
+ +
+# SPDX-License-Identifier: MIT OR Apache-2.0 +# 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 +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. +# Verify sending and receiving works as expected.
+class TxRxHandshake +class TxRxHandshake
+ def name + 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) + def run(qemu_out, qemu_in)
+ qemu_in.write_nonblock('ABC') + 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
+end +end
+ +
@ -1738,7 +1811,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate
+ end + end
+ +
+ def run(qemu_out, _qemu_in) + 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
+end +end
+ +
@ -1749,7 +1822,7 @@ diff -uNr 11_exceptions_part1_groundwork/tests/00_console_sanity.rb 12_integrate
+ end + end
+ +
+ def run(qemu_out, _qemu_in) + 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
+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 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 --- 11_exceptions_part1_groundwork/tests/00_console_sanity.rs
+++ 12_integrated_testing/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 +// SPDX-License-Identifier: MIT OR Apache-2.0
+// +//
+// Copyright (c) 2019-2021 Andre Richter <andre.o.richter@gmail.com> +// 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()); + print!("{}", console().chars_read());
+ +
+ // The QEMU process running this test will be closed by the I/O test harness. + // The QEMU process running this test will be closed by the I/O test harness.
+ // cpu::wait_forever(); + 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()
+} +}
diff -uNr 11_exceptions_part1_groundwork/tests/01_timer_sanity.rs 12_integrated_testing/tests/01_timer_sanity.rs 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(); + exception::handling_init();
+ bsp::console::qemu_bring_up_console(); + 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!("Testing synchronous exception handling by causing a page fault");
+ println!("-------------------------------------------------------------------\n");
+ +
+ if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { + if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() {
+ println!("MMU: {}", string); + 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() + 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 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 --- 11_exceptions_part1_groundwork/tests/panic_exit_success/mod.rs
+++ 12_integrated_testing/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() + 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 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 --- 11_exceptions_part1_groundwork/test-types/Cargo.toml
+++ 12_integrated_testing/test-types/Cargo.toml +++ 12_integrated_testing/test-types/Cargo.toml

@ -160,8 +160,9 @@ extern "Rust" {
/// The default runner for unit tests. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);

@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) {
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }

@ -8,6 +8,13 @@ require 'expect'
TIMEOUT_SECS = 3 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. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -16,7 +23,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
@ -27,7 +34,7 @@ class TxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end
@ -38,7 +45,7 @@ class RxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end

@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! {
print!("{}", console().chars_read()); print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness. // The QEMU process running this test will be closed by the I/O test harness.
// cpu::wait_forever(); 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()
} }

@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! {
exception::handling_init(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() {
println!("MMU: {}", string); 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name # Optional integration test name.
UNAME_S = $(shell uname -s) ifdef TEST
TEST_ARG = --test $(TEST)
else
TEST_ARG = --test '*'
endif
# BSP-specific arguments ##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE export LINKER_FILE
# Testing-specific arguments KERNEL_ELF = target/$(TARGET)/release/kernel
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."
##--------------------------------------------------------------------------------------------------
## Command building blocks
##--------------------------------------------------------------------------------------------------
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_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_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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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 endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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" 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 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 --- 12_integrated_testing/src/_arch/aarch64/cpu/smp.rs
+++ 13_exceptions_part2_peripheral_IRQs/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. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);

@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) {
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }

@ -8,6 +8,13 @@ require 'expect'
TIMEOUT_SECS = 3 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. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -16,7 +23,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
@ -27,7 +34,7 @@ class TxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end
@ -38,7 +45,7 @@ class RxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end

@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! {
print!("{}", console().chars_read()); print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness. // The QEMU process running this test will be closed by the I/O test harness.
// cpu::wait_forever(); 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()
} }

@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! {
exception::handling_init(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() {
println!("MMU: {}", string); 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name # Optional integration test name.
UNAME_S = $(shell uname -s) ifdef TEST
TEST_ARG = --test $(TEST)
else
TEST_ARG = --test '*'
endif
# BSP-specific arguments ##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE export LINKER_FILE
# Testing-specific arguments KERNEL_ELF = target/$(TARGET)/release/kernel
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."
##--------------------------------------------------------------------------------------------------
## Command building blocks
##--------------------------------------------------------------------------------------------------
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -75,105 +87,110 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/work/X1_JTAG_boot ##------------------------------------------------------------------------------
DOCKER_ARG_DEV = --privileged -v /dev:/dev DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_NET = --network host 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_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_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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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 endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -182,10 +199,108 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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(); exception::handling_init();
bsp::console::qemu_bring_up_console(); bsp::console::qemu_bring_up_console();
@@ -29,10 +29,30 @@ @@ -29,10 +29,30 @@
// This line will be printed as the test header.
println!("Testing synchronous exception handling by causing a page fault"); println!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
- if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() { - if let Err(string) = memory::mmu::mmu().enable_mmu_and_caching() {
- println!("MMU: {}", string); - println!("MMU: {}", string);

@ -165,8 +165,9 @@ extern "Rust" {
/// The default runner for unit tests. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);

@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) {
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }

@ -8,6 +8,13 @@ require 'expect'
TIMEOUT_SECS = 3 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. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -16,7 +23,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
@ -27,7 +34,7 @@ class TxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end
@ -38,7 +45,7 @@ class RxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end

@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! {
print!("{}", console().chars_read()); print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness. // The QEMU process running this test will be closed by the I/O test harness.
// cpu::wait_forever(); 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()
} }

@ -26,8 +26,8 @@ unsafe fn kernel_init() -> ! {
exception::handling_init(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() {
Err(string) => { 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name # Optional integration test name.
UNAME_S = $(shell uname -s) ifdef TEST
TEST_ARG = --test $(TEST)
else
TEST_ARG = --test '*'
endif
# BSP-specific arguments ##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE export LINKER_FILE
# Testing-specific arguments KERNEL_ELF = target/$(TARGET)/release/kernel
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."
##--------------------------------------------------------------------------------------------------
## Command building blocks
##--------------------------------------------------------------------------------------------------
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -75,107 +87,112 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -O binary
KERNEL_ELF = target/$(TARGET)/release/kernel EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
EXEC_TT_TOOL = ruby translation_table_tool/main.rb
DOCKER_IMAGE = rustembedded/osdev-utils EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DEV = --privileged -v /dev:/dev ##------------------------------------------------------------------------------
DOCKER_ARG_NET = --network host 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_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_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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @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) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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 endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -184,10 +201,109 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 diff -uNr 14_virtual_mem_part2_mmio_remap/Makefile 15_virtual_mem_part3_precomputed_tables/Makefile
--- 14_virtual_mem_part2_mmio_remap/Makefile --- 14_virtual_mem_part2_mmio_remap/Makefile
+++ 15_virtual_mem_part3_precomputed_tables/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): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @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) ## Build the stripped kernel binary
@@ -134,6 +135,7 @@ @@ -271,6 +273,7 @@
TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g') TEST_ELF=$$(echo $$1 | sed -e 's/.*target/target/g')
TEST_BINARY=$$(echo $$1.img | 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 $(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 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 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(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
- let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() { - let phys_kernel_tables_base_addr = match memory::mmu::kernel_map_binary() {
- Err(string) => { - Err(string) => {

@ -165,8 +165,9 @@ extern "Rust" {
/// The default runner for unit tests. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);

@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) {
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }

@ -8,6 +8,13 @@ require 'expect'
TIMEOUT_SECS = 3 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. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -16,7 +23,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
@ -27,7 +34,7 @@ class TxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end
@ -38,7 +45,7 @@ class RxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end

@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! {
print!("{}", console().chars_read()); print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness. // The QEMU process running this test will be closed by the I/O test harness.
// cpu::wait_forever(); 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()
} }

@ -24,8 +24,8 @@ unsafe fn kernel_init() -> ! {
exception::handling_init(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
println!("Writing beyond mapped area to address 9 GiB..."); println!("Writing beyond mapped area to address 9 GiB...");
let big_addr: u64 = 9 * 1024 * 1024 * 1024; 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 DEV_SERIAL ?= /dev/ttyUSB0
# Query the host system's kernel name # Optional integration test name.
UNAME_S = $(shell uname -s) ifdef TEST
TEST_ARG = --test $(TEST)
else
TEST_ARG = --test '*'
endif
# BSP-specific arguments ##--------------------------------------------------------------------------------------------------
## Hardcoded configuration values
##--------------------------------------------------------------------------------------------------
# BSP-specific arguments.
ifeq ($(BSP),rpi3) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -44,20 +58,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE export LINKER_FILE
# Testing-specific arguments KERNEL_ELF = target/$(TARGET)/release/kernel
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."
##--------------------------------------------------------------------------------------------------
## Command building blocks
##--------------------------------------------------------------------------------------------------
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS) RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -75,107 +87,112 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -O binary
KERNEL_ELF = target/$(TARGET)/release/kernel EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
EXEC_TT_TOOL = ruby translation_table_tool/main.rb
DOCKER_IMAGE = rustembedded/osdev-utils EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb
DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
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 ## Dockerization
DOCKER_ARG_DEV = --privileged -v /dev:/dev ##------------------------------------------------------------------------------
DOCKER_ARG_NET = --network host 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_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_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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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)
DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_ARG_DIR_JTAG) $(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) DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE)
else else
DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \#
endif 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) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @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) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(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 endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
jtagboot: ##------------------------------------------------------------------------------
@$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) ## Run clippy
##------------------------------------------------------------------------------
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)
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -184,10 +201,109 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 --- 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 +++ 16_virtual_mem_part4_higher_half_kernel/tests/02_exception_sync_page_fault.rs
@@ -27,8 +27,8 @@ @@ -27,8 +27,8 @@
// This line will be printed as the test header.
println!("Testing synchronous exception handling by causing a page fault"); println!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
- println!("Writing beyond mapped area to address 9 GiB..."); - println!("Writing beyond mapped area to address 9 GiB...");
- let big_addr: u64 = 9 * 1024 * 1024 * 1024; - let big_addr: u64 = 9 * 1024 * 1024 * 1024;

@ -160,8 +160,9 @@ pub fn version() -> &'static str {
/// The default runner for unit tests. /// The default runner for unit tests.
pub fn test_runner(tests: &[&test_types::UnitTest]) { pub fn test_runner(tests: &[&test_types::UnitTest]) {
// This line will be printed as the test header.
println!("Running {} tests", tests.len()); println!("Running {} tests", tests.len());
println!("-------------------------------------------------------------------\n");
for (i, test) in tests.iter().enumerate() { for (i, test) in tests.iter().enumerate() {
print!("{:>3}. {:.<58}", i + 1, test.name); print!("{:>3}. {:.<58}", i + 1, test.name);

@ -23,12 +23,12 @@ fn _panic_print(args: fmt::Arguments) {
#[linkage = "weak"] #[linkage = "weak"]
#[no_mangle] #[no_mangle]
fn _panic_exit() -> ! { fn _panic_exit() -> ! {
#[cfg(not(test_build))] #[cfg(not(feature = "test_build"))]
{ {
cpu::wait_forever() cpu::wait_forever()
} }
#[cfg(test_build)] #[cfg(feature = "test_build")]
{ {
cpu::qemu_exit_failure() cpu::qemu_exit_failure()
} }

@ -8,6 +8,13 @@ require 'expect'
TIMEOUT_SECS = 3 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. # Verify sending and receiving works as expected.
class TxRxHandshake class TxRxHandshake
def name def name
@ -16,7 +23,7 @@ class TxRxHandshake
def run(qemu_out, qemu_in) def run(qemu_out, qemu_in)
qemu_in.write_nonblock('ABC') 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
end end
@ -27,7 +34,7 @@ class TxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end
@ -38,7 +45,7 @@ class RxStatistics
end end
def run(qemu_out, _qemu_in) 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
end end

@ -31,12 +31,5 @@ unsafe fn kernel_init() -> ! {
print!("{}", console().chars_read()); print!("{}", console().chars_read());
// The QEMU process running this test will be closed by the I/O test harness. // The QEMU process running this test will be closed by the I/O test harness.
// cpu::wait_forever(); 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()
} }

@ -24,8 +24,8 @@ unsafe fn kernel_init() -> ! {
exception::handling_init(); exception::handling_init();
bsp::console::qemu_bring_up_console(); 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!("Testing synchronous exception handling by causing a page fault");
println!("-------------------------------------------------------------------\n");
println!("Writing to bottom of address space to address 1 GiB..."); println!("Writing to bottom of address space to address 1 GiB...");
let big_addr: u64 = 1 * 1024 * 1024 * 1024; 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> ## 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 ?= rpi3
# Default to a serial device name that is common in Linux. # Default to a serial device name that is common in Linux.
DEV_SERIAL ?= /dev/ttyUSB0 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) ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img KERNEL_BIN = kernel8.img
@ -38,11 +45,18 @@ else ifeq ($(BSP),rpi4)
RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 RUSTC_MISC_ARGS = -C target-cpu=cortex-a72
endif endif
# Export for build.rs QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
# Export for build.rs.
export LINKER_FILE 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 = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
@ -59,64 +73,103 @@ OBJCOPY_CMD = rust-objcopy \
--strip-all \ --strip-all \
-O binary -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 ## Dockerization
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t ##------------------------------------------------------------------------------
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_ARG_DEV = --privileged -v /dev:/dev 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_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_TOOLS = $(DOCKER_CMD) $(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 # Dockerize commands, which require USB device passthrough, only on Linux.
ifeq ($(UNAME_S),Linux) ifeq ($(shell uname -s),Linux)
DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) 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 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 .PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
all: $(KERNEL_BIN) all: $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the kernel ELF
##------------------------------------------------------------------------------
$(KERNEL_ELF): $(KERNEL_ELF):
$(call colorecho, "\nCompiling kernel - $(BSP)") $(call colorecho, "\nCompiling kernel - $(BSP)")
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(RUSTC_CMD)
##------------------------------------------------------------------------------
## Build the stripped kernel binary
##------------------------------------------------------------------------------
$(KERNEL_BIN): $(KERNEL_ELF) $(KERNEL_BIN): $(KERNEL_ELF)
@$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Build the documentation
##------------------------------------------------------------------------------
doc: doc:
$(call colorecho, "\nGenerating docs") $(call colorecho, "\nGenerating docs")
@$(DOC_CMD) --document-private-items --open @$(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: qemu:
$(call colorecho, "\n$(QEMU_MISSING_STRING)") $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else
else # QEMU is supported.
qemu: $(KERNEL_BIN) qemu: $(KERNEL_BIN)
$(call colorecho, "\nLaunching QEMU") $(call colorecho, "\nLaunching QEMU")
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif endif
##------------------------------------------------------------------------------
## Push the kernel to the real HW target
##------------------------------------------------------------------------------
chainboot: $(KERNEL_BIN) chainboot: $(KERNEL_BIN)
@$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run clippy
##------------------------------------------------------------------------------
clippy: clippy:
@RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD) @RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
##------------------------------------------------------------------------------
## Clean
##------------------------------------------------------------------------------
clean: clean:
rm -rf target $(KERNEL_BIN) rm -rf target $(KERNEL_BIN)
##------------------------------------------------------------------------------
## Run readelf
##------------------------------------------------------------------------------
readelf: $(KERNEL_ELF) readelf: $(KERNEL_ELF)
$(call colorecho, "\nLaunching readelf") $(call colorecho, "\nLaunching readelf")
@$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF) @$(DOCKER_TOOLS) $(READELF_BINARY) --headers $(KERNEL_ELF)
##------------------------------------------------------------------------------
## Run objdump
##------------------------------------------------------------------------------
objdump: $(KERNEL_ELF) objdump: $(KERNEL_ELF)
$(call colorecho, "\nLaunching objdump") $(call colorecho, "\nLaunching objdump")
@$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \ @$(DOCKER_TOOLS) $(OBJDUMP_BINARY) --disassemble --demangle \
@ -125,10 +178,40 @@ objdump: $(KERNEL_ELF)
--section .got \ --section .got \
$(KERNEL_ELF) | rustfilt $(KERNEL_ELF) | rustfilt
##------------------------------------------------------------------------------
## Run nm
##------------------------------------------------------------------------------
nm: $(KERNEL_ELF) nm: $(KERNEL_ELF)
$(call colorecho, "\nLaunching nm") $(call colorecho, "\nLaunching nm")
@$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt @$(DOCKER_TOOLS) $(NM_BINARY) --demangle --print-size $(KERNEL_ELF) | sort | rustfilt
# For rust-analyzer ##------------------------------------------------------------------------------
## Helper target for rust-analyzer
##------------------------------------------------------------------------------
check: check:
@RUSTFLAGS="$(RUSTFLAGS)" $(CHECK_CMD) --message-format=json @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 # The main class
class MiniPush < MiniTerm class MiniPush < MiniTerm
def initialize(serial_name, binary_image_path) def initialize(serial_name, payload_path)
super(serial_name) super(serial_name)
@name_short = 'MP' # override @name_short = 'MP' # override
@binary_image_path = binary_image_path @payload_path = payload_path
@binary_size = nil @payload_size = nil
@binary_image = nil @payload_data = nil
end end
private private
# The three characters signaling the request token form the consecutive sequence "\x03\x03\x03". # 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" puts "[#{@name_short}] 🔌 Please power the target now"
# Timeout for the request token starts after the first sign of life was received. # Timeout for the request token starts after the first sign of life was received.
@ -54,27 +54,28 @@ class MiniPush < MiniTerm
end end
end end
def load_binary def load_payload
@binary_size = File.size(@binary_image_path) @payload_size = File.size(@payload_path)
@binary_image = File.binread(@binary_image_path) @payload_data = File.binread(@payload_path)
end end
def send_size 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' raise ProtocolError if @target_serial.read(2) != 'OK'
end end
def send_binary def send_payload
pb = ProgressBar.create( pb = ProgressBar.create(
total: @binary_size, total: @payload_size,
format: "[#{@name_short}] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a", format: "[#{@name_short}] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a",
rate_scale: ->(rate) { rate / 1024 }, rate_scale: ->(rate) { rate / 1024 },
length: 92 length: 92,
output: $stdout
) )
# Send in 512 byte chunks. # Send in 512 byte chunks.
while pb.progress < pb.total 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) pb.progress += @target_serial.write(part)
end end
end end
@ -95,10 +96,10 @@ class MiniPush < MiniTerm
# override # override
def run def run
open_serial open_serial
wait_for_binary_request wait_for_payload_request
load_binary load_payload
send_size send_size
send_binary send_payload
terminal terminal
rescue ConnectionError, EOFError, Errno::EIO, ProtocolError, Timeout::Error => e rescue ConnectionError, EOFError, Errno::EIO, ProtocolError, Timeout::Error => e
handle_reconnect(e) handle_reconnect(e)

@ -7,8 +7,9 @@
require 'rubygems' require 'rubygems'
require 'bundler/setup' require 'bundler/setup'
require 'io/console'
require 'colorize' require 'colorize'
require 'io/console'
require 'serialport' require 'serialport'
SERIAL_BAUD = 921_600 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 #!/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 'fileutils'
require_relative 'devtool/copyright' require_relative 'devtool/copyright'
# Actions for tutorial folders # Actions for tutorial folders.
class TutorialCrate class TutorialCrate
attr_reader :folder attr_reader :folder
@ -26,16 +26,19 @@ class TutorialCrate
def clean def clean
puts 'Cleaning '.light_blue + @folder puts 'Cleaning '.light_blue + @folder
Dir.chdir(@folder) { system('make clean') } # No output needed.
Dir.chdir(@folder) { `make clean` }
end end
def update def update
puts "\n\n"
puts 'Updating '.light_blue + @folder puts 'Updating '.light_blue + @folder
Dir.chdir(@folder) { system('cargo update') } Dir.chdir(@folder) { system('cargo update') }
end end
def clippy(bsp) def clippy(bsp)
puts "\n\n"
puts "Clippy #{@folder} - BSP: #{bsp}".light_blue puts "Clippy #{@folder} - BSP: #{bsp}".light_blue
Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make clippy") } Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make clippy") }
@ -46,40 +49,60 @@ class TutorialCrate
end end
def make(bsp) def make(bsp)
puts "\n\n"
puts "Make #{@folder} - BSP: #{bsp}".light_blue puts "Make #{@folder} - BSP: #{bsp}".light_blue
Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make") } Dir.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make") }
end end
def test_unit def test(bsp)
return unless kernel_tests? 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 end
def test_integration def test_boot(bsp)
return unless kernel_tests? 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.chdir(@folder) { exit(1) unless system("BSP=#{bsp} make test_boot") }
Dir['tests/*.rs'].sort.each do |t| end
t = t.delete_prefix('tests/').delete_suffix('.rs')
exit(1) unless system("TEST=#{t} make test") def test_unit(bsp)
end return unless unit_integration_tests?
end
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 end
private private
def kernel_tests? def boot_test?
File.exist?("#{@folder}/tests/runner.rb") Dir.exist?("#{@folder}/tests")
end
def unit_integration_tests?
!Dir.glob("#{@folder}/tests/00_*.rs").empty?
end end
end end
# Forks commands to all applicable receivers # Forks commands to all applicable receivers.
class DevTool class DevTool
def initialize def initialize
@user_has_supplied_crates = false @user_has_supplied_crates = false
@ -100,11 +123,7 @@ class DevTool
def clippy(bsp = nil) def clippy(bsp = nil)
bsp ||= @bsp bsp ||= @bsp
@crates.each do |c| @crates.each { |c| c.clippy(bsp) }
c.clippy(bsp)
puts
puts
end
end end
def diff def diff
@ -132,34 +151,40 @@ class DevTool
def make(bsp = nil) def make(bsp = nil)
bsp ||= @bsp bsp ||= @bsp
@crates.each do |c| @crates.each { |c| c.make(bsp) }
c.make(bsp)
puts
puts
end
end end
def make_xtra def make_xtra
return if @user_has_supplied_crates return if @user_has_supplied_crates
puts "\n\n"
puts 'Make Xtra stuff'.light_blue puts 'Make Xtra stuff'.light_blue
system('cd *_uart_chainloader && bash update.sh') system('cd *_uart_chainloader && bash update.sh')
system('cd X1_JTAG_boot && bash update.sh') system('cd X1_JTAG_boot && bash update.sh')
end end
def test_xtra def test(bsp = nil)
return if @user_has_supplied_crates bsp ||= @bsp
puts 'Test Xtra stuff'.light_blue @crates.each { |c| c.test(bsp) }
exit(1) unless system('cd *_uart_chainloader && make test')
end end
def test_unit def test_boot(bsp = nil)
@crates.each(&:test_unit) bsp ||= @bsp
@crates.each { |c| c.test_boot(bsp) }
end end
def test_integration def test_unit(bsp = nil)
@crates.each(&:test_integration) 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 end
def copyright def copyright
@ -176,31 +201,25 @@ class DevTool
exit(1) unless system('bundle exec rubocop') exit(1) unless system('bundle exec rubocop')
end end
def ready_for_publish def ready_for_publish_no_rust
clean clean
fmt fmt
misspell
rubocop rubocop
clippy('rpi4')
clippy('rpi3')
copyright copyright
diff diff
misspell
clean
make_xtra
test_xtra
test_unit
test_integration
clean clean
end end
def ready_for_publish_no_rust def ready_for_publish
clean ready_for_publish_no_rust
fmt
misspell make_xtra
rubocop clippy('rpi4')
copyright clippy('rpi3')
diff test_boot('rpi3')
test_unit('rpi3')
test_integration('rpi3')
clean clean
end end
@ -288,6 +307,7 @@ class DevTool
tracked_files.select do |f| tracked_files.select do |f|
next unless File.exist?(f) next unless File.exist?(f)
next if f.include?('build.rs') next if f.include?('build.rs')
next if f.include?('boot_test_string.rb')
f.include?('Makefile') || f.include?('Makefile') ||
f.include?('Dockerfile') || f.include?('Dockerfile') ||

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

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

Loading…
Cancel
Save