Skip to content

Commit

Permalink
testing: support test_in_svsm on non-QEMU targets
Browse files Browse the repository at this point in the history
Tests that require interaction with a QEMU-based test environment (such
as specific I/O ports) should only be invoked when running in that test
environment.

Signed-off-by: Jon Lange <jlange@microsoft.com>
  • Loading branch information
msft-jlange committed Dec 17, 2024
1 parent 928e8ff commit 1f460da
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 78 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ test-igvm: bin/coconut-test-qemu.igvm bin/coconut-test-hyperv.igvm bin/coconut-t
test-in-svsm: utils/cbit bin/coconut-test-qemu.igvm $(IGVMMEASUREBIN)
./scripts/test-in-svsm.sh

test-in-hyperv: bin/coconut-test-hyperv.igvm

doc:
cargo doc -p svsm --open --all-features --document-private-items

Expand Down
6 changes: 5 additions & 1 deletion bootlib/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@ pub struct IgvmParamBlock {
/// Indicates whether the guest can support alternate injection.
pub use_alternate_injection: u8,

/// Indicates whether the guest can assume firmware services specific to
/// QEMU.
pub is_qemu: u8,

#[doc(hidden)]
pub _reserved: [u8; 5],
pub _reserved: [u8; 4],

/// Metadata containing information about the firmware image embedded in the
/// IGVM file.
Expand Down
6 changes: 6 additions & 0 deletions igvmbuilder/src/igvm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ impl IgvmBuilder {
(fw_info, vtom)
};

let is_qemu: u8 = match self.options.hypervisor {
Hypervisor::Qemu => 1,
_ => 0,
};

// Most of the parameter block can be initialised with constants.
Ok(IgvmParamBlock {
param_area_size,
Expand All @@ -231,6 +236,7 @@ impl IgvmBuilder {
kernel_base: self.gpa_map.kernel.get_start(),
vtom,
use_alternate_injection: u8::from(self.options.alt_injection),
is_qemu,
..Default::default()
})
}
Expand Down
7 changes: 7 additions & 0 deletions kernel/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,11 @@ impl SvsmConfig<'_> {
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.use_alternate_injection(),
}
}

pub fn is_qemu(&self) -> bool {
match self {
SvsmConfig::FirmwareConfig(_) => true,
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.is_qemu(),
}
}
}
144 changes: 86 additions & 58 deletions kernel/src/cpu/vc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ use crate::mm::GuestPtr;
use crate::sev::ghcb::GHCB;
use core::fmt;

#[cfg(test)]
use crate::testutils::is_qemu_test_env;

