Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
22 changes: 22 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,27 @@ 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.
//
// TODO: switch to the WMI property once host changes are
// saturated.
if properties.is_openhcl {
let cli = match efi_diagnostics_log_level {
crate::EfiDiagnosticsLogLevel::Default => None,
crate::EfiDiagnosticsLogLevel::Info => Some("INFO"),
crate::EfiDiagnosticsLogLevel::Full => Some("FULL"),
};
if let Some(cli) = cli {
append_cmdline(
&mut openhcl_command_line,
format!("HCL_EFI_DIAGNOSTICS_LOG_LEVEL={cli}"),
);
}
Comment thread
maheeraeron marked this conversation as resolved.
Outdated
}

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