Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openhcl/underhill_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ async fn launch_workers(
default_boot_always_attempt: opt.default_boot_always_attempt,
guest_state_lifetime: opt.guest_state_lifetime,
guest_state_encryption_policy: opt.guest_state_encryption_policy,
efi_diagnostics_log_level: opt.efi_diagnostics_log_level,
strict_encryption_policy: opt.strict_encryption_policy,
attempt_ak_cert_callback: opt.attempt_ak_cert_callback,
enable_vpci_relay: opt.enable_vpci_relay,
Expand Down
34 changes: 34 additions & 0 deletions openhcl/underhill_core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ impl FromStr for GuestStateEncryptionPolicyCli {
}
}

#[derive(Clone, Copy, Debug, MeshPayload)]
pub enum EfiDiagnosticsLogLevelCli {
Default,
Info,
Full,
}

impl FromStr for EfiDiagnosticsLogLevelCli {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<EfiDiagnosticsLogLevelCli, anyhow::Error> {
match s {
"DEFAULT" | "0" => Ok(EfiDiagnosticsLogLevelCli::Default),
"INFO" | "1" => Ok(EfiDiagnosticsLogLevelCli::Info),
"FULL" | "2" => Ok(EfiDiagnosticsLogLevelCli::Full),
_ => Err(anyhow::anyhow!("Invalid EFI diagnostics log level: {}", s)),
}
}
}

