diff --git a/Cargo.lock b/Cargo.lock index 935a6cc17..2bc64e95b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,6 +118,7 @@ dependencies = [ "gdbstub_arch", "log", "packit", + "test", ] [[package]] @@ -131,6 +132,10 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test" +version = "0.1.0" + [[package]] name = "unicode-ident" version = "1.0.10" diff --git a/Cargo.toml b/Cargo.toml index b4cf3471b..dc279caf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,8 @@ gdbstub_arch = { version = "0.2.4", optional = true } log = { version = "0.4.17", features = ["max_level_info", "release_max_level_info"] } packit = { git = "https://github.com/coconut-svsm/packit", version = "0.1.0" } -[build-dependencies] +[target."x86_64-unknown-none".dev-dependencies] +test = { version = "0.1.0", path = "test" } [features] default = ["enable-stacktrace"] diff --git a/Makefile b/Makefile index 4d8621477..711c65e2c 100644 --- a/Makefile +++ b/Makefile @@ -2,16 +2,19 @@ FEATURES ?= "default" CARGO_ARGS = --features ${FEATURES} ifdef RELEASE -TARGET_PATH="release" +TARGET_PATH=release CARGO_ARGS += --release else -TARGET_PATH="debug" +TARGET_PATH=debug endif STAGE2_ELF = "target/x86_64-unknown-none/${TARGET_PATH}/stage2" KERNEL_ELF = "target/x86_64-unknown-none/${TARGET_PATH}/svsm" +TEST_KERNEL_ELF = target/x86_64-unknown-none/${TARGET_PATH}/svsm-test FS_FILE ?= none +C_BIT_POS ?= 51 + STAGE1_OBJS = stage1/stage1.o stage1/reset.o all: stage1/kernel.elf svsm.bin @@ -19,6 +22,29 @@ all: stage1/kernel.elf svsm.bin test: cargo test --target=x86_64-unknown-linux-gnu +test-in-svsm: stage1/test-kernel.elf svsm.bin +ifndef QEMU + echo "Set QEMU environment variable to QEMU installation path" && exit 1 +endif +ifndef OVMF + echo "Set OVMFenvironment variable to a folder containing OVMF_CODE.fd and OVMF_VARS.fd" && exit 1 +endif + $(QEMU)/qemu-system-x86_64 \ + -enable-kvm \ + -cpu EPYC-v4 \ + -machine q35,confidential-guest-support=sev0,memory-backend=ram1,kvm-type=protected \ + -object memory-backend-memfd-private,id=ram1,size=1G,share=true \ + -object sev-snp-guest,id=sev0,cbitpos=$(C_BIT_POS),reduced-phys-bits=1,svsm=on \ + -smp 8 \ + -no-reboot \ + -drive if=pflash,format=raw,unit=0,file=$(OVMF)/OVMF_CODE.fd,readonly=on \ + -drive if=pflash,format=raw,unit=1,file=$(OVMF)/OVMF_VARS.fd,snapshot=on \ + -drive if=pflash,format=raw,unit=2,file=./svsm.bin,readonly=on \ + -nographic \ + -monitor none \ + -serial stdio \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 || true + utils/gen_meta: utils/gen_meta.c cc -O3 -Wall -o $@ $< @@ -36,6 +62,10 @@ stage1/kernel.elf: cargo build ${CARGO_ARGS} --bin svsm objcopy -O elf64-x86-64 --strip-unneeded ${KERNEL_ELF} $@ +stage1/test-kernel.elf: + LINK_TEST=1 cargo +nightly test --config 'target.x86_64-unknown-none.runner=["sh", "-c", "cp $$0 ${TEST_KERNEL_ELF}"]' + objcopy -O elf64-x86-64 --strip-unneeded ${TEST_KERNEL_ELF} stage1/kernel.elf + stage1/svsm-fs.bin: ifneq ($(FS_FILE), none) cp -f $(FS_FILE) stage1/svsm-fs.bin @@ -57,4 +87,4 @@ clean: cargo clean rm -f stage1/stage2.bin svsm.bin stage1/meta.bin stage1/kernel.elf stage1/stage1 stage1/svsm-fs.bin ${STAGE1_OBJS} utils/gen_meta utils/print-meta -.PHONY: stage1/stage2.bin stage1/kernel.elf svsm.bin clean stage1/svsm-fs.bin test +.PHONY: stage1/stage2.bin stage1/kernel.elf stage1/test-kernel.elf svsm.bin clean stage1/svsm-fs.bin test test-in-svsm diff --git a/build.rs b/build.rs index 9ca054b1c..638649628 100644 --- a/build.rs +++ b/build.rs @@ -17,4 +17,14 @@ fn main() { println!("cargo:rustc-link-arg-bin=svsm=--no-relax"); println!("cargo:rustc-link-arg-bin=svsm=-Tsvsm.lds"); println!("cargo:rustc-link-arg-bin=svsm=-no-pie"); + + // Extra linker args for tests. + println!("cargo:rerun-if-env-changed=LINK_TEST"); + if std::env::var("LINK_TEST").is_ok() { + println!("cargo:rustc-link-arg=-nostdlib"); + println!("cargo:rustc-link-arg=--build-id=none"); + println!("cargo:rustc-link-arg=--no-relax"); + println!("cargo:rustc-link-arg=-Tsvsm.lds"); + println!("cargo:rustc-link-arg=-no-pie"); + } } diff --git a/src/lib.rs b/src/lib.rs index a7664c3bd..4808ec31f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,16 @@ // Author: Nicolai Stange #![no_std] +#![cfg_attr(all(test, target_os = "none"), no_main)] +#![cfg_attr(all(test, target_os = "none"), feature(custom_test_frameworks))] +#![cfg_attr( + all(test, target_os = "none"), + test_runner(crate::testing::svsm_test_runner) +)] +#![cfg_attr( + all(test, target_os = "none"), + reexport_test_harness_main = "test_main" +)] pub mod acpi; pub mod address; @@ -33,3 +43,15 @@ pub mod utils; #[test] fn test_nop() {} + +// When running tests inside the SVSM: +// Build the kernel entrypoint. +#[cfg(all(test, target_os = "none"))] +#[path = "svsm.rs"] +pub mod svsm_bin; +// The kernel expects to access this crate as svsm, so reexport. +#[cfg(all(test, target_os = "none"))] +extern crate self as svsm; +// Include a module containing the test runner. +#[cfg(all(test, target_os = "none"))] +pub mod testing; diff --git a/src/svsm.rs b/src/svsm.rs index b78fb5f64..d36facd89 100644 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -4,8 +4,8 @@ // // Author: Joerg Roedel -#![no_std] -#![no_main] +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] extern crate alloc; @@ -470,6 +470,9 @@ pub extern "C" fn svsm_main() { panic!("Failed to launch FW: {:#?}", e); } + #[cfg(test)] + crate::test_main(); + request_loop(); panic!("Road ends here!"); diff --git a/src/testing.rs b/src/testing.rs new file mode 100644 index 000000000..793967d61 --- /dev/null +++ b/src/testing.rs @@ -0,0 +1,33 @@ +use log::info; +use test::ShouldPanic; + +use crate::sev::msr_protocol::request_termination_msr; + +pub fn svsm_test_runner(test_cases: &[&test::TestDescAndFn]) { + info!("running {} tests", test_cases.len()); + for mut test_case in test_cases.iter().copied().copied() { + if test_case.desc.should_panic == ShouldPanic::Yes { + test_case.desc.ignore = true; + test_case + .desc + .ignore_message + .get_or_insert("#[should_panic] not supported"); + } + + if test_case.desc.ignore { + if let Some(message) = test_case.desc.ignore_message { + info!("test {} ... ignored, {message}", test_case.desc.name.0); + } else { + info!("test {} ... ignored", test_case.desc.name.0); + } + continue; + } + + info!("test {} ...", test_case.desc.name.0); + (test_case.testfn.0)(); + } + + info!("All tests passed!"); + + request_termination_msr(); +}