pub const SVM_EXIT_EXCP_BASE: usize = 0x40;
pub const SVM_EXIT_LAST_EXCP: usize = 0x5f;
pub const SVM_EXIT_RDTSC: usize = 0x6e;
Expand Down Expand Up @@ -442,74 +445,88 @@ mod tests {
#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_8() {
const TEST_VAL: u8 = 0x12;
verify_ghcb_gets_altered(|| outb(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inb(TESTDEV_ECHO_LAST_PORT))
);
if is_qemu_test_env() {
const TEST_VAL: u8 = 0x12;
verify_ghcb_gets_altered(|| outb(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inb(TESTDEV_ECHO_LAST_PORT))
);
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_16() {
const TEST_VAL: u16 = 0x4321;
verify_ghcb_gets_altered(|| outw(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inw(TESTDEV_ECHO_LAST_PORT))
);
if is_qemu_test_env() {
const TEST_VAL: u16 = 0x4321;
verify_ghcb_gets_altered(|| outw(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inw(TESTDEV_ECHO_LAST_PORT))
);
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_32() {
const TEST_VAL: u32 = 0xabcd1234;
verify_ghcb_gets_altered(|| outl(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inl(TESTDEV_ECHO_LAST_PORT))
);
if is_qemu_test_env() {
const TEST_VAL: u32 = 0xabcd1234;
verify_ghcb_gets_altered(|| outl(TESTDEV_ECHO_LAST_PORT, TEST_VAL));
assert_eq!(
TEST_VAL,
verify_ghcb_gets_altered(|| inl(TESTDEV_ECHO_LAST_PORT))
);
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_8_hardcoded() {
const TEST_VAL: u8 = 0x12;
verify_ghcb_gets_altered(|| outb_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inb_from_testdev_echo));
if is_qemu_test_env() {
const TEST_VAL: u8 = 0x12;
verify_ghcb_gets_altered(|| outb_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inb_from_testdev_echo));
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_16_hardcoded() {
const TEST_VAL: u16 = 0x4321;
verify_ghcb_gets_altered(|| outw_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inw_from_testdev_echo));
if is_qemu_test_env() {
const TEST_VAL: u16 = 0x4321;
verify_ghcb_gets_altered(|| outw_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inw_from_testdev_echo));
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_32_hardcoded() {
const TEST_VAL: u32 = 0xabcd1234;
verify_ghcb_gets_altered(|| outl_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inl_from_testdev_echo));
if is_qemu_test_env() {
const TEST_VAL: u32 = 0xabcd1234;
verify_ghcb_gets_altered(|| outl_to_testdev_echo(TEST_VAL));
assert_eq!(TEST_VAL, verify_ghcb_gets_altered(inl_from_testdev_echo));
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_port_io_string_16_get_last() {
const TEST_DATA: &[u16] = &[0x1234, 0x5678, 0x9abc, 0xdef0];
verify_ghcb_gets_altered(|| rep_outsw(TESTDEV_ECHO_LAST_PORT, TEST_DATA));
assert_eq!(
TEST_DATA.last().unwrap(),
&verify_ghcb_gets_altered(|| inw(TESTDEV_ECHO_LAST_PORT))
);

let mut test_data: [u16; 4] = [0; 4];
verify_ghcb_gets_altered(|| rep_insw(TESTDEV_ECHO_LAST_PORT, &mut test_data));
for d in test_data.iter() {
assert_eq!(d, TEST_DATA.last().unwrap());
if is_qemu_test_env() {
const TEST_DATA: &[u16] = &[0x1234, 0x5678, 0x9abc, 0xdef0];
verify_ghcb_gets_altered(|| rep_outsw(TESTDEV_ECHO_LAST_PORT, TEST_DATA));
assert_eq!(
TEST_DATA.last().unwrap(),
&verify_ghcb_gets_altered(|| inw(TESTDEV_ECHO_LAST_PORT))
);

let mut test_data: [u16; 4] = [0; 4];
verify_ghcb_gets_altered(|| rep_insw(TESTDEV_ECHO_LAST_PORT, &mut test_data));
for d in test_data.iter() {
assert_eq!(d, TEST_DATA.last().unwrap());
}
}
}

Expand All @@ -531,27 +548,33 @@ mod tests {
#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_rdmsr_apic() {
let apic_base = verify_ghcb_gets_altered(|| read_msr(MSR_APIC_BASE));
assert_eq!(apic_base & APIC_BASE_PHYS_ADDR_MASK, APIC_DEFAULT_PHYS_BASE);
if is_qemu_test_env() {
let apic_base = verify_ghcb_gets_altered(|| read_msr(MSR_APIC_BASE));
assert_eq!(apic_base & APIC_BASE_PHYS_ADDR_MASK, APIC_DEFAULT_PHYS_BASE);
}
}

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_rdmsr_debug_ctl() {
const MSR_DEBUG_CTL: u32 = 0x1d9;
let apic_base = verify_ghcb_gets_altered(|| read_msr(MSR_DEBUG_CTL));
assert_eq!(apic_base, 0);
if is_qemu_test_env() {
const MSR_DEBUG_CTL: u32 = 0x1d9;
let apic_base = verify_ghcb_gets_altered(|| read_msr(MSR_DEBUG_CTL));
assert_eq!(apic_base, 0);
}
}

const MSR_TSC_AUX: u32 = 0xc0000103;

#[test]
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
fn test_wrmsr_tsc_aux() {
let test_val = 0x1234;
verify_ghcb_gets_altered(|| write_msr(MSR_TSC_AUX, test_val));
let readback = verify_ghcb_gets_altered(|| read_msr(MSR_TSC_AUX));
assert_eq!(test_val, readback);
if is_qemu_test_env() {
let test_val = 0x1234;
verify_ghcb_gets_altered(|| write_msr(MSR_TSC_AUX, test_val));
let readback = verify_ghcb_gets_altered(|| read_msr(MSR_TSC_AUX));
assert_eq!(test_val, readback);
}
}

#[test]
Expand All @@ -566,26 +589,31 @@ mod tests {
// #[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
#[ignore = "Currently unhandled by #VC handler"]
fn test_vmmcall_vapic_poll_irq() {
const VMMCALL_HC_VAPIC_POLL_IRQ: u32 = 1;
if is_qemu_test_env() {
const VMMCALL_HC_VAPIC_POLL_IRQ: u32 = 1;

let res =
verify_ghcb_gets_altered(|| unsafe { raw_vmmcall(VMMCALL_HC_VAPIC_POLL_IRQ, 0, 0, 0) });
assert_eq!(res, 0);
let res = verify_ghcb_gets_altered(|| unsafe {
raw_vmmcall(VMMCALL_HC_VAPIC_POLL_IRQ, 0, 0, 0)
});
assert_eq!(res, 0);
}
}

#[test]
// #[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
#[ignore = "Currently unhandled by #VC handler"]
fn test_read_write_dr7() {
const DR7_DEFAULT: u64 = 0x400;
const DR7_TEST: u64 = 0x401;
if is_qemu_test_env() {
const DR7_DEFAULT: u64 = 0x400;
const DR7_TEST: u64 = 0x401;

let old_dr7 = verify_ghcb_gets_altered(get_dr7);
assert_eq!(old_dr7, DR7_DEFAULT);
let old_dr7 = verify_ghcb_gets_altered(get_dr7);
assert_eq!(old_dr7, DR7_DEFAULT);

verify_ghcb_gets_altered(|| set_dr7(DR7_TEST));
let new_dr7 = verify_ghcb_gets_altered(get_dr7);
assert_eq!(new_dr7, DR7_TEST);
verify_ghcb_gets_altered(|| set_dr7(DR7_TEST));
let new_dr7 = verify_ghcb_gets_altered(get_dr7);
assert_eq!(new_dr7, DR7_TEST);
}
}

#[test]
Expand Down
31 changes: 17 additions & 14 deletions kernel/src/greq/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,29 @@ mod tests {

use crate::serial::Terminal;
use crate::testing::{assert_eq_warn, svsm_test_io, IORequest};
use crate::testutils::is_qemu_test_env;

use alloc::vec;

let sp = svsm_test_io().unwrap();
if is_qemu_test_env() {
let sp = svsm_test_io().unwrap();

sp.put_byte(IORequest::GetLaunchMeasurement as u8);
sp.put_byte(IORequest::GetLaunchMeasurement as u8);

let mut expected_measurement = [0u8; 48];
for byte in &mut expected_measurement {
*byte = sp.get_byte();
}
let mut expected_measurement = [0u8; 48];
for byte in &mut expected_measurement {
*byte = sp.get_byte();
}

let mut buf = vec![0; size_of::<SnpReportResponse>()];
let size = get_regular_report(&mut buf).unwrap();
assert_eq!(size, buf.len());
let mut buf = vec![0; size_of::<SnpReportResponse>()];
let size = get_regular_report(&mut buf).unwrap();
assert_eq!(size, buf.len());

let (response, _rest) = SnpReportResponse::ref_from_prefix(&buf).unwrap();
response.validate().unwrap();
// FIXME: we still have some cases where the precalculated value does
// not match, so for now we just issue a warning until we fix the problem.
assert_eq_warn!(expected_measurement, *response.measurement());
let (response, _rest) = SnpReportResponse::ref_from_prefix(&buf).unwrap();
response.validate().unwrap();
// FIXME: we still have some cases where the precalculated value does
// not match, so for now we just issue a warning until we fix the problem.
assert_eq_warn!(expected_measurement, *response.measurement());
}
}
}
4 changes: 4 additions & 0 deletions kernel/src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,8 @@ impl IgvmParams<'_> {
pub fn use_alternate_injection(&self) -> bool {
self.igvm_param_block.use_alternate_injection != 0
}

pub fn is_qemu(&self) -> bool {
self.igvm_param_block.is_qemu != 0
}
}
3 changes: 3 additions & 0 deletions kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ extern crate self as svsm;
// Include a module containing the test runner.
#[cfg(all(test, test_in_svsm))]
pub mod testing;
// Utilities for test configurations.
#[cfg(test)]
pub mod testutils;
7 changes: 6 additions & 1 deletion kernel/src/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,12 @@ pub extern "C" fn svsm_main() {
.expect("Failed to launch request processing task");

#[cfg(test)]
crate::test_main();
{
if config.is_qemu() {
crate::testutils::set_qemu_test_env();
}
crate::test_main();
}

match exec_user("/init", opendir("/").expect("Failed to find FS root")) {
Ok(_) => (),
Expand Down
16 changes: 12 additions & 4 deletions kernel/src/testing.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core::arch::asm;
use log::info;
use test::ShouldPanic;

Expand All @@ -7,6 +8,7 @@ use crate::{
platform::SVSM_PLATFORM,
serial::SerialPort,
sev::ghcb::GHCBIOSize,
testutils::is_qemu_test_env,
};

#[macro_export]
Expand Down Expand Up @@ -86,9 +88,15 @@ pub fn svsm_test_runner(test_cases: &[&test::TestDescAndFn]) {
}

fn exit() -> ! {
const QEMU_EXIT_PORT: u16 = 0xf4;
current_ghcb()
.ioio_out(QEMU_EXIT_PORT, GHCBIOSize::Size32, 0)
.unwrap();
if is_qemu_test_env() {
const QEMU_EXIT_PORT: u16 = 0xf4;
current_ghcb()
.ioio_out(QEMU_EXIT_PORT, GHCBIOSize::Size32, 0)
.unwrap();
}
// SAFETY: HLT instruction does not affect memory.
unsafe {
asm!("hlt");
}
unreachable!();
}
11 changes: 11 additions & 0 deletions kernel/src/testutils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use core::sync::atomic::{AtomicBool, Ordering};

static QEMU_TEST_ENV: AtomicBool = AtomicBool::new(false);

pub fn is_qemu_test_env() -> bool {
QEMU_TEST_ENV.load(Ordering::Acquire)
}

pub fn set_qemu_test_env() {
QEMU_TEST_ENV.store(true, Ordering::Release)
}

0 comments on commit 1f460da

Please sign in to comment.