#[derive(Clone, Debug, MeshPayload, Inspect, InspectMut)]
pub enum KeepAliveConfig {
EnabledHostAndPrivatePoolPresent,
Expand Down Expand Up @@ -264,6 +284,11 @@ pub struct Options {
/// Specify which guest state encryption policy to use.
pub guest_state_encryption_policy: Option<GuestStateEncryptionPolicyCli>,

/// (HCL_EFI_DIAGNOSTICS_LOG_LEVEL=\<EfiDiagnosticsLogLevelCli\>)
/// Specify the EFI diagnostics log level filter (DEFAULT, INFO, or FULL).
/// Overrides the value in DPS when set.
pub efi_diagnostics_log_level: Option<EfiDiagnosticsLogLevelCli>,

/// (HCL_STRICT_ENCRYPTION_POLICY=1) Strict guest state encryption policy.
pub strict_encryption_policy: Option<bool>,

Expand Down Expand Up @@ -458,6 +483,14 @@ impl Options {
})
.ok()
});
let efi_diagnostics_log_level = read_env("HCL_EFI_DIAGNOSTICS_LOG_LEVEL").and_then(|x| {
x.to_string_lossy()
.parse::<EfiDiagnosticsLogLevelCli>()
.map_err(|e| {
tracing::warn!("failed to parse HCL_EFI_DIAGNOSTICS_LOG_LEVEL: {:#}", e)
})
.ok()
});
let strict_encryption_policy = parse_env_bool_opt("HCL_STRICT_ENCRYPTION_POLICY");
let attempt_ak_cert_callback = parse_env_bool_opt("HCL_ATTEMPT_AK_CERT_CALLBACK");
let enable_vpci_relay = parse_env_bool_opt("OPENHCL_ENABLE_VPCI_RELAY");
Expand Down Expand Up @@ -526,6 +559,7 @@ impl Options {
default_boot_always_attempt,
guest_state_lifetime,
guest_state_encryption_policy,
efi_diagnostics_log_level,
strict_encryption_policy,
attempt_ak_cert_callback,
enable_vpci_relay,
Expand Down
13 changes: 13 additions & 0 deletions openhcl/underhill_core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use crate::nvme_manager::device::VfioNvmeDriverSpawner;
use crate::nvme_manager::manager::NvmeDiskConfig;
use crate::nvme_manager::manager::NvmeDiskResolver;
use crate::nvme_manager::manager::NvmeManager;
use crate::options::EfiDiagnosticsLogLevelCli;
use crate::options::GuestStateEncryptionPolicyCli;
use crate::options::GuestStateLifetimeCli;
use crate::options::KeepAliveConfig;
Expand Down Expand Up @@ -309,6 +310,8 @@ pub struct UnderhillEnvCfg {
pub guest_state_lifetime: Option<GuestStateLifetimeCli>,
/// Guest state encryption policy
pub guest_state_encryption_policy: Option<GuestStateEncryptionPolicyCli>,
/// EFI diagnostics log level filter (overrides DPS value when set)
pub efi_diagnostics_log_level: Option<EfiDiagnosticsLogLevelCli>,
/// Strict guest state encryption policy
pub strict_encryption_policy: Option<bool>,
/// Attempt to renew the AK cert
Expand Down Expand Up @@ -1549,6 +1552,16 @@ async fn new_underhill_vm(
};
}

if let Some(level) = env_cfg.efi_diagnostics_log_level {
tracing::info!("using HCL_EFI_DIAGNOSTICS_LOG_LEVEL={level:?} from cmdline");
use get_protocol::dps_json::EfiDiagnosticsLogLevelType;
dps.general.efi_diagnostics_log_level = match level {
EfiDiagnosticsLogLevelCli::Default => EfiDiagnosticsLogLevelType::DEFAULT,
EfiDiagnosticsLogLevelCli::Info => EfiDiagnosticsLogLevelType::INFO,
EfiDiagnosticsLogLevelCli::Full => EfiDiagnosticsLogLevelType::FULL,
};
}

if let Some(value) = env_cfg.strict_encryption_policy {
tracing::info!("using HCL_STRICT_ENCRYPTION_POLICY={value} from cmdline");
dps.general
Expand Down
30 changes: 30 additions & 0 deletions petri/src/vm/hyperv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ impl PetriVmmBackend for HyperVPetriBackend {
enable_vpci_boot,
secure_boot_enabled,
default_boot_always_attempt,
efi_diagnostics_log_level,
..
}) = config.firmware.uefi_config()
{
Expand Down Expand Up @@ -216,6 +217,35 @@ impl PetriVmmBackend for HyperVPetriBackend {
);
}

// Plumb the EFI diagnostics log level via the OpenHCL command
// line. The corresponding `EfiDiagnosticsLogLevel` WMI property
// is not available on all hosts (e.g. rs_prerelease), so we
// rely on the underhill env var fallback instead. This means
// we currently only support setting the level on OpenHCL-backed
// VMs; for plain Hyper-V UEFI VMs we fail loudly rather than
// silently dropping the setting.
//
// TODO: switch to the WMI property (which would also cover the
// non-OpenHCL path) once host changes are saturated.
let efi_diag_cli = match efi_diagnostics_log_level {
crate::EfiDiagnosticsLogLevel::Default => None,
crate::EfiDiagnosticsLogLevel::Info => Some("INFO"),
crate::EfiDiagnosticsLogLevel::Full => Some("FULL"),
};
if let Some(cli) = efi_diag_cli {
if !properties.is_openhcl {
anyhow::bail!(
"with_efi_diagnostics_log_level({:?}) is only supported for \
OpenHCL-backed Hyper-V UEFI VMs in this code path",
efi_diagnostics_log_level
);
}
append_cmdline(
&mut openhcl_command_line,
format!("HCL_EFI_DIAGNOSTICS_LOG_LEVEL={cli}"),
);
}

if *enable_vpci_boot {
todo!("hyperv nvme boot");
}
Expand Down
32 changes: 32 additions & 0 deletions petri/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,20 @@ impl<T: PetriVmmBackend> PetriVmBuilder<T> {
self
}

/// Sets the UEFI diagnostics log level filter.
///
/// By default only ERROR and WARN level entries are forwarded to the
/// host tracing infrastructure. Use this to also surface INFO (or all)
/// entries when a test needs to observe them.
pub fn with_efi_diagnostics_log_level(mut self, level: EfiDiagnosticsLogLevel) -> Self {
self.config
.firmware
.uefi_config_mut()
.expect("EFI diagnostics log level is only supported for UEFI firmware.")
.efi_diagnostics_log_level = level;
Comment thread
maheeraeron marked this conversation as resolved.
self
}

/// Sets whether UEFI should always attempt a default boot.
pub fn with_default_boot_always_attempt(mut self, enable: bool) -> Self {
self.config
Expand Down Expand Up @@ -2158,6 +2172,8 @@ pub struct UefiConfig {
pub default_boot_always_attempt: bool,
/// Enable vPCI boot (for NVMe)
pub enable_vpci_boot: bool,
/// EFI diagnostics log level filter
pub efi_diagnostics_log_level: EfiDiagnosticsLogLevel,
}

impl Default for UefiConfig {
Expand All @@ -2168,10 +2184,26 @@ impl Default for UefiConfig {
disable_frontpage: true,
default_boot_always_attempt: false,
enable_vpci_boot: false,
efi_diagnostics_log_level: EfiDiagnosticsLogLevel::Default,
}
}
}

/// EFI diagnostics log level filter.
///
/// Controls which UEFI diagnostics log entries are forwarded to the host
/// tracing infrastructure (and thus visible via kmsg / test output).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum EfiDiagnosticsLogLevel {
/// Default log level (ERROR and WARN only).
#[default]
Default,
/// Include INFO logs (ERROR, WARN, and INFO).
Info,
/// All log levels.
Full,
}

