diff --git a/.cargo/config.toml b/.cargo/config.toml index c424c85..b7f1846 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,3 +8,4 @@ rustflags = [ "-C", "target-cpu=cortex-a53", "-C", "embed-bitcode=yes", ] +runner = "cargo make test-runner" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5b3c54..85c4962 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,18 @@ jobs: run: rustup component add rust-src llvm-tools-preview - name: "Install build tools" - run: cargo install cargo-make + run: cargo install cargo-make cargo-binutils + + - name: "Validate rust-lld" + run: | + which rust-lld || echo "Not found" + otool -L ~/.cargo/bin/rust-lld + if: runner.os == 'macOS' + + - name: "Print Tools Version" + run: | + cargo make --version + cargo objcopy --version - name: "Deny Warnings" run: cargo make build @@ -55,6 +66,8 @@ jobs: - name: Install QEMU (Linux) run: | + sudo apt install software-properties-common + sudo add-apt-repository ppa:jacob/virtualisation sudo apt update sudo apt install qemu-system-aarch64 if: runner.os == 'Linux' @@ -85,7 +98,8 @@ jobs: - name: 'Build kernel' run: cargo make build - # TODO: add tests runner + - name: 'Run tests' + run: cargo make test check_formatting: name: "Check Formatting" diff --git a/Justfile b/Justfile index b955a30..c21df01 100644 --- a/Justfile +++ b/Justfile @@ -14,3 +14,6 @@ clean: clippy: cargo make clippy + +test: + cargo make test diff --git a/Makefile.toml b/Makefile.toml index 644a6b7..0cef496 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -29,7 +29,8 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64" # # -d in_asm,unimp,int QEMU_OPTS = "-M raspi3 -d int -semihosting" -QEMU_SERIAL = "-serial null -serial stdio" +QEMU_SERIAL_OPTS = "-serial null -serial stdio" +QEMU_TESTS_OPTS = "-nographic" QEMU = "qemu-system-aarch64" # For gdb connection: diff --git a/README.md b/README.md index 9c9d5fb..77da3c7 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,12 @@ To build kernel for Raspberry Pi and copy it to SDCard mounted at `/Volumes/BOOT just device ``` +To run tests (tests require QEMU): + +``` +just test +``` + On the device boot SD card you'll need a configuration file instructing RasPi to launch in 64-bit mode. ``` diff --git a/nucleus/Makefile.toml b/nucleus/Makefile.toml index e56f538..40787e1 100644 --- a/nucleus/Makefile.toml +++ b/nucleus/Makefile.toml @@ -9,10 +9,26 @@ script = [ "${OBJCOPY} ${OBJCOPY_PARAMS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${DEFAULT_TARGET}/release/vesper ${KERNEL_BIN}" ] +[tasks.custom-binary] +script = [ + "cp ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.elf", + "${OBJCOPY} ${OBJCOPY_PARAMS} ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.bin" +] + [tasks.build] env = { "TARGET_FEATURES" = "" } args = ["build", "${BUILD_STD}", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"] +[tasks.test] +env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" } +args = ["test", "${BUILD_STD}", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"] + +[tasks.test-runner] +dependencies = ["custom-binary"] +script = [ + "${QEMU} ${QEMU_OPTS} ${QEMU_TESTS_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.bin" +] + [tasks.build-qemu] env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" } command = "cargo" diff --git a/nucleus/src/macros.rs b/nucleus/src/macros.rs new file mode 100644 index 0000000..c986bb9 --- /dev/null +++ b/nucleus/src/macros.rs @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: BlueOak-1.0.0 + */ + +// https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::macros::_print(format_args!($($arg)*))); +} + +// https://doc.rust-lang.org/src/std/macros.rs.html +#[macro_export] +macro_rules! println { + () => (print!("\n")); + ($($arg:tt)*) => ({ + $crate::macros::_print(format_args_nl!($($arg)*)); + }) +} + +#[doc(hidden)] +#[cfg(not(any(test, qemu)))] +pub fn _print(_args: core::fmt::Arguments) { + // @todo real system implementation +} + +#[doc(hidden)] +#[cfg(any(test, qemu))] // qemu feature not enabled here?? we pass --features=qemu to cargo test +pub fn _print(args: core::fmt::Arguments) { + use crate::{qemu, write_to}; + let mut buf = [0u8; 512]; + qemu::semihosting_sys_write0_call(write_to::c_show(&mut buf, args).unwrap()); +} diff --git a/nucleus/src/main.rs b/nucleus/src/main.rs index 4a6fea2..466d97d 100644 --- a/nucleus/src/main.rs +++ b/nucleus/src/main.rs @@ -3,13 +3,26 @@ */ #![no_std] #![no_main] +#![feature(asm)] +#![feature(format_args_nl)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::tests::test_runner)] +#![reexport_test_harness_main = "test_main"] #[cfg(not(target_arch = "aarch64"))] use architecture_not_supported_sorry; +extern crate rlibc; // To enable linking memory intrinsics. + #[macro_use] pub mod arch; pub use arch::*; +mod macros; +#[cfg(feature = "qemu")] +mod qemu; +#[cfg(test)] +mod tests; +mod write_to; entry!(kmain); @@ -17,6 +30,9 @@ entry!(kmain); // arch crate is responsible for calling this #[inline] pub fn kmain() -> ! { + #[cfg(test)] + test_main(); + endless_sleep() } diff --git a/nucleus/src/qemu.rs b/nucleus/src/qemu.rs new file mode 100644 index 0000000..0612d81 --- /dev/null +++ b/nucleus/src/qemu.rs @@ -0,0 +1,11 @@ +#[cfg(test)] +pub fn semihosting_sys_write0_call(text: &str) { + // SAFETY: text must be \0-terminated! + unsafe { + asm!( + "mov w0, #0x04 + hlt #0xF000" + , in("x1") text.as_ptr() as u64 + ); + } +} diff --git a/nucleus/src/tests.rs b/nucleus/src/tests.rs new file mode 100644 index 0000000..cae6ed9 --- /dev/null +++ b/nucleus/src/tests.rs @@ -0,0 +1,14 @@ +//============================================================================ +// Testing environment +//============================================================================ +use crate::println; + +#[cfg(test)] +pub fn test_runner(tests: &[&dyn Fn()]) { + println!("Running {} tests", tests.len()); + for test in tests { + test(); + } + println!("[success]"); + qemu_exit::aarch64::exit_success(); +}