/// Control the logging configuration of OpenVMM/OpenHCL.
#[derive(Debug, Clone)]
pub enum OpenvmmLogConfig {
Expand Down
31 changes: 29 additions & 2 deletions petri/src/vm/openvmm/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use super::PetriVmConfigOpenVmm;
use super::PetriVmResourcesOpenVmm;
use crate::Drive;
use crate::EfiDiagnosticsLogLevel;
use crate::Firmware;
use crate::IsolationType;
use crate::MemoryConfig;
Expand Down Expand Up @@ -552,7 +553,21 @@ impl PetriVmConfigOpenVmm {
debugger_rpc: None,
generation_id_recv: None,
rtc_delta_milliseconds: 0,
efi_diagnostics_log_level: Default::default(), // TODO: Add config for tests
efi_diagnostics_log_level: match firmware
.uefi_config()
.map(|c| c.efi_diagnostics_log_level)
.unwrap_or_default()
{
EfiDiagnosticsLogLevel::Default => {
openvmm_defs::config::EfiDiagnosticsLogLevelType::Default
}
EfiDiagnosticsLogLevel::Info => {
openvmm_defs::config::EfiDiagnosticsLogLevelType::Info
}
EfiDiagnosticsLogLevel::Full => {
openvmm_defs::config::EfiDiagnosticsLogLevelType::Full
}
},
};

// Make the pipette connection listener.
Expand Down Expand Up @@ -774,6 +789,7 @@ impl PetriVmConfigSetupCore<'_> {
disable_frontpage,
default_boot_always_attempt,
enable_vpci_boot,
efi_diagnostics_log_level: _, // applied to top-level Config below
},
},
) => {
Expand Down Expand Up @@ -933,6 +949,7 @@ impl PetriVmConfigSetupCore<'_> {
disable_frontpage,
default_boot_always_attempt,
enable_vpci_boot,
efi_diagnostics_log_level,
},
OpenHclConfig { vmbus_redirect, .. },
) = match self.firmware {
Expand Down Expand Up @@ -985,7 +1002,17 @@ impl PetriVmConfigSetupCore<'_> {
no_persistent_secrets: self.tpm_config.as_ref().is_some_and(|c| c.no_persistent_secrets),
igvm_attest_test_config: None,
test_gsp_by_id,
efi_diagnostics_log_level: Default::default(), // TODO: make configurable
efi_diagnostics_log_level: match efi_diagnostics_log_level {
EfiDiagnosticsLogLevel::Default => {
get_resources::ged::EfiDiagnosticsLogLevelType::Default
}
EfiDiagnosticsLogLevel::Info => {
get_resources::ged::EfiDiagnosticsLogLevelType::Info
}
EfiDiagnosticsLogLevel::Full => {
get_resources::ged::EfiDiagnosticsLogLevelType::Full
}
},
hv_sint_enabled: false,
};

Expand Down
39 changes: 39 additions & 0 deletions vmm_tests/vmm_tests/tests/tests/multiarch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use anyhow::Context;
use futures::StreamExt;
use petri::EfiDiagnosticsLogLevel;
use petri::MemoryConfig;
use petri::PetriHaltReason;
use petri::PetriVmBuilder;
Expand Down Expand Up @@ -480,6 +481,44 @@ async fn efi_diagnostics_no_boot<T: PetriVmmBackend>(
anyhow::bail!("Did not find expected message in kmsg");
}

/// Test EFI diagnostics with INFO-level logging enabled
/// TODO:
/// - change hyperv tests to use WMI instead of env_cfg once
/// CI runners support it
#[vmm_test_with(noagent(
openvmm_openhcl_uefi_x64(none),
hyperv_openhcl_uefi_x64(none),
hyperv_openhcl_uefi_aarch64(none)
))]
async fn efi_diagnostics_info_level<T: PetriVmmBackend>(
config: PetriVmBuilder<T>,
) -> anyhow::Result<()> {
let vm = config
.with_uefi_frontpage(true)
.with_efi_diagnostics_log_level(EfiDiagnosticsLogLevel::Info)
.run_without_agent()
.await?;

// Marker emitted by `firmware_uefi::service::diagnostics` for every
// UEFI log entry tagged with `DEBUG_INFO`.
//
// Presence of this marker in the kmsg output validates that.
const INFO_MARKER: &str = "debug_level=INFO";

let mut kmsg = vm.kmsg().await?;

while let Some(data) = kmsg.next().await {
let data = data.context("reading kmsg")?;
let msg = kmsg::KmsgParsedEntry::new(&data).unwrap();
let raw = msg.message.as_raw();
if raw.contains(INFO_MARKER) {
return Ok(());
}
}

anyhow::bail!("Did not find any INFO-level UEFI diagnostics entry ({INFO_MARKER:?}) in kmsg");
}

/// Boot our guest-test UEFI image, which will run some tests,
/// and then purposefully triple fault itself via an expiring
/// watchdog timer.
Expand Down
Loading