From 316d60936f5ffb9c06052f831defdffa787e57af Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 13:27:56 -0400 Subject: [PATCH 1/9] good --- Cargo.lock | 34 +++++++-- Cargo.toml | 1 + openhcl/openvmm_hcl_resources/Cargo.toml | 1 + openhcl/openvmm_hcl_resources/src/lib.rs | 1 + openhcl/underhill_core/Cargo.toml | 2 +- .../underhill_core/src/emuplat/firmware.rs | 6 +- openhcl/underhill_core/src/emuplat/mod.rs | 1 + openvmm/openvmm_core/Cargo.toml | 3 +- openvmm/openvmm_core/src/emuplat/mod.rs | 1 + openvmm/openvmm_entry/Cargo.toml | 1 + openvmm/openvmm_resources/Cargo.toml | 1 + openvmm/openvmm_resources/src/lib.rs | 1 + vm/devices/firmware/firmware_uefi/Cargo.toml | 4 + .../firmware/firmware_uefi/fuzz/Cargo.toml | 2 + .../firmware_uefi/fuzz/fuzz_diagnostics.rs | 2 +- .../firmware/firmware_uefi/fuzz/fuzz_nvram.rs | 2 +- vm/devices/firmware/firmware_uefi/src/lib.rs | 30 ++------ .../firmware_uefi/src/platform/logger.rs | 23 ------ .../firmware_uefi/src/platform/mod.rs | 7 -- .../firmware_uefi/src/platform/nvram.rs | 18 ----- .../src/service/diagnostics/mod.rs | 49 +----------- .../firmware_uefi/src/service/event_log.rs | 6 +- .../firmware_uefi/src/service/nvram/mod.rs | 4 +- .../firmware_uefi_resources/Cargo.toml | 22 ++++++ vmm_core/vm_manifest_builder/Cargo.toml | 1 + vmm_core/vmotherboard/Cargo.toml | 4 - vmm_core/vmotherboard/src/base_chipset.rs | 76 ------------------- 27 files changed, 83 insertions(+), 220 deletions(-) delete mode 100644 vm/devices/firmware/firmware_uefi/src/platform/logger.rs delete mode 100644 vm/devices/firmware/firmware_uefi/src/platform/mod.rs delete mode 100644 vm/devices/firmware/firmware_uefi/src/platform/nvram.rs create mode 100644 vm/devices/firmware/firmware_uefi_resources/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 76892895d3..0118b52e92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2030,9 +2030,12 @@ dependencies = [ "async-trait", "bitfield-struct 0.11.0", "chipset_device", + "chipset_device_resources", + "chipset_resources", "crypto", "der", "firmware_uefi_custom_vars", + "firmware_uefi_resources", "generation_id", "getrandom 0.4.2", "guestmem", @@ -2051,6 +2054,7 @@ dependencies = [ "uefi_nvram_specvars", "uefi_nvram_storage", "uefi_specs", + "vm_resource", "vmcore", "watchdog_core", "wchar", @@ -2067,6 +2071,21 @@ dependencies = [ "uefi_specs", ] +[[package]] +name = "firmware_uefi_resources" +version = "0.0.0" +dependencies = [ + "chipset_resources", + "firmware_uefi_custom_vars", + "inspect", + "mesh", + "mesh_protobuf", + "uefi_nvram_storage", + "uefi_specs", + "vm_resource", + "watchdog_core", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2476,11 +2495,13 @@ dependencies = [ "arbitrary", "crypto", "firmware_uefi", + "firmware_uefi_resources", "guestmem", "guid", "libfuzzer-sys", "ucs2 0.0.0", "uefi_nvram_specvars", + "uefi_specs", "xtask_fuzz", "zerocopy", ] @@ -5352,8 +5373,8 @@ dependencies = [ "disk_backend", "fdt", "firmware_pcat", - "firmware_uefi", "firmware_uefi_custom_vars", + "firmware_uefi_resources", "floppy", "floppy_resources", "framebuffer", @@ -5373,7 +5394,6 @@ dependencies = [ "input_core", "inspect", "loader", - "local_clock", "membacking", "memory_range", "mesh", @@ -5469,6 +5489,7 @@ dependencies = [ "disk_backend_resources", "disk_crypt_resources", "firmware_uefi_custom_vars", + "firmware_uefi_resources", "floppy_resources", "framebuffer", "fs-err", @@ -5562,6 +5583,7 @@ dependencies = [ "chipset_resources", "debug_worker", "disk_striped", + "firmware_uefi", "guest_watchdog", "hyperv_ic", "mesh_worker", @@ -5646,6 +5668,7 @@ dependencies = [ "disk_vhdmp", "disklayer_ram", "disklayer_sqlite", + "firmware_uefi", "gdma", "guest_crash_device", "guest_emulation_device", @@ -8467,8 +8490,8 @@ dependencies = [ "disk_get_vmgs", "disk_nvme", "firmware_pcat", - "firmware_uefi", "firmware_uefi_custom_vars", + "firmware_uefi_resources", "framebuffer", "fs-err", "futures", @@ -9566,6 +9589,7 @@ name = "vm_manifest_builder" version = "0.0.0" dependencies = [ "chipset_resources", + "firmware_uefi_resources", "input_core", "mesh", "missing_dev_resources", @@ -10234,7 +10258,6 @@ dependencies = [ "closeable_mutex", "cvm_tracing", "firmware_pcat", - "firmware_uefi", "floppy", "floppy_pcat_stub", "framebuffer", @@ -10244,7 +10267,6 @@ dependencies = [ "ide", "inspect", "inspect_counters", - "local_clock", "mesh", "missing_dev", "pal_async", @@ -10257,12 +10279,10 @@ dependencies = [ "thiserror 2.0.16", "tracelimit", "tracing", - "uefi_nvram_storage", "vga", "vga_proxy", "vm_resource", "vmcore", - "watchdog_core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8d9659ec42..22e6f8e11f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,6 +232,7 @@ chipset_resources = { path = "vm/devices/chipset_resources" } firmware_pcat = { path = "vm/devices/firmware/firmware_pcat" } firmware_uefi = { path = "vm/devices/firmware/firmware_uefi" } firmware_uefi_custom_vars = { path = "vm/devices/firmware/firmware_uefi_custom_vars" } +firmware_uefi_resources = { path = "vm/devices/firmware/firmware_uefi_resources" } uefi_nvram_storage = { path = "vm/devices/firmware/uefi_nvram_storage" } uefi_specs = { path = "vm/devices/firmware/uefi_specs" } uefi_nvram_specvars = { path = "vm/devices/firmware/uefi_nvram_specvars" } diff --git a/openhcl/openvmm_hcl_resources/Cargo.toml b/openhcl/openvmm_hcl_resources/Cargo.toml index 266e63a0d4..31ef6008f3 100644 --- a/openhcl/openvmm_hcl_resources/Cargo.toml +++ b/openhcl/openvmm_hcl_resources/Cargo.toml @@ -31,6 +31,7 @@ uidevices = { workspace = true, optional = true } chipset.workspace = true chipset_legacy.workspace = true chipset_resources.workspace = true +firmware_uefi.workspace = true hyperv_ic.workspace = true missing_dev.workspace = true serial_16550.workspace = true diff --git a/openhcl/openvmm_hcl_resources/src/lib.rs b/openhcl/openvmm_hcl_resources/src/lib.rs index 1dc6563db3..598232e9d3 100644 --- a/openhcl/openvmm_hcl_resources/src/lib.rs +++ b/openhcl/openvmm_hcl_resources/src/lib.rs @@ -29,6 +29,7 @@ vm_resource::register_static_resolvers! { #[cfg(guest_arch = "x86_64")] chipset::pm::resolver::HyperVPowerManagementResolver, chipset_resources::cmos_rtc_time_source::SystemTimeClockResolver, + firmware_uefi::resolver::UefiDeviceResolver, missing_dev::resolver::MissingDevResolver, #[cfg(feature = "tpm")] tpm_device::resolver::TpmDeviceResolver, diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 3e998786f6..4d2cc96674 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -54,8 +54,8 @@ disk_backend_resources.workspace = true disk_blockdevice.workspace = true disk_get_vmgs.workspace = true disk_nvme.workspace = true -firmware_uefi.workspace = true firmware_uefi_custom_vars.workspace = true +firmware_uefi_resources.workspace = true gdma_defs.workspace = true hyperv_ic_guest.workspace = true hyperv_ic_resources.workspace = true diff --git a/openhcl/underhill_core/src/emuplat/firmware.rs b/openhcl/underhill_core/src/emuplat/firmware.rs index 4e94ac894d..7769c1d70a 100644 --- a/openhcl/underhill_core/src/emuplat/firmware.rs +++ b/openhcl/underhill_core/src/emuplat/firmware.rs @@ -2,8 +2,8 @@ // Licensed under the MIT License. use cvm_tracing::CVM_ALLOWED; -use firmware_uefi::platform::logger::UefiEvent; -use firmware_uefi::platform::logger::UefiLogger; +use firmware_uefi_resources::platform::UefiEvent; +use firmware_uefi_resources::platform::UefiLogger; use guest_emulation_transport::GuestEmulationTransportClient; use guest_emulation_transport::api::EventLogId; use std::sync::Weak; @@ -54,7 +54,7 @@ pub struct UnderhillVsmConfig { pub partition: Weak, } -impl firmware_uefi::platform::nvram::VsmConfig for UnderhillVsmConfig { +impl firmware_uefi_resources::platform::VsmConfig for UnderhillVsmConfig { fn revoke_guest_vsm(&self) { if let Some(partition) = self.partition.upgrade() { if let Err(err) = partition.revoke_guest_vsm() { diff --git a/openhcl/underhill_core/src/emuplat/mod.rs b/openhcl/underhill_core/src/emuplat/mod.rs index c1d92505de..ae96a9180d 100644 --- a/openhcl/underhill_core/src/emuplat/mod.rs +++ b/openhcl/underhill_core/src/emuplat/mod.rs @@ -10,6 +10,7 @@ pub mod netvsp; pub mod non_volatile_store; pub mod pm_timer_assist; pub mod tpm; +pub mod uefi; pub mod vga_proxy; pub mod watchdog; diff --git a/openvmm/openvmm_core/Cargo.toml b/openvmm/openvmm_core/Cargo.toml index 75f478e55a..edc98d00e1 100644 --- a/openvmm/openvmm_core/Cargo.toml +++ b/openvmm/openvmm_core/Cargo.toml @@ -55,7 +55,7 @@ closeable_mutex.workspace = true disk_backend.workspace = true firmware_pcat.workspace = true firmware_uefi_custom_vars.workspace = true -firmware_uefi.workspace = true +firmware_uefi_resources.workspace = true uefi_nvram_storage = { workspace = true, features = ["save_restore"] } framebuffer.workspace = true get_resources.workspace = true @@ -83,7 +83,6 @@ debug_ptr.workspace = true fdt.workspace = true guid.workspace = true inspect.workspace = true -local_clock.workspace = true mesh_worker.workspace = true mesh.workspace = true pal_async.workspace = true diff --git a/openvmm/openvmm_core/src/emuplat/mod.rs b/openvmm/openvmm_core/src/emuplat/mod.rs index e146bca166..6a83ec0da6 100644 --- a/openvmm/openvmm_core/src/emuplat/mod.rs +++ b/openvmm/openvmm_core/src/emuplat/mod.rs @@ -3,3 +3,4 @@ pub mod firmware; pub mod i440bx_host_pci_bridge; +pub mod uefi; diff --git a/openvmm/openvmm_entry/Cargo.toml b/openvmm/openvmm_entry/Cargo.toml index 20409624ba..f608dfd283 100644 --- a/openvmm/openvmm_entry/Cargo.toml +++ b/openvmm/openvmm_entry/Cargo.toml @@ -29,6 +29,7 @@ openvmm_ttrpc_vmservice.workspace = true disk_backend_resources.workspace = true disk_crypt_resources.workspace = true firmware_uefi_custom_vars.workspace = true +firmware_uefi_resources.workspace = true hyperv_secure_boot_templates.workspace = true hyperv_uefi_custom_vars_json.workspace = true floppy_resources.workspace = true diff --git a/openvmm/openvmm_resources/Cargo.toml b/openvmm/openvmm_resources/Cargo.toml index 8a592bea32..bade8691df 100644 --- a/openvmm/openvmm_resources/Cargo.toml +++ b/openvmm/openvmm_resources/Cargo.toml @@ -49,6 +49,7 @@ disklayer_sqlite = { workspace = true, optional = true } chipset.workspace = true chipset_legacy.workspace = true chipset_resources.workspace = true +firmware_uefi.workspace = true missing_dev.workspace = true guest_watchdog.workspace = true tpm_device = { workspace = true, optional = true, features = ["tpm"] } diff --git a/openvmm/openvmm_resources/src/lib.rs b/openvmm/openvmm_resources/src/lib.rs index 0cc0c8cee8..4414b2a4d8 100644 --- a/openvmm/openvmm_resources/src/lib.rs +++ b/openvmm/openvmm_resources/src/lib.rs @@ -28,6 +28,7 @@ vm_resource::register_static_resolvers! { #[cfg(guest_arch = "x86_64")] chipset::pm::resolver::HyperVPowerManagementResolver, chipset_resources::cmos_rtc_time_source::SystemTimeClockResolver, + firmware_uefi::resolver::UefiDeviceResolver, missing_dev::resolver::MissingDevResolver, #[cfg(feature = "tpm")] tpm_device::resolver::TpmDeviceResolver, diff --git a/vm/devices/firmware/firmware_uefi/Cargo.toml b/vm/devices/firmware/firmware_uefi/Cargo.toml index b55a9aadb6..8e7502b3f8 100644 --- a/vm/devices/firmware/firmware_uefi/Cargo.toml +++ b/vm/devices/firmware/firmware_uefi/Cargo.toml @@ -12,6 +12,7 @@ fuzzing = [] [dependencies] firmware_uefi_custom_vars.workspace = true +firmware_uefi_resources.workspace = true uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] } uefi_specs.workspace = true uefi_nvram_specvars.workspace = true @@ -19,7 +20,10 @@ generation_id.workspace = true watchdog_core.workspace = true chipset_device.workspace = true +chipset_device_resources.workspace = true +chipset_resources.workspace = true guestmem.workspace = true +vm_resource.workspace = true vmcore.workspace = true async-trait.workspace = true diff --git a/vm/devices/firmware/firmware_uefi/fuzz/Cargo.toml b/vm/devices/firmware/firmware_uefi/fuzz/Cargo.toml index 42e18aac9f..f632ea9609 100644 --- a/vm/devices/firmware/firmware_uefi/fuzz/Cargo.toml +++ b/vm/devices/firmware/firmware_uefi/fuzz/Cargo.toml @@ -11,11 +11,13 @@ rust-version.workspace = true arbitrary = { workspace = true, features = ["derive"] } crypto = { workspace = true, features = ["vendored"] } firmware_uefi = { workspace = true, features = ["fuzzing"] } +firmware_uefi_resources.workspace = true guid.workspace = true guestmem.workspace = true libfuzzer-sys.workspace = true ucs2.workspace = true uefi_nvram_specvars.workspace = true +uefi_specs.workspace = true xtask_fuzz.workspace = true zerocopy.workspace = true diff --git a/vm/devices/firmware/firmware_uefi/fuzz/fuzz_diagnostics.rs b/vm/devices/firmware/firmware_uefi/fuzz/fuzz_diagnostics.rs index 6fa6bbd3fc..6b70111465 100644 --- a/vm/devices/firmware/firmware_uefi/fuzz/fuzz_diagnostics.rs +++ b/vm/devices/firmware/firmware_uefi/fuzz/fuzz_diagnostics.rs @@ -7,7 +7,7 @@ use arbitrary::Arbitrary; use firmware_uefi::service::diagnostics::DiagnosticsServices; -use firmware_uefi::service::diagnostics::LogLevel; +use firmware_uefi_resources::LogLevel; use guestmem::GuestMemory; use xtask_fuzz::fuzz_target; diff --git a/vm/devices/firmware/firmware_uefi/fuzz/fuzz_nvram.rs b/vm/devices/firmware/firmware_uefi/fuzz/fuzz_nvram.rs index d25f124923..e574e4781a 100644 --- a/vm/devices/firmware/firmware_uefi/fuzz/fuzz_nvram.rs +++ b/vm/devices/firmware/firmware_uefi/fuzz/fuzz_nvram.rs @@ -9,7 +9,6 @@ use arbitrary::Arbitrary; use crypto::pkcs7::Pkcs7SignedData; use crypto::rsa::RsaKeyPair; use crypto::x509::X509Certificate; -use firmware_uefi::platform::nvram::EFI_TIME; use firmware_uefi::service::nvram::spec_services::ParsedAuthVar; use firmware_uefi::service::nvram::spec_services::auth_var_crypto; use guid::Guid; @@ -17,6 +16,7 @@ use std::borrow::Cow; use ucs2::Ucs2LeVec; use uefi_nvram_specvars::signature_list::SignatureData; use uefi_nvram_specvars::signature_list::SignatureList; +use uefi_specs::uefi::time::EFI_TIME; use xtask_fuzz::fuzz_target; use zerocopy::FromBytes; diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index 78e5135487..c87569be69 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -50,28 +50,27 @@ #![expect(missing_docs)] #![forbid(unsafe_code)] -pub mod platform; +pub mod resolver; #[cfg(feature = "fuzzing")] pub mod service; #[cfg(not(feature = "fuzzing"))] mod service; -pub use crate::service::diagnostics::LogLevel; - use chipset_device::ChipsetDevice; use chipset_device::io::IoError; use chipset_device::io::IoResult; use chipset_device::mmio::MmioIntercept; use chipset_device::pio::PortIoIntercept; use chipset_device::poll_device::PollDevice; -use firmware_uefi_custom_vars::CustomVars; +use firmware_uefi_resources::LogLevel; +use firmware_uefi_resources::UefiCommandSet; +use firmware_uefi_resources::UefiConfig; +use firmware_uefi_resources::platform::UefiLogger; +use firmware_uefi_resources::platform::VsmConfig; use guestmem::GuestMemory; -use inspect::Inspect; use inspect::InspectMut; use local_clock::InspectableLocalClock; use pal_async::local::block_on; -use platform::logger::UefiLogger; -use platform::nvram::VsmConfig; use service::diagnostics::DEFAULT_LOGS_PER_PERIOD; use service::diagnostics::WATCHDOG_LOGS_PER_PERIOD; use std::convert::TryInto; @@ -94,12 +93,6 @@ pub enum UefiInitError { EventLog(#[from] service::event_log::EventLogError), } -#[derive(Inspect, PartialEq, Clone)] -pub enum UefiCommandSet { - X64, - Aarch64, -} - #[derive(InspectMut)] struct UefiDeviceServices { nvram: service::nvram::NvramServices, @@ -121,17 +114,6 @@ const MMIO_RANGE_END: u64 = 0xeffedfff; const REGISTER_ADDRESS: u16 = 0x0; const REGISTER_DATA: u16 = 0x4; -/// Various bits of static configuration data. -#[derive(Clone)] -pub struct UefiConfig { - pub custom_uefi_vars: CustomVars, - pub secure_boot: bool, - pub initial_generation_id: [u8; 16], - pub use_mmio: bool, - pub command_set: UefiCommandSet, - pub diagnostics_log_level: LogLevel, -} - /// Various runtime objects used by the UEFI device + underlying services. pub struct UefiRuntimeDeps<'a> { pub gm: GuestMemory, diff --git a/vm/devices/firmware/firmware_uefi/src/platform/logger.rs b/vm/devices/firmware/firmware_uefi/src/platform/logger.rs deleted file mode 100644 index 42cfaf1848..0000000000 --- a/vm/devices/firmware/firmware_uefi/src/platform/logger.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Interfaces required to support UEFI event logging. - -use std::fmt::Debug; - -#[derive(Debug)] -pub enum UefiEvent { - BootSuccess(BootInfo), - BootFailure(BootInfo), - NoBootDevice, -} - -#[derive(Debug)] -pub struct BootInfo { - pub secure_boot_succeeded: bool, -} - -/// Interface to log UEFI events. -pub trait UefiLogger: Send { - fn log_event(&self, event: UefiEvent); -} diff --git a/vm/devices/firmware/firmware_uefi/src/platform/mod.rs b/vm/devices/firmware/firmware_uefi/src/platform/mod.rs deleted file mode 100644 index d7b0352024..0000000000 --- a/vm/devices/firmware/firmware_uefi/src/platform/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Platform interfaces required by the UEFI device. - -pub mod logger; -pub mod nvram; diff --git a/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs b/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs deleted file mode 100644 index b8f910ebf7..0000000000 --- a/vm/devices/firmware/firmware_uefi/src/platform/nvram.rs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Interfaces required to support UEFI nvram services. - -pub use uefi_nvram_storage::NextVariable; -pub use uefi_nvram_storage::NvramStorage; -pub use uefi_nvram_storage::NvramStorageError; -pub use uefi_specs::uefi::time::EFI_TIME; - -/// Callbacks that enable nvram services to revoke VSM on ExitBootServices if -/// requested by the guest. -/// -/// This could be backed by different implementations on the host, such as in -/// Underhill asking the host to revoke VSM via a hypercall. -pub trait VsmConfig: Send { - fn revoke_guest_vsm(&self); -} diff --git a/vm/devices/firmware/firmware_uefi/src/service/diagnostics/mod.rs b/vm/devices/firmware/firmware_uefi/src/service/diagnostics/mod.rs index 5e8b17bb15..fac10d4531 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/diagnostics/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/diagnostics/mod.rs @@ -15,14 +15,13 @@ //! internal implementation details should be in submodules. use crate::UefiDevice; +use firmware_uefi_resources::LogLevel; use gpa::Gpa; use guestmem::GuestMemory; use inspect::Inspect; use log::Log; -use mesh::payload::Protobuf; use processor::ProcessingError; use uefi_specs::hyperv::debug_level::DEBUG_ERROR; -use uefi_specs::hyperv::debug_level::DEBUG_INFO; use uefi_specs::hyperv::debug_level::DEBUG_WARN; mod accumulator; @@ -105,52 +104,6 @@ fn emit_log_unrestricted(log: &Log) { } } -/// Log level configuration - encapsulates a u32 mask where u32::MAX means log everything -#[derive(Debug, Clone, Copy, PartialEq, Eq, Protobuf)] -#[mesh(transparent)] -pub struct LogLevel(u32); - -impl LogLevel { - /// Create default log level configuration (ERROR and WARN only) - pub const fn make_default() -> Self { - Self(DEBUG_ERROR | DEBUG_WARN) - } - - /// Create info log level configuration (ERROR, WARN, and INFO) - pub const fn make_info() -> Self { - Self(DEBUG_ERROR | DEBUG_WARN | DEBUG_INFO) - } - - /// Create full log level configuration (all levels) - pub const fn make_full() -> Self { - Self(u32::MAX) - } - - /// Checks if a raw debug level should be logged based on this log level configuration - pub fn should_log(self, raw_debug_level: u32) -> bool { - if self.0 == u32::MAX { - true // Log everything - } else { - (raw_debug_level & self.0) != 0 - } - } -} - -impl Default for LogLevel { - fn default() -> Self { - Self::make_default() - } -} - -impl Inspect for LogLevel { - fn inspect(&self, req: inspect::Request<'_>) { - let human_readable = log::debug_level_to_string(self.0); - req.respond() - .field("raw_value", self.0) - .field("debug_levels", human_readable.as_ref()); - } -} - /// Definition of the diagnostics services state #[derive(Inspect)] pub struct DiagnosticsServices { diff --git a/vm/devices/firmware/firmware_uefi/src/service/event_log.rs b/vm/devices/firmware/firmware_uefi/src/service/event_log.rs index 23dd563a38..4be5150736 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/event_log.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/event_log.rs @@ -4,9 +4,9 @@ //! UEFI Event Logging subsystem. use crate::UefiDevice; -use crate::platform::logger::BootInfo; -use crate::platform::logger::UefiEvent; -use crate::platform::logger::UefiLogger; +use firmware_uefi_resources::platform::BootInfo; +use firmware_uefi_resources::platform::UefiEvent; +use firmware_uefi_resources::platform::UefiLogger; use guestmem::GuestMemory; use guestmem::GuestMemoryError; use inspect::Inspect; diff --git a/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs b/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs index d08e745923..f088d4e10b 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs @@ -17,8 +17,8 @@ pub use spec_services::NvramServicesExt; pub use spec_services::NvramSpecServices; use crate::UefiDevice; -use crate::platform::nvram::VsmConfig; use firmware_uefi_custom_vars::CustomVars; +use firmware_uefi_resources::platform::VsmConfig; use guestmem::GuestMemoryError; use inspect::Inspect; use std::borrow::Cow; @@ -37,7 +37,7 @@ mod spec_services; #[derive(Debug, Error)] pub enum NvramSetupError { #[error("could not query backing nvram storage")] - BadNvramStorage(#[source] crate::platform::nvram::NvramStorageError), + BadNvramStorage(#[source] uefi_nvram_storage::NvramStorageError), #[error("could not inject pre-boot var '{0}': {1:?}")] InjectPreBootVar( Cow<'static, ucs2::Ucs2LeSlice>, diff --git a/vm/devices/firmware/firmware_uefi_resources/Cargo.toml b/vm/devices/firmware/firmware_uefi_resources/Cargo.toml new file mode 100644 index 0000000000..aa715910a9 --- /dev/null +++ b/vm/devices/firmware/firmware_uefi_resources/Cargo.toml @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "firmware_uefi_resources" +edition.workspace = true +rust-version.workspace = true + +[dependencies] +chipset_resources.workspace = true +firmware_uefi_custom_vars.workspace = true +uefi_nvram_storage.workspace = true +uefi_specs.workspace = true +watchdog_core.workspace = true + +inspect.workspace = true +mesh.workspace = true +mesh_protobuf.workspace = true +vm_resource.workspace = true + +[lints] +workspace = true diff --git a/vmm_core/vm_manifest_builder/Cargo.toml b/vmm_core/vm_manifest_builder/Cargo.toml index a040d3a9b0..561e3418fb 100644 --- a/vmm_core/vm_manifest_builder/Cargo.toml +++ b/vmm_core/vm_manifest_builder/Cargo.toml @@ -8,6 +8,7 @@ rust-version.workspace = true [dependencies] chipset_resources.workspace = true +firmware_uefi_resources.workspace = true input_core.workspace = true missing_dev_resources.workspace = true serial_16550_resources.workspace = true diff --git a/vmm_core/vmotherboard/Cargo.toml b/vmm_core/vmotherboard/Cargo.toml index b37654af06..1d53e73399 100644 --- a/vmm_core/vmotherboard/Cargo.toml +++ b/vmm_core/vmotherboard/Cargo.toml @@ -27,8 +27,6 @@ chipset_legacy.workspace = true chipset.workspace = true chipset_resources.workspace = true firmware_pcat.workspace = true -firmware_uefi.workspace = true -uefi_nvram_storage = { workspace = true, features = ["inspect"] } framebuffer.workspace = true floppy = { optional = true, workspace = true } floppy_pcat_stub = { optional = true, workspace = true } @@ -39,7 +37,6 @@ pci_bus.workspace = true pcie.workspace = true vga_proxy = { optional = true, workspace = true } vga = { optional = true, workspace = true } -watchdog_core.workspace = true address_filter.workspace = true arc_cyclic_builder.workspace = true @@ -47,7 +44,6 @@ closeable_mutex.workspace = true cvm_tracing.workspace = true inspect_counters.workspace = true inspect.workspace = true -local_clock.workspace = true mesh.workspace = true pal_async.workspace = true range_map_vec.workspace = true diff --git a/vmm_core/vmotherboard/src/base_chipset.rs b/vmm_core/vmotherboard/src/base_chipset.rs index 43a5facc60..540c70f1ec 100644 --- a/vmm_core/vmotherboard/src/base_chipset.rs +++ b/vmm_core/vmotherboard/src/base_chipset.rs @@ -16,7 +16,6 @@ use chipset_device_resources::GPE0_LINE_SET; use chipset_device_resources::IRQ_LINE_SET; use chipset_device_resources::ResolveChipsetDeviceHandleParams; use closeable_mutex::CloseableMutex; -use firmware_uefi::UefiCommandSet; use framebuffer::Framebuffer; use framebuffer::FramebufferDevice; use framebuffer::FramebufferLocalControl; @@ -220,7 +219,6 @@ impl<'a> BaseChipsetBuilder<'a> { deps_generic_pci_bus, deps_generic_psp: _, // not actually a device... yet deps_hyperv_firmware_pcat, - deps_hyperv_firmware_uefi, deps_hyperv_framebuffer, deps_hyperv_ide, deps_hyperv_vga, @@ -451,55 +449,6 @@ impl<'a> BaseChipsetBuilder<'a> { // The ACPI GPE0 line to use for generation ID. This must match the // value in the DSDT. const GPE0_LINE_GENERATION_ID: u32 = 0; - // for ARM64, 3 + 32 (SPI range start) = 35, - // the SYSTEM_SPI_GENCOUNTER vector for the GIC - const GENERATION_ID_IRQ: u32 = 3; - - if let Some(options::dev::HyperVFirmwareUefi { - config, - logger, - nvram_storage, - generation_id_recv, - watchdog_platform, - watchdog_recv, - vsm_config, - time_source, - }) = deps_hyperv_firmware_uefi - { - builder - .arc_mutex_device("uefi") - .try_add_async(async |services| { - let notify_interrupt = match config.command_set { - UefiCommandSet::X64 => { - services.new_line(GPE0_LINE_SET, "genid", GPE0_LINE_GENERATION_ID) - } - UefiCommandSet::Aarch64 => { - services.new_line(IRQ_LINE_SET, "genid", GENERATION_ID_IRQ) - } - }; - let vmtime = services.register_vmtime(); - let gm = foundation.trusted_vtl0_dma_memory.clone(); - let runtime_deps = firmware_uefi::UefiRuntimeDeps { - gm: gm.clone(), - nvram_storage, - logger, - vmtime, - watchdog_platform, - watchdog_recv, - generation_id_deps: generation_id::GenerationIdRuntimeDeps { - generation_id_recv, - gm, - notify_interrupt, - }, - vsm_config, - time_source, - }; - - firmware_uefi::UefiDevice::new(runtime_deps, config, foundation.is_restoring) - .await - }) - .await?; - } if let Some(options::dev::HyperVFirmwarePcat { config, @@ -1040,7 +989,6 @@ pub mod options { generic_psp: dev::GenericPspDeps, hyperv_firmware_pcat: dev::HyperVFirmwarePcat, - hyperv_firmware_uefi: dev::HyperVFirmwareUefi, hyperv_framebuffer: dev::HyperVFramebufferDeps, hyperv_ide: dev::HyperVIdeDeps, hyperv_vga: dev::HyperVVgaDeps, @@ -1077,7 +1025,6 @@ pub mod options { use super::*; use crate::BusIdPci; use chipset_resources::battery::HostBatteryUpdate; - use local_clock::InspectableLocalClock; macro_rules! feature_gated { ( @@ -1252,29 +1199,6 @@ pub mod options { pub replay_mtrrs: Box, } - /// Hyper-V specific UEFI Helper Device - pub struct HyperVFirmwareUefi { - /// Bundle of static configuration required by the Hyper-V UEFI - /// helper device - pub config: firmware_uefi::UefiConfig, - /// Interface to log UEFI BIOS events - pub logger: Box, - /// Interface for storing/retrieving UEFI NVRAM variables - pub nvram_storage: Box, - /// Channel to receive updated generation ID values - pub generation_id_recv: mesh::Receiver<[u8; 16]>, - /// Device-specific functions the platform must provide in order - /// to use the UEFI watchdog device. - pub watchdog_platform: Box, - /// Channel receiver for watchdog timeout notifications. - pub watchdog_recv: mesh::Receiver<()>, - /// Interface to revoke VSM on `ExitBootServices()` if requested - /// by the guest. - pub vsm_config: Option>, - /// Time source - pub time_source: Box, - } - /// Hyper-V specific framebuffer device // TODO: this doesn't really belong in base_chipset... it's less-so a // device, and more a bit of "infrastructure" that supports other From 2f546a7b631d8faf75c1ebb7dd0928b82dcc8111 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 13:34:04 -0400 Subject: [PATCH 2/9] fix tests --- vm/devices/firmware/firmware_uefi_resources/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/devices/firmware/firmware_uefi_resources/Cargo.toml b/vm/devices/firmware/firmware_uefi_resources/Cargo.toml index aa715910a9..9095eabd5b 100644 --- a/vm/devices/firmware/firmware_uefi_resources/Cargo.toml +++ b/vm/devices/firmware/firmware_uefi_resources/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true [dependencies] chipset_resources.workspace = true firmware_uefi_custom_vars.workspace = true -uefi_nvram_storage.workspace = true +uefi_nvram_storage = { workspace = true, features = ["save_restore"] } uefi_specs.workspace = true watchdog_core.workspace = true From b1936f4bb9805b30ce985f9415fc5688cb6c5e06 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 16:15:21 -0400 Subject: [PATCH 3/9] rest --- openhcl/underhill_core/src/emuplat/uefi.rs | 221 +++++++++++++ openhcl/underhill_core/src/worker.rs | 294 +++++++----------- openvmm/openvmm_core/src/emuplat/firmware.rs | 34 +- openvmm/openvmm_core/src/emuplat/uefi.rs | 152 +++++++++ openvmm/openvmm_core/src/worker/dispatch.rs | 112 +------ openvmm/openvmm_entry/src/lib.rs | 122 +++++--- .../firmware/firmware_uefi/src/resolver.rs | 154 +++++++++ .../firmware_uefi_resources/src/lib.rs | 240 ++++++++++++++ vmm_core/vm_manifest_builder/src/lib.rs | 63 +++- 9 files changed, 1072 insertions(+), 320 deletions(-) create mode 100644 openhcl/underhill_core/src/emuplat/uefi.rs create mode 100644 openvmm/openvmm_core/src/emuplat/uefi.rs create mode 100644 vm/devices/firmware/firmware_uefi/src/resolver.rs create mode 100644 vm/devices/firmware/firmware_uefi_resources/src/lib.rs diff --git a/openhcl/underhill_core/src/emuplat/uefi.rs b/openhcl/underhill_core/src/emuplat/uefi.rs new file mode 100644 index 0000000000..c5f0c604dd --- /dev/null +++ b/openhcl/underhill_core/src/emuplat/uefi.rs @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Resolvers for the per-platform [`firmware_uefi`] dependencies (Underhill). + +use crate::emuplat::firmware::UnderhillLogger; +use crate::emuplat::firmware::UnderhillVsmConfig; +use crate::emuplat::non_volatile_store::VmgsBrokerNonVolatileStore; +use crate::emuplat::watchdog::UnderhillWatchdogPlatform; +use anyhow::Context as _; +use async_trait::async_trait; +use firmware_uefi_resources::ResolvedUefiLogger; +use firmware_uefi_resources::ResolvedUefiNvramStorage; +use firmware_uefi_resources::ResolvedUefiVsmConfig; +use firmware_uefi_resources::ResolvedUefiWatchdogPlatform; +use firmware_uefi_resources::UefiLoggerHandleKind; +use firmware_uefi_resources::UefiNvramStorageHandleKind; +use firmware_uefi_resources::UefiVsmConfigHandleKind; +use firmware_uefi_resources::UefiWatchdogPlatformHandleKind; +use guest_emulation_transport::GuestEmulationTransportClient; +use hcl_compat_uefi_nvram_storage::HclCompatNvram; +use hcl_compat_uefi_nvram_storage::HclCompatNvramQuirks; +use std::sync::Arc; +use std::sync::Weak; +use uefi_nvram_storage::VmmNvramStorage; +use uefi_nvram_storage::in_memory::InMemoryNvram; +use virt_mshv_vtl::UhPartition; +use vm_resource::AsyncResolveResource; +use vm_resource::PlatformResource; +use vm_resource::ResolveResource; +use vmcore::non_volatile_store::EphemeralNonVolatileStore; +use vmm_core::emuplat::hcl_compat_uefi_nvram_storage::VmgsStorageBackendAdapter; +use vmm_core::partition_unit::Halt; +use watchdog_core::platform::WatchdogCallback; +use watchdog_core::platform::WatchdogPlatform; + +/// Resolver that creates a fresh [`UnderhillLogger`] each time it is resolved. +pub struct UnderhillUefiLoggerResolver { + get: GuestEmulationTransportClient, +} + +impl UnderhillUefiLoggerResolver { + pub fn new(get: GuestEmulationTransportClient) -> Self { + Self { get } + } +} + +impl ResolveResource for UnderhillUefiLoggerResolver { + type Output = ResolvedUefiLogger; + type Error = std::convert::Infallible; + + fn resolve( + &self, + _resource: PlatformResource, + _input: (), + ) -> Result { + Ok(ResolvedUefiLogger(Box::new(UnderhillLogger { + get: self.get.clone(), + }))) + } +} + +/// Resolver that produces UEFI NVRAM storage backed by the host VMGS, or an +/// in-memory fallback. +pub struct UnderhillUefiNvramStorageResolver { + vmgs_client: Option, +} + +impl UnderhillUefiNvramStorageResolver { + pub fn new(vmgs_client: Option) -> Self { + Self { vmgs_client } + } +} + +impl ResolveResource + for UnderhillUefiNvramStorageResolver +{ + type Output = ResolvedUefiNvramStorage; + type Error = anyhow::Error; + + fn resolve( + &self, + _resource: PlatformResource, + _input: (), + ) -> Result { + let storage: Box = match &self.vmgs_client { + Some(vmgs) => Box::new(HclCompatNvram::new( + VmgsStorageBackendAdapter( + vmgs.as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) + .context("failed to instantiate UEFI NVRAM store")?, + ), + Some(HclCompatNvramQuirks { + skip_corrupt_vars_with_missing_null_term: true, + }), + )), + None => Box::new(InMemoryNvram::new()), + }; + Ok(ResolvedUefiNvramStorage(storage)) + } +} + +/// Resolver that produces a fresh [`UnderhillWatchdogPlatform`] each time it +/// is resolved, along with the matching receiver. +#[expect(unused)] // One of these will be unused no matter what +pub struct UnderhillUefiWatchdogPlatformResolver { + get: GuestEmulationTransportClient, + // TODO: Should this be a weak? + partition: Arc, + halt_vps: Arc, +} + +impl UnderhillUefiWatchdogPlatformResolver { + pub fn new( + get: GuestEmulationTransportClient, + partition: Arc, + halt_vps: Arc, + ) -> Self { + Self { + get, + partition, + halt_vps, + } + } +} + +#[async_trait] +impl AsyncResolveResource + for UnderhillUefiWatchdogPlatformResolver +{ + type Output = ResolvedUefiWatchdogPlatform; + type Error = anyhow::Error; + + async fn resolve( + &self, + _resolver: &vm_resource::ResourceResolver, + _handle: PlatformResource, + _input: &(), + ) -> Result { + let (watchdog_send, watchdog_recv) = mesh::channel(); + let store = EphemeralNonVolatileStore::new_boxed(); + let mut platform = UnderhillWatchdogPlatform::new(store, self.get.clone()) + .await + .context("failed to initialize UEFI watchdog platform")?; + #[cfg(guest_arch = "x86_64")] + platform.add_callback(Box::new(UefiWatchdogTimeoutNmi { + partition: self.partition.clone(), + watchdog_send, + })); + #[cfg(guest_arch = "aarch64")] + platform.add_callback(Box::new(UefiWatchdogTimeoutReset { + halt_vps: self.halt_vps.clone(), + watchdog_send, + })); + Ok(ResolvedUefiWatchdogPlatform { + platform: Box::new(platform), + watchdog_recv, + }) + } +} + +/// Resolver that produces a fresh [`UnderhillVsmConfig`] each time it is +/// resolved. +pub struct UnderhillUefiVsmConfigResolver { + partition: Weak, +} + +impl UnderhillUefiVsmConfigResolver { + pub fn new(partition: Weak) -> Self { + Self { partition } + } +} + +impl ResolveResource for UnderhillUefiVsmConfigResolver { + type Output = ResolvedUefiVsmConfig; + type Error = std::convert::Infallible; + + fn resolve( + &self, + _resource: PlatformResource, + _input: (), + ) -> Result { + Ok(ResolvedUefiVsmConfig(Box::new(UnderhillVsmConfig { + partition: self.partition.clone(), + }))) + } +} + +#[cfg(guest_arch = "x86_64")] +struct UefiWatchdogTimeoutNmi { + partition: Arc, + watchdog_send: mesh::Sender<()>, +} + +#[cfg(guest_arch = "x86_64")] +#[async_trait] +impl WatchdogCallback for UefiWatchdogTimeoutNmi { + async fn on_timeout(&mut self) { + use virt::Partition; + self.partition.request_msi( + hvdef::Vtl::Vtl0, + virt::irqcon::MsiRequest::new_x86(virt::irqcon::DeliveryMode::NMI, 0, false, 0, false), + ); + self.watchdog_send.send(()); + } +} + +#[cfg(guest_arch = "aarch64")] +struct UefiWatchdogTimeoutReset { + halt_vps: Arc, + watchdog_send: mesh::Sender<()>, +} + +#[cfg(guest_arch = "aarch64")] +#[async_trait] +impl WatchdogCallback for UefiWatchdogTimeoutReset { + async fn on_timeout(&mut self) { + use vmm_core_defs::HaltReason; + self.halt_vps.halt(HaltReason::Reset); + self.watchdog_send.send(()); + } +} diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index fb460e1083..24a9580b9f 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -9,7 +9,6 @@ cfg_if::cfg_if! { use chipset_device_resources::BSP_LINT_LINE_SET; use chipset_resources::pm::DEFAULT_ACPI_IRQ; use chipset_resources::pm::DEFAULT_PM_PIO_BASE; - use virt::irqcon::MsiRequest; use vmm_core::acpi_builder::AcpiTablesBuilder; } else if #[cfg(guest_arch = "aarch64")] { pub use hvdef::HvArm64RegisterName as HvArchRegisterName; @@ -26,7 +25,6 @@ use crate::dispatch::vtl2_settings_worker::wait_for_mana; use crate::emuplat::EmuplatServicing; use crate::emuplat::cmos_rtc_time_source::UnderhillCmosRtcTimeSourceResolver; use crate::emuplat::firmware::UnderhillLogger; -use crate::emuplat::firmware::UnderhillVsmConfig; use crate::emuplat::framebuffer::FramebufferRemoteControl; use crate::emuplat::i440bx_host_pci_bridge::ArcMutexGetBackedAdjustGpaRange; use crate::emuplat::i440bx_host_pci_bridge::GetBackedAdjustGpaRange; @@ -72,8 +70,8 @@ use debug_ptr::DebugPtr; use disk_backend::Disk; use disk_backend_resources::BlockDeviceDiskHandle; use disk_blockdevice::resolver::BlockDeviceResolver; -use firmware_uefi::LogLevel; -use firmware_uefi::UefiCommandSet; +use firmware_uefi_resources::LogLevel; +use firmware_uefi_resources::UefiCommandSet; use futures::executor::block_on; use futures::future::join_all; use futures_concurrency::future::Race; @@ -87,7 +85,6 @@ use guest_emulation_transport::api::platform_settings::DevicePlatformSettings; use guest_emulation_transport::api::platform_settings::General; use guestmem::GuestMemory; use guid::Guid; -use hcl_compat_uefi_nvram_storage::HclCompatNvramQuirks; use hvdef::HvRegisterValue; use hvdef::Vtl; use hvdef::hypercall::HvGuestOsId; @@ -2081,7 +2078,7 @@ async fn new_underhill_vm( let (vmgs_client, vmgs_handle) = spawn_vmgs_broker(get_spawner, vmgs); resolver.add_resolver(vmgs_client.clone()); ( - Some(Box::new(vmgs_client.clone()) as Box), + Some(vmgs_client.clone()), Some((vmgs_client, meta, vmgs_handle)), ) } else { @@ -2093,7 +2090,7 @@ async fn new_underhill_vm( chipset_device_worker::resolver::RemoteChipsetDeviceResolver( OpenHclRemoteDynamicResolvers { get: get_client.clone(), - vmgs: vmgs.as_ref().map(|(client, _, _)| client.clone()), + vmgs: vmgs_client.clone(), }, ), ); @@ -2402,6 +2399,117 @@ async fn new_underhill_vm( ); } + if matches!(firmware_type, FirmwareType::Uefi) { + use crate::emuplat::uefi::*; + use firmware_uefi_custom_vars::CustomVars; + use guest_emulation_transport::api::platform_settings::SecureBootTemplateType; + + // map the GET's template enum onto the hardcoded secureboot template type + let base_vars = match dps.general.secure_boot_template { + SecureBootTemplateType::None => CustomVars::default(), + SecureBootTemplateType::MicrosoftWindows => { + if cfg!(guest_arch = "x86_64") { + hyperv_secure_boot_templates::x64::microsoft_windows() + } else if cfg!(guest_arch = "aarch64") { + hyperv_secure_boot_templates::aarch64::microsoft_windows() + } else { + anyhow::bail!("no secure boot template for current guest_arch") + } + } + SecureBootTemplateType::MicrosoftUefiCertificateAuthority => { + if cfg!(guest_arch = "x86_64") { + hyperv_secure_boot_templates::x64::microsoft_uefi_ca() + } else if cfg!(guest_arch = "aarch64") { + hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca() + } else { + anyhow::bail!("no secure boot template for current guest_arch") + } + } + }; + + // check if vmgs includes custom UEFI JSON + let custom_uefi_json_data = if let Some(vmgs_client) = vmgs_client.as_ref() { + vmgs_client + .as_non_volatile_store(vmgs::FileId::CUSTOM_UEFI, false) + .context("failed to instantiate custom UEFI JSON store")? + .restore() + .await + .context("failed to get custom UEFI JSON data")? + } else { + None + }; + + // obtain the final custom uefi vars by applying the delta onto + // the base vars + let custom_uefi_vars = match custom_uefi_json_data { + Some(data) => { + let res = (|| -> Result { + let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?; + Ok(base_vars.apply_delta(delta)?) + })(); + + match res { + Ok(vars) => vars, + Err(e) => { + tracing::error!(CVM_ALLOWED, "Failed to load custom UEFI vars"); + get_client + .event_log_fatal(EventLogId::BOOT_FAILURE_SECURE_BOOT_FAILED) + .await; + return Err(e).context("failed to load custom UEFI variables"); + } + } + } + None => base_vars, + }; + + let config = firmware_uefi_resources::UefiConfig { + custom_uefi_vars, + secure_boot: dps.general.secure_boot_enabled, + initial_generation_id, + use_mmio: cfg!(not(guest_arch = "x86_64")), + command_set: if cfg!(guest_arch = "x86_64") { + UefiCommandSet::X64 + } else { + UefiCommandSet::Aarch64 + }, + diagnostics_log_level: { + use get_protocol::dps_json::EfiDiagnosticsLogLevelType as LogLevelType; + let level = dps.general.efi_diagnostics_log_level.0; + match level { + x if x == LogLevelType::DEFAULT.0 => LogLevel::make_default(), + x if x == LogLevelType::INFO.0 => LogLevel::make_info(), + x if x == LogLevelType::FULL.0 => LogLevel::make_full(), + _ => LogLevel::make_default(), + } + }, + }; + + // Register the platform resolvers used by the resource-model UEFI + // device. + resolver.add_resolver(UnderhillUefiLoggerResolver::new(get_client.clone())); + resolver.add_resolver(UnderhillUefiNvramStorageResolver::new(vmgs_client.clone())); + resolver.add_async_resolver(UnderhillUefiWatchdogPlatformResolver::new( + get_client.clone(), + partition.clone(), + halt_vps.clone(), + )); + resolver.add_resolver(UnderhillUefiVsmConfigResolver::new(Arc::downgrade( + &partition, + ))); + + let generation_id_recv = get_client + .take_generation_id_recv() + .await + .expect("first time taking chan"); + + chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest { + config, + generation_id_recv, + vsm_config: true, + time_source: PlatformResource.into_resource(), + }); + } + let vm_manifest_builder::VmChipsetResult { chipset, mut chipset_devices, @@ -2416,7 +2524,6 @@ async fn new_underhill_vm( #[cfg(not(guest_arch = "x86_64"))] let deps_hyperv_firmware_pcat = None; - let mut deps_hyperv_firmware_uefi = None; match firmware_type { #[cfg(not(guest_arch = "x86_64"))] FirmwareType::Pcat => { @@ -2498,151 +2605,9 @@ async fn new_underhill_vm( replay_mtrrs: Box::new(move || halt_vps.replay_mtrrs()), }) } - FirmwareType::Uefi => { - use firmware_uefi_custom_vars::CustomVars; - use guest_emulation_transport::api::platform_settings::SecureBootTemplateType; - use hcl_compat_uefi_nvram_storage::HclCompatNvram; - use vmm_core::emuplat::hcl_compat_uefi_nvram_storage::VmgsStorageBackendAdapter; - - // map the GET's template enum onto the hardcoded secureboot template type - let base_vars = match dps.general.secure_boot_template { - SecureBootTemplateType::None => CustomVars::default(), - SecureBootTemplateType::MicrosoftWindows => { - if cfg!(guest_arch = "x86_64") { - hyperv_secure_boot_templates::x64::microsoft_windows() - } else if cfg!(guest_arch = "aarch64") { - hyperv_secure_boot_templates::aarch64::microsoft_windows() - } else { - anyhow::bail!("no secure boot template for current guest_arch") - } - } - SecureBootTemplateType::MicrosoftUefiCertificateAuthority => { - if cfg!(guest_arch = "x86_64") { - hyperv_secure_boot_templates::x64::microsoft_uefi_ca() - } else if cfg!(guest_arch = "aarch64") { - hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca() - } else { - anyhow::bail!("no secure boot template for current guest_arch") - } - } - }; - - // check if vmgs includes custom UEFI JSON - let custom_uefi_json_data = if let Some(vmgs_client) = vmgs_client.as_ref() { - vmgs_client - .as_non_volatile_store(vmgs::FileId::CUSTOM_UEFI, false) - .context("failed to instantiate custom UEFI JSON store")? - .restore() - .await - .context("failed to get custom UEFI JSON data")? - } else { - None - }; - - // obtain the final custom uefi vars by applying the delta onto - // the base vars - let custom_uefi_vars = match custom_uefi_json_data { - Some(data) => { - let res = (|| -> Result { - let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?; - Ok(base_vars.apply_delta(delta)?) - })(); - - match res { - Ok(vars) => vars, - Err(e) => { - tracing::error!(CVM_ALLOWED, "Failed to load custom UEFI vars"); - get_client - .event_log_fatal(EventLogId::BOOT_FAILURE_SECURE_BOOT_FAILED) - .await; - return Err(e).context("failed to load custom UEFI variables"); - } - } - } - None => base_vars, - }; - - let config = firmware_uefi::UefiConfig { - custom_uefi_vars, - secure_boot: dps.general.secure_boot_enabled, - initial_generation_id, - use_mmio: cfg!(not(guest_arch = "x86_64")), - command_set: if cfg!(guest_arch = "x86_64") { - UefiCommandSet::X64 - } else { - UefiCommandSet::Aarch64 - }, - diagnostics_log_level: { - use get_protocol::dps_json::EfiDiagnosticsLogLevelType as LogLevelType; - let level = dps.general.efi_diagnostics_log_level.0; - match level { - x if x == LogLevelType::DEFAULT.0 => LogLevel::make_default(), - x if x == LogLevelType::INFO.0 => LogLevel::make_info(), - x if x == LogLevelType::FULL.0 => LogLevel::make_full(), - _ => LogLevel::make_default(), - } - }, - }; - - let (watchdog_send, watchdog_recv) = mesh::channel(); - deps_hyperv_firmware_uefi = Some(dev::HyperVFirmwareUefi { - config, - logger: Box::new(UnderhillLogger { - get: get_client.clone(), - }), - nvram_storage: if let Some(vmgs_client) = vmgs_client.as_ref() { - Box::new(HclCompatNvram::new( - VmgsStorageBackendAdapter( - vmgs_client - .as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) - .context("failed to instantiate UEFI NVRAM store")?, - ), - Some(HclCompatNvramQuirks { - skip_corrupt_vars_with_missing_null_term: true, - }), - )) - } else { - Box::new(uefi_nvram_storage::in_memory::InMemoryNvram::new()) - }, - generation_id_recv: get_client - .take_generation_id_recv() - .await - .expect("first time taking chan"), - watchdog_platform: { - // UEFI watchdog doesn't persist to VMGS at this time - let store = EphemeralNonVolatileStore::new_boxed(); - - // Create base watchdog platform - let mut underhill_watchdog_platform = - UnderhillWatchdogPlatform::new(store, get_client.clone()).await?; - - // Inject NMI on watchdog timeout - #[cfg(guest_arch = "x86_64")] - let watchdog_callback = WatchdogTimeoutNmi { - partition: partition.clone(), - watchdog_send: Some(watchdog_send), - }; - - // ARM64 does not have NMI support yet, so halt instead - #[cfg(guest_arch = "aarch64")] - let watchdog_callback = WatchdogTimeoutReset { - halt_vps: halt_vps.clone(), - watchdog_send: Some(watchdog_send), - }; - - // Add the callback - underhill_watchdog_platform.add_callback(Box::new(watchdog_callback)); - - Box::new(underhill_watchdog_platform) - }, - watchdog_recv, - vsm_config: Some(Box::new(UnderhillVsmConfig { - partition: Arc::downgrade(&partition), - })), - time_source: Box::new(rtc_time_source.new_linked_clock()), - }) + FirmwareType::Uefi | FirmwareType::None => { + // UEFI is configured via `chipset.with_uefi(...)` above } - FirmwareType::None => {} }; if dps.general.processor_idle_enabled { @@ -2956,7 +2921,6 @@ async fn new_underhill_vm( let devices = BaseChipsetDevices { deps_generic_cmos_rtc, deps_generic_psp, - deps_hyperv_firmware_uefi, deps_generic_isa_dma, deps_generic_isa_floppy: None, deps_generic_pci_bus: None, @@ -3957,30 +3921,6 @@ impl ChipsetDevice for FallbackMmioDevice { } } -#[cfg(guest_arch = "x86_64")] -struct WatchdogTimeoutNmi { - partition: Arc, - watchdog_send: Option>, -} - -#[cfg(guest_arch = "x86_64")] -#[async_trait::async_trait] -impl WatchdogCallback for WatchdogTimeoutNmi { - async fn on_timeout(&mut self) { - crate::livedump::livedump().await; - - // Unlike Hyper-V, we only send the NMI to the BSP. - self.partition.request_msi( - Vtl::Vtl0, - MsiRequest::new_x86(virt::irqcon::DeliveryMode::NMI, 0, false, 0, false), - ); - - if let Some(watchdog_send) = &self.watchdog_send { - watchdog_send.send(()); - } - } -} - struct WatchdogTimeoutReset { halt_vps: Arc, watchdog_send: Option>, diff --git a/openvmm/openvmm_core/src/emuplat/firmware.rs b/openvmm/openvmm_core/src/emuplat/firmware.rs index 6550328aef..d4c33759e3 100644 --- a/openvmm/openvmm_core/src/emuplat/firmware.rs +++ b/openvmm/openvmm_core/src/emuplat/firmware.rs @@ -5,9 +5,13 @@ use firmware_pcat::PcatEvent; use firmware_pcat::PcatLogger; -use firmware_uefi::platform::logger::UefiEvent; -use firmware_uefi::platform::logger::UefiLogger; +use firmware_uefi_resources::ResolvedUefiLogger; +use firmware_uefi_resources::UefiLoggerHandleKind; +use firmware_uefi_resources::platform::UefiEvent; +use firmware_uefi_resources::platform::UefiLogger; use get_resources::ged::FirmwareEvent; +use vm_resource::PlatformResource; +use vm_resource::ResolveResource; /// Forwards UEFI and PCAT events to via the provided [`mesh::Sender`]. #[derive(Debug)] @@ -47,3 +51,29 @@ impl PcatLogger for MeshLogger { self.send(event); } } + +// TODO: PCAT resolving +pub struct MeshLoggerResolver { + sender: Option>, +} + +impl MeshLoggerResolver { + pub fn new(sender: Option>) -> Self { + Self { sender } + } +} + +impl ResolveResource for MeshLoggerResolver { + type Output = ResolvedUefiLogger; + type Error = std::convert::Infallible; + + fn resolve( + &self, + _resource: PlatformResource, + _input: (), + ) -> Result { + Ok(ResolvedUefiLogger(Box::new(MeshLogger::new( + self.sender.clone(), + )))) + } +} diff --git a/openvmm/openvmm_core/src/emuplat/uefi.rs b/openvmm/openvmm_core/src/emuplat/uefi.rs new file mode 100644 index 0000000000..c2330d3276 --- /dev/null +++ b/openvmm/openvmm_core/src/emuplat/uefi.rs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Resolvers for the per-platform [`firmware_uefi`] dependencies. + +use crate::partition::HvlitePartition; +use crate::vmgs_non_volatile_store::HvLiteVmgsNonVolatileStore; +use anyhow::Context as _; +use async_trait::async_trait; +use firmware_uefi_resources::ResolvedUefiNvramStorage; +use firmware_uefi_resources::ResolvedUefiWatchdogPlatform; +use firmware_uefi_resources::UefiNvramStorageHandleKind; +use firmware_uefi_resources::UefiWatchdogPlatformHandleKind; +use hcl_compat_uefi_nvram_storage::HclCompatNvram; +use std::sync::Arc; +use uefi_nvram_storage::VmmNvramStorage; +use uefi_nvram_storage::in_memory::InMemoryNvram; +use vm_resource::AsyncResolveResource; +use vm_resource::PlatformResource; +use vm_resource::ResolveResource; +use vmcore::non_volatile_store::EphemeralNonVolatileStore; +use vmm_core::emuplat::hcl_compat_uefi_nvram_storage::VmgsStorageBackendAdapter; +use vmm_core::partition_unit::Halt; +use watchdog_core::platform::BaseWatchdogPlatform; +use watchdog_core::platform::WatchdogCallback; +use watchdog_core::platform::WatchdogPlatform; + +/// Resolver that produces UEFI NVRAM storage backed by the host VMGS file (if +/// one is available) or by an in-memory fallback. +pub struct VmgsUefiNvramStorageResolver { + vmgs_client: Option, +} + +impl VmgsUefiNvramStorageResolver { + pub fn new(vmgs_client: Option) -> Self { + Self { vmgs_client } + } +} + +impl ResolveResource + for VmgsUefiNvramStorageResolver +{ + type Output = ResolvedUefiNvramStorage; + type Error = anyhow::Error; + + fn resolve( + &self, + _resource: PlatformResource, + _input: (), + ) -> Result { + let storage: Box = match &self.vmgs_client { + Some(vmgs) => Box::new(HclCompatNvram::new( + VmgsStorageBackendAdapter( + vmgs.as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) + .context("failed to instantiate UEFI NVRAM store")?, + ), + None, + )), + None => Box::new(InMemoryNvram::new()), + }; + Ok(ResolvedUefiNvramStorage(storage)) + } +} + +/// Resolver that produces a fresh [`BaseWatchdogPlatform`] (and the matching +/// receiver) for the UEFI watchdog on each resolution. +#[expect(unused)] // One of these will be unused no matter what +pub struct OpenvmmUefiWatchdogPlatformResolver { + // TODO: Should this be a weak reference? + partition: Arc, + halt_vps: Arc, +} + +impl OpenvmmUefiWatchdogPlatformResolver { + pub fn new(partition: Arc, halt_vps: Arc) -> Self { + Self { + partition, + halt_vps, + } + } +} + +#[async_trait] +impl AsyncResolveResource + for OpenvmmUefiWatchdogPlatformResolver +{ + type Output = ResolvedUefiWatchdogPlatform; + type Error = anyhow::Error; + + async fn resolve( + &self, + _resolver: &vm_resource::ResourceResolver, + _resource: PlatformResource, + _input: &(), + ) -> Result { + let (watchdog_send, watchdog_recv) = mesh::channel(); + let store = EphemeralNonVolatileStore::new_boxed(); + let mut platform = BaseWatchdogPlatform::new(store) + .await + .context("failed to initialize UEFI watchdog platform")?; + #[cfg(guest_arch = "x86_64")] + platform.add_callback(Box::new(UefiWatchdogTimeoutNmi { + partition: self.partition.clone(), + watchdog_send, + })); + #[cfg(guest_arch = "aarch64")] + platform.add_callback(Box::new(UefiWatchdogTimeoutReset { + halt_vps: self.halt_vps.clone(), + watchdog_send, + })); + Ok(ResolvedUefiWatchdogPlatform { + platform: Box::new(platform), + watchdog_recv, + }) + } +} + +/// On-timeout callback used by the OpenVMM UEFI watchdog: sends an NMI to the +/// BSP on x86_64 and resets the VM on aarch64. +#[cfg(guest_arch = "x86_64")] +struct UefiWatchdogTimeoutNmi { + // TODO: Should this be a weak? + partition: Arc, + watchdog_send: mesh::Sender<()>, +} + +#[cfg(guest_arch = "x86_64")] +#[async_trait] +impl WatchdogCallback for UefiWatchdogTimeoutNmi { + async fn on_timeout(&mut self) { + self.partition.request_msi( + hvdef::Vtl::Vtl0, + virt::irqcon::MsiRequest::new_x86(virt::irqcon::DeliveryMode::NMI, 0, false, 0, false), + ); + self.watchdog_send.send(()); + } +} + +#[cfg(guest_arch = "aarch64")] +struct UefiWatchdogTimeoutReset { + halt_vps: Arc, + watchdog_send: mesh::Sender<()>, +} + +#[cfg(guest_arch = "aarch64")] +#[async_trait] +impl WatchdogCallback for UefiWatchdogTimeoutReset { + async fn on_timeout(&mut self) { + self.halt_vps.halt(vmcore::halt::HaltReason::Reset); + self.watchdog_send.send(()); + } +} diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index a14ed6e1e9..c457cc1e9b 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -18,8 +18,7 @@ use chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle; use debug_ptr::DebugPtr; use disk_backend::Disk; use disk_backend::resolve::ResolveDiskParameters; -use firmware_uefi::LogLevel; -use firmware_uefi::UefiCommandSet; +use firmware_uefi_resources::LogLevel; use floppy_resources::FloppyDiskConfig; use futures::FutureExt; use futures::StreamExt; @@ -35,7 +34,6 @@ use ide_resources::IdeDeviceConfig; use igvm::IgvmFile; use input_core::InputData; use input_core::MultiplexedInputHandle; -use local_clock::LocalClockDelta; use membacking::GuestMemoryBuilder; use membacking::GuestMemoryManager; use membacking::SharedMemoryBacking; @@ -1207,80 +1205,21 @@ impl InitializedVm { #[cfg_attr(not(guest_arch = "x86_64"), expect(unused_mut))] let mut deps_hyperv_firmware_pcat = None; - let mut deps_hyperv_firmware_uefi = None; match &cfg.load_mode { LoadMode::Uefi { .. } => { - let (watchdog_send, watchdog_recv) = mesh::channel(); - deps_hyperv_firmware_uefi = Some(dev::HyperVFirmwareUefi { - config: firmware_uefi::UefiConfig { - custom_uefi_vars: cfg.custom_uefi_vars, - secure_boot: cfg.secure_boot_enabled, - initial_generation_id: { - let mut generation_id = [0; 16]; - getrandom::fill(&mut generation_id).expect("rng failure"); - generation_id - }, - use_mmio: cfg!(not(guest_arch = "x86_64")), - command_set: if cfg!(guest_arch = "x86_64") { - UefiCommandSet::X64 - } else { - UefiCommandSet::Aarch64 - }, - diagnostics_log_level: cfg.efi_diagnostics_log_level, - }, - logger, - nvram_storage: { - use hcl_compat_uefi_nvram_storage::HclCompatNvram; - use uefi_nvram_storage::in_memory::InMemoryNvram; - use vmm_core::emuplat::hcl_compat_uefi_nvram_storage::VmgsStorageBackendAdapter; - - match vmgs_client { - Some(vmgs) => Box::new(HclCompatNvram::new( - VmgsStorageBackendAdapter( - vmgs.as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) - .context("failed to instantiate UEFI NVRAM store")?, - ), - None, - )), - None => Box::new(InMemoryNvram::new()), - } - }, - generation_id_recv, - watchdog_platform: { - use vmcore::non_volatile_store::EphemeralNonVolatileStore; - - // UEFI watchdog doesn't persist to VMGS at this time - let store = EphemeralNonVolatileStore::new_boxed(); - - // Create the base watchdog platform - let mut base_watchdog_platform = BaseWatchdogPlatform::new(store).await?; - - // Inject NMI on watchdog timeout - #[cfg(guest_arch = "x86_64")] - let watchdog_callback = WatchdogTimeoutNmi { - partition: partition.clone(), - watchdog_send: Some(watchdog_send), - }; - - // ARM64 does not have NMI support yet, so halt instead - #[cfg(guest_arch = "aarch64")] - let watchdog_callback = WatchdogTimeoutReset { - halt_vps: halt_vps.clone(), - watchdog_send: Some(watchdog_send), - }; - - // Add callbacks - base_watchdog_platform.add_callback(Box::new(watchdog_callback)); - - Box::new(base_watchdog_platform) - }, - watchdog_recv, - vsm_config: None, - // TODO: persist SystemTimeClock time across reboots. - time_source: Box::new(local_clock::SystemTimeClock::new( - LocalClockDelta::from_millis(cfg.rtc_delta_milliseconds), - )), - }) + use emuplat::uefi::*; + // Register the platform-specific resolvers used by the UEFI + // device. + resolver.add_resolver(emuplat::firmware::MeshLoggerResolver::new( + cfg.firmware_event_send.clone(), + )); + resolver.add_resolver(VmgsUefiNvramStorageResolver::new( + vmgs_client_inspect_handle.clone(), + )); + resolver.add_async_resolver(OpenvmmUefiWatchdogPlatformResolver::new( + partition.clone(), + halt_vps.clone(), + )); } #[cfg(guest_arch = "x86_64")] LoadMode::Pcat { @@ -1662,7 +1601,6 @@ impl InitializedVm { deps_generic_pci_bus, deps_generic_psp, deps_hyperv_firmware_pcat, - deps_hyperv_firmware_uefi, deps_hyperv_framebuffer, deps_hyperv_ide, deps_hyperv_vga, @@ -3552,28 +3490,6 @@ fn add_devices_to_dsdt_arm64( } } -#[cfg(guest_arch = "x86_64")] -struct WatchdogTimeoutNmi { - partition: Arc, - watchdog_send: Option>, -} - -#[cfg(guest_arch = "x86_64")] -#[async_trait::async_trait] -impl WatchdogCallback for WatchdogTimeoutNmi { - async fn on_timeout(&mut self) { - // Unlike Hyper-V, we only send the NMI to the BSP. - self.partition.request_msi( - Vtl::Vtl0, - virt::irqcon::MsiRequest::new_x86(virt::irqcon::DeliveryMode::NMI, 0, false, 0, false), - ); - - if let Some(watchdog_send) = &self.watchdog_send { - watchdog_send.send(()); - } - } -} - struct WatchdogTimeoutReset { halt_vps: Arc, watchdog_send: Option>, diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index ddc21cf893..08da3e6545 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -940,6 +940,88 @@ async fn vm_config_from_command_line( ); } + let custom_uefi_vars = { + use firmware_uefi_custom_vars::CustomVars; + + // load base vars from specified template, or use an empty set of base + // vars if none was specified. + let base_vars = match opt.secure_boot_template { + Some(template) => match (arch, template) { + (MachineArch::X86_64, SecureBootTemplateCli::Windows) => { + hyperv_secure_boot_templates::x64::microsoft_windows() + } + (MachineArch::X86_64, SecureBootTemplateCli::UefiCa) => { + hyperv_secure_boot_templates::x64::microsoft_uefi_ca() + } + (MachineArch::Aarch64, SecureBootTemplateCli::Windows) => { + hyperv_secure_boot_templates::aarch64::microsoft_windows() + } + (MachineArch::Aarch64, SecureBootTemplateCli::UefiCa) => { + hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca() + } + }, + None => CustomVars::default(), + }; + + // TODO: fallback to VMGS read if no command line flag was given + + let custom_uefi_json_data = match &opt.custom_uefi_json { + Some(file) => Some(fs_err::read(file).context("opening custom uefi json file")?), + None => None, + }; + + // obtain the final custom uefi vars by applying the delta onto the base vars + match custom_uefi_json_data { + Some(data) => { + let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?; + base_vars.apply_delta(delta)? + } + None => base_vars, + } + }; + + let efi_diagnostics_log_level = match opt.efi_diagnostics_log_level.unwrap_or_default() { + EfiDiagnosticsLogLevelCli::Default => EfiDiagnosticsLogLevelType::Default, + EfiDiagnosticsLogLevelCli::Info => EfiDiagnosticsLogLevelType::Info, + EfiDiagnosticsLogLevelCli::Full => EfiDiagnosticsLogLevelType::Full, + }; + + if opt.uefi { + use firmware_uefi_resources::UefiCommandSet; + use firmware_uefi_resources::UefiConfig; + use vm_manifest_builder::UefiManifest; + + let log_level = match efi_diagnostics_log_level { + EfiDiagnosticsLogLevelType::Default => { + firmware_uefi_resources::LogLevel::make_default() + } + EfiDiagnosticsLogLevelType::Info => firmware_uefi_resources::LogLevel::make_info(), + EfiDiagnosticsLogLevelType::Full => firmware_uefi_resources::LogLevel::make_full(), + }; + let mut initial_generation_id = [0; 16]; + getrandom::fill(&mut initial_generation_id).expect("rng failure"); + let (_send, generation_id_recv) = mesh::channel(); + chipset = chipset.with_uefi(UefiManifest { + config: UefiConfig { + custom_uefi_vars: custom_uefi_vars.clone(), + secure_boot: opt.secure_boot, + initial_generation_id, + use_mmio: !matches!(arch, MachineArch::X86_64), + command_set: match arch { + MachineArch::X86_64 => UefiCommandSet::X64, + MachineArch::Aarch64 => UefiCommandSet::Aarch64, + }, + diagnostics_log_level: log_level, + }, + generation_id_recv, + vsm_config: false, + time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { + delta_milliseconds: 0, + } + .into_resource(), + }); + } + // TODO: load from VMGS file if it exists let bios_guid = Guid::new_random(); @@ -1245,46 +1327,6 @@ async fn vm_config_from_command_line( }); } - let custom_uefi_vars = { - use firmware_uefi_custom_vars::CustomVars; - - // load base vars from specified template, or use an empty set of base - // vars if none was specified. - let base_vars = match opt.secure_boot_template { - Some(template) => match (arch, template) { - (MachineArch::X86_64, SecureBootTemplateCli::Windows) => { - hyperv_secure_boot_templates::x64::microsoft_windows() - } - (MachineArch::X86_64, SecureBootTemplateCli::UefiCa) => { - hyperv_secure_boot_templates::x64::microsoft_uefi_ca() - } - (MachineArch::Aarch64, SecureBootTemplateCli::Windows) => { - hyperv_secure_boot_templates::aarch64::microsoft_windows() - } - (MachineArch::Aarch64, SecureBootTemplateCli::UefiCa) => { - hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca() - } - }, - None => CustomVars::default(), - }; - - // TODO: fallback to VMGS read if no command line flag was given - - let custom_uefi_json_data = match &opt.custom_uefi_json { - Some(file) => Some(fs_err::read(file).context("opening custom uefi json file")?), - None => None, - }; - - // obtain the final custom uefi vars by applying the delta onto the base vars - match custom_uefi_json_data { - Some(data) => { - let delta = hyperv_uefi_custom_vars_json::load_delta_from_json(&data)?; - base_vars.apply_delta(delta)? - } - None => base_vars, - } - }; - let vga_firmware = if opt.pcat { Some(openvmm_pcat_locator::find_svga_bios( opt.vga_firmware.as_deref(), diff --git a/vm/devices/firmware/firmware_uefi/src/resolver.rs b/vm/devices/firmware/firmware_uefi/src/resolver.rs new file mode 100644 index 0000000000..a3fb773aa9 --- /dev/null +++ b/vm/devices/firmware/firmware_uefi/src/resolver.rs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Resource resolver for the Hyper-V UEFI helper chipset device. + +use crate::UefiDevice; +use crate::UefiRuntimeDeps; +use async_trait::async_trait; +use chipset_device_resources::GPE0_LINE_SET; +use chipset_device_resources::IRQ_LINE_SET; +use chipset_device_resources::ResolveChipsetDeviceHandleParams; +use chipset_device_resources::ResolvedChipsetDevice; +use chipset_resources::CmosRtcTimeSourceHandleKind; +use firmware_uefi_resources::ResolvedUefiWatchdogPlatform; +use firmware_uefi_resources::UefiCommandSet; +use firmware_uefi_resources::UefiDeviceHandle; +use firmware_uefi_resources::UefiLoggerHandleKind; +use firmware_uefi_resources::UefiNvramStorageHandleKind; +use firmware_uefi_resources::UefiVsmConfigHandleKind; +use firmware_uefi_resources::UefiWatchdogPlatformHandleKind; +use thiserror::Error; +use vm_resource::AsyncResolveResource; +use vm_resource::ResolveError; +use vm_resource::ResourceResolver; +use vm_resource::declare_static_async_resolver; +use vm_resource::kind::ChipsetDeviceHandleKind; + +/// Resolver for the Hyper-V UEFI helper device. +pub struct UefiDeviceResolver; + +declare_static_async_resolver! { + UefiDeviceResolver, + (ChipsetDeviceHandleKind, UefiDeviceHandle), +} + +/// Errors that can occur while resolving a UEFI device handle. +#[derive(Debug, Error)] +pub enum ResolveUefiDeviceError { + /// Failed to resolve the UEFI logger. + #[error("failed to resolve UEFI logger")] + ResolveLogger(#[source] ResolveError), + /// Failed to resolve the UEFI NVRAM storage. + #[error("failed to resolve UEFI NVRAM storage")] + ResolveNvramStorage(#[source] ResolveError), + /// Failed to resolve the UEFI watchdog platform. + #[error("failed to resolve UEFI watchdog platform")] + ResolveWatchdogPlatform(#[source] ResolveError), + /// Failed to resolve the UEFI VSM configuration. + #[error("failed to resolve UEFI VSM configuration")] + ResolveVsmConfig(#[source] ResolveError), + /// Failed to resolve the UEFI time source. + #[error("failed to resolve UEFI time source")] + ResolveTimeSource(#[source] ResolveError), + /// Failed to initialize the UEFI device. + #[error("failed to initialize UEFI device")] + Init(#[from] crate::UefiInitError), +} + +// The ACPI GPE0 line to use for generation ID. This must match the value in +// the DSDT. +const GPE0_LINE_GENERATION_ID: u32 = 0; +// For ARM64, 3 + 32 (SPI range start) = 35, the SYSTEM_SPI_GENCOUNTER vector +// for the GIC. +const GENERATION_ID_IRQ: u32 = 3; + +#[async_trait] +impl AsyncResolveResource for UefiDeviceResolver { + type Output = ResolvedChipsetDevice; + type Error = ResolveUefiDeviceError; + + async fn resolve( + &self, + resolver: &ResourceResolver, + resource: UefiDeviceHandle, + input: ResolveChipsetDeviceHandleParams<'_>, + ) -> Result { + let UefiDeviceHandle { + config, + generation_id_recv, + logger, + nvram_storage, + watchdog_platform, + vsm_config, + time_source, + } = resource; + + let logger = resolver + .resolve::(logger, ()) + .await + .map_err(ResolveUefiDeviceError::ResolveLogger)? + .0; + let nvram_storage = resolver + .resolve::(nvram_storage, ()) + .await + .map_err(ResolveUefiDeviceError::ResolveNvramStorage)? + .0; + let ResolvedUefiWatchdogPlatform { + platform: watchdog_platform, + watchdog_recv, + } = resolver + .resolve::(watchdog_platform, &()) + .await + .map_err(ResolveUefiDeviceError::ResolveWatchdogPlatform)?; + let vsm_config = if let Some(vsm_config) = vsm_config { + Some( + resolver + .resolve::(vsm_config, ()) + .await + .map_err(ResolveUefiDeviceError::ResolveVsmConfig)? + .0, + ) + } else { + None + }; + let time_source = resolver + .resolve::(time_source, ()) + .await + .map_err(ResolveUefiDeviceError::ResolveTimeSource)? + .0; + + let notify_interrupt = match config.command_set { + UefiCommandSet::X64 => { + input + .configure + .new_line(GPE0_LINE_SET, "genid", GPE0_LINE_GENERATION_ID) + } + UefiCommandSet::Aarch64 => { + input + .configure + .new_line(IRQ_LINE_SET, "genid", GENERATION_ID_IRQ) + } + }; + + let gm = input.encrypted_guest_memory.clone(); + let runtime_deps = UefiRuntimeDeps { + gm: gm.clone(), + nvram_storage, + logger, + vmtime: input.vmtime, + watchdog_platform, + watchdog_recv, + generation_id_deps: generation_id::GenerationIdRuntimeDeps { + generation_id_recv, + gm, + notify_interrupt, + }, + vsm_config, + time_source, + }; + + let device = UefiDevice::new(runtime_deps, config, input.is_restoring).await?; + Ok(device.into()) + } +} diff --git a/vm/devices/firmware/firmware_uefi_resources/src/lib.rs b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs new file mode 100644 index 0000000000..e253ac94e7 --- /dev/null +++ b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Resource definitions for the Hyper-V UEFI helper device +//! ([`firmware_uefi`](../firmware_uefi/index.html)). +//! +//! This crate exists so that crates which need to construct a UEFI device +//! handle (e.g., `vm_manifest_builder`) or register platform-specific +//! resolvers (e.g., `openvmm_core`, `underhill_core`) do not need to take a +//! dependency on the full `firmware_uefi` device implementation. + +#![forbid(unsafe_code)] +#![expect(missing_docs)] + +use chipset_resources::CmosRtcTimeSourceHandleKind; +use firmware_uefi_custom_vars::CustomVars; +use inspect::Inspect; +use mesh::MeshPayload; +use mesh_protobuf::Protobuf; +use std::borrow::Cow; +use uefi_nvram_storage::VmmNvramStorage; +use uefi_specs::hyperv::debug_level::DEBUG_ERROR; +use uefi_specs::hyperv::debug_level::DEBUG_FLAG_NAMES; +use uefi_specs::hyperv::debug_level::DEBUG_INFO; +use uefi_specs::hyperv::debug_level::DEBUG_WARN; +use vm_resource::CanResolveTo; +use vm_resource::Resource; +use vm_resource::ResourceId; +use vm_resource::ResourceKind; +use vm_resource::kind::ChipsetDeviceHandleKind; +use watchdog_core::platform::WatchdogPlatform; + +pub mod platform { + //! Platform interfaces required by the UEFI device. + + /// A UEFI event that should be surfaced to the host. + #[derive(Debug)] + pub enum UefiEvent { + BootSuccess(BootInfo), + BootFailure(BootInfo), + NoBootDevice, + } + + /// Information about a boot attempt. + #[derive(Debug)] + pub struct BootInfo { + pub secure_boot_succeeded: bool, + } + + /// Interface to log UEFI events. + pub trait UefiLogger: Send { + fn log_event(&self, event: UefiEvent); + } + + /// Callbacks that enable nvram services to revoke VSM on + /// `ExitBootServices` if requested by the guest. + pub trait VsmConfig: Send { + fn revoke_guest_vsm(&self); + } +} + +/// The UEFI command set understood by the device. +#[derive(Debug, Inspect, PartialEq, Clone, Protobuf)] +pub enum UefiCommandSet { + X64, + Aarch64, +} + +/// Log level configuration - encapsulates a `u32` mask where [`u32::MAX`] means +/// "log everything". +#[derive(Debug, Clone, Copy, PartialEq, Eq, Protobuf)] +#[mesh(transparent)] +pub struct LogLevel(u32); + +impl LogLevel { + /// Create default log level configuration (ERROR and WARN only) + pub const fn make_default() -> Self { + Self(DEBUG_ERROR | DEBUG_WARN) + } + + /// Create info log level configuration (ERROR, WARN, and INFO) + pub const fn make_info() -> Self { + Self(DEBUG_ERROR | DEBUG_WARN | DEBUG_INFO) + } + + /// Create full log level configuration (all levels) + pub const fn make_full() -> Self { + Self(u32::MAX) + } + + /// Checks if a raw debug level should be logged based on this log level + /// configuration. + pub fn should_log(self, raw_debug_level: u32) -> bool { + if self.0 == u32::MAX { + true + } else { + (raw_debug_level & self.0) != 0 + } + } + + /// Returns the raw u32 mask. + pub fn as_u32(self) -> u32 { + self.0 + } +} + +impl Default for LogLevel { + fn default() -> Self { + Self::make_default() + } +} + +impl Inspect for LogLevel { + fn inspect(&self, req: inspect::Request<'_>) { + let human_readable = debug_level_to_string(self.0); + req.respond() + .field("raw_value", self.0) + .field("debug_levels", human_readable.as_ref()); + } +} + +/// Converts a debug level mask to a human-readable string. +pub fn debug_level_to_string(debug_level: u32) -> Cow<'static, str> { + if debug_level.count_ones() == 1 { + if let Some(&(_, name)) = DEBUG_FLAG_NAMES + .iter() + .find(|&&(flag, _)| flag == debug_level) + { + return Cow::Borrowed(name); + } + } + + let flags: Vec<&str> = DEBUG_FLAG_NAMES + .iter() + .filter(|&&(flag, _)| debug_level & flag != 0) + .map(|&(_, name)| name) + .collect(); + + if flags.is_empty() { + Cow::Borrowed("UNKNOWN") + } else { + Cow::Owned(flags.join("+")) + } +} + +/// Static configuration for the UEFI device. +#[derive(Debug, Clone, Protobuf)] +pub struct UefiConfig { + pub custom_uefi_vars: CustomVars, + pub secure_boot: bool, + pub initial_generation_id: [u8; 16], + pub use_mmio: bool, + pub command_set: UefiCommandSet, + pub diagnostics_log_level: LogLevel, +} + +/// Resource kind for the platform-provided UEFI logger. +pub enum UefiLoggerHandleKind {} + +impl ResourceKind for UefiLoggerHandleKind { + const NAME: &'static str = "uefi_logger"; +} + +/// Resolved UEFI logger. +pub struct ResolvedUefiLogger(pub Box); + +impl CanResolveTo for UefiLoggerHandleKind { + type Input<'a> = (); +} + +/// Resource kind for the platform-provided UEFI NVRAM storage. +pub enum UefiNvramStorageHandleKind {} + +impl ResourceKind for UefiNvramStorageHandleKind { + const NAME: &'static str = "uefi_nvram_storage"; +} + +/// Resolved UEFI NVRAM storage. +pub struct ResolvedUefiNvramStorage(pub Box); + +impl CanResolveTo for UefiNvramStorageHandleKind { + type Input<'a> = (); +} + +/// Resource kind for the UEFI watchdog platform implementation. +pub enum UefiWatchdogPlatformHandleKind {} + +impl ResourceKind for UefiWatchdogPlatformHandleKind { + const NAME: &'static str = "uefi_watchdog_platform"; +} + +/// Resolved UEFI watchdog platform, including the receiver used by the device +/// to wake up on watchdog timeout notifications. +pub struct ResolvedUefiWatchdogPlatform { + pub platform: Box, + pub watchdog_recv: mesh::Receiver<()>, +} + +impl CanResolveTo for UefiWatchdogPlatformHandleKind { + type Input<'a> = &'a (); +} + +/// Resource kind for the platform VSM configuration callbacks. +pub enum UefiVsmConfigHandleKind {} + +impl ResourceKind for UefiVsmConfigHandleKind { + const NAME: &'static str = "uefi_vsm_config"; +} + +/// Resolved VSM configuration callbacks. +pub struct ResolvedUefiVsmConfig(pub Box); + +impl CanResolveTo for UefiVsmConfigHandleKind { + type Input<'a> = (); +} + +/// A handle to the Hyper-V UEFI helper chipset device. +#[derive(MeshPayload)] +pub struct UefiDeviceHandle { + /// Static configuration data. + pub config: UefiConfig, + /// Channel receiver for updated generation ID values. + pub generation_id_recv: mesh::Receiver<[u8; 16]>, + /// Platform-provided UEFI event logger. + pub logger: Resource, + /// Platform-provided UEFI NVRAM backing storage. + pub nvram_storage: Resource, + /// Platform-provided UEFI watchdog hooks (NMI on x64, halt on aarch64, + /// etc.). + pub watchdog_platform: Resource, + /// Optional platform-provided VSM revocation callbacks. Only used by + /// platforms that support guest VSM. + pub vsm_config: Option>, + /// Real-time clock time source used for UEFI time services. + pub time_source: Resource, +} + +impl ResourceId for UefiDeviceHandle { + const ID: &'static str = "hyperv_firmware_uefi"; +} diff --git a/vmm_core/vm_manifest_builder/src/lib.rs b/vmm_core/vm_manifest_builder/src/lib.rs index ddfaa525f1..dd8830074f 100644 --- a/vmm_core/vm_manifest_builder/src/lib.rs +++ b/vmm_core/vm_manifest_builder/src/lib.rs @@ -35,6 +35,8 @@ use chipset_resources::pm::DEFAULT_PM_PIO_BASE; use chipset_resources::pm::HyperVPowerManagementDeviceHandle; use chipset_resources::pm::PIIX4_PM_BDF; use chipset_resources::pm::Piix4PowerManagementDeviceHandle; +use firmware_uefi_resources::UefiConfig; +use firmware_uefi_resources::UefiDeviceHandle; use input_core::MultiplexedInputHandle; use missing_dev_resources::MissingDevHandle; use serial_16550_resources::Serial16550DeviceHandle; @@ -67,9 +69,22 @@ pub struct VmManifestBuilder { guest_watchdog: bool, psp: bool, platform_pm_timer_assist: bool, + uefi: Option, debugcon: Option<(Resource, u16)>, } +/// Configuration for the Hyper-V UEFI helper device. +pub struct UefiManifest { + /// Static configuration for the UEFI device. + pub config: UefiConfig, + /// Channel receiver for guest generation ID updates. + pub generation_id_recv: mesh::Receiver<[u8; 16]>, + /// Whether to wire up the platform VSM configuration resource. + pub vsm_config: bool, + /// Time source resource for UEFI time services. + pub time_source: Resource, +} + /// The VM's base chipset type, which determines the set of core devices (such /// as timers, interrupt controllers, and buses) that are present in the VM. pub enum BaseChipsetType { @@ -144,6 +159,7 @@ impl VmManifestBuilder { guest_watchdog: false, psp: false, platform_pm_timer_assist: false, + uefi: None, debugcon: None, } } @@ -242,6 +258,21 @@ impl VmManifestBuilder { self } + /// Enable the Hyper-V UEFI helper device. + /// + /// All platform-specific dependencies (logger, NVRAM storage, watchdog + /// platform, optional VSM config, time source) are resolved via + /// [`vm_resource::PlatformResource`] resolvers that the caller must + /// register with the resource resolver. + /// + /// Only supported by [`BaseChipsetType::HypervGen2Uefi`]. Panics + /// otherwise. + pub fn with_uefi(mut self, uefi: UefiManifest) -> Self { + assert!(matches!(self.ty, BaseChipsetType::HypervGen2Uefi)); + self.uefi = Some(uefi); + self + } + /// Build the VM manifest. pub fn build(self) -> Result { let mut result = VmChipsetResult { @@ -285,7 +316,6 @@ impl VmManifestBuilder { with_generic_pci_bus: false, with_generic_psp: false, with_hyperv_firmware_pcat: true, - with_hyperv_firmware_uefi: false, with_hyperv_framebuffer: !self.proxy_vga, with_hyperv_ide: true, with_hyperv_vga: !self.proxy_vga, @@ -314,7 +344,6 @@ impl VmManifestBuilder { with_generic_pci_bus: false, with_generic_psp: self.psp, with_hyperv_firmware_pcat: false, - with_hyperv_firmware_uefi: false, with_hyperv_framebuffer: self.framebuffer, with_hyperv_ide: false, with_hyperv_vga: false, @@ -358,7 +387,6 @@ impl VmManifestBuilder { with_generic_pci_bus: false, with_generic_psp: self.psp, with_hyperv_firmware_pcat: false, - with_hyperv_firmware_uefi: matches!(self.ty, BaseChipsetType::HypervGen2Uefi), with_hyperv_framebuffer: self.framebuffer, with_hyperv_ide: false, with_hyperv_vga: false, @@ -389,6 +417,12 @@ impl VmManifestBuilder { if self.guest_watchdog { result.attach_guest_watchdog(); } + if matches!(self.ty, BaseChipsetType::HypervGen2Uefi) { + result.attach_uefi( + self.uefi + .expect("must have called .with_uefi to enable uefi"), + ); + } } BaseChipsetType::HclHost => { result.chipset = BaseChipsetManifest { @@ -566,6 +600,29 @@ impl VmChipsetResult { self } + fn attach_uefi(&mut self, uefi: UefiManifest) -> &mut Self { + let UefiManifest { + config, + generation_id_recv, + vsm_config, + time_source, + } = uefi; + self.chipset_devices.push(ChipsetDeviceHandle { + name: "uefi".to_owned(), + resource: UefiDeviceHandle { + config, + generation_id_recv, + logger: PlatformResource.into_resource(), + nvram_storage: PlatformResource.into_resource(), + watchdog_platform: PlatformResource.into_resource(), + vsm_config: vsm_config.then(|| PlatformResource.into_resource()), + time_source, + } + .into_resource(), + }); + self + } + fn maybe_attach_arch_serial( &mut self, arch: MachineArch, From a5fe9d7da92c40b37116e5b888c8012a6309fadb Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 16:55:36 -0400 Subject: [PATCH 4/9] bring back livedump and fix build --- openhcl/underhill_core/src/emuplat/uefi.rs | 2 ++ openhcl/underhill_core/src/worker.rs | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openhcl/underhill_core/src/emuplat/uefi.rs b/openhcl/underhill_core/src/emuplat/uefi.rs index c5f0c604dd..8101a0ddd2 100644 --- a/openhcl/underhill_core/src/emuplat/uefi.rs +++ b/openhcl/underhill_core/src/emuplat/uefi.rs @@ -195,6 +195,7 @@ struct UefiWatchdogTimeoutNmi { #[async_trait] impl WatchdogCallback for UefiWatchdogTimeoutNmi { async fn on_timeout(&mut self) { + crate::livedump::livedump().await; use virt::Partition; self.partition.request_msi( hvdef::Vtl::Vtl0, @@ -214,6 +215,7 @@ struct UefiWatchdogTimeoutReset { #[async_trait] impl WatchdogCallback for UefiWatchdogTimeoutReset { async fn on_timeout(&mut self) { + crate::livedump::livedump().await; use vmm_core_defs::HaltReason; self.halt_vps.halt(HaltReason::Reset); self.watchdog_send.send(()); diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index 24a9580b9f..ca1e920ba1 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -24,7 +24,6 @@ use crate::dispatch::vtl2_settings_worker::disk_from_disk_type; use crate::dispatch::vtl2_settings_worker::wait_for_mana; use crate::emuplat::EmuplatServicing; use crate::emuplat::cmos_rtc_time_source::UnderhillCmosRtcTimeSourceResolver; -use crate::emuplat::firmware::UnderhillLogger; use crate::emuplat::framebuffer::FramebufferRemoteControl; use crate::emuplat::i440bx_host_pci_bridge::ArcMutexGetBackedAdjustGpaRange; use crate::emuplat::i440bx_host_pci_bridge::GetBackedAdjustGpaRange; @@ -2594,7 +2593,7 @@ async fn new_underhill_vm( deps_hyperv_firmware_pcat = Some(dev::HyperVFirmwarePcat { config, - logger: Box::new(UnderhillLogger { + logger: Box::new(crate::emuplat::firmware::UnderhillLogger { get: get_client.clone(), }), generation_id_recv: get_client From c761efaccb25e464cf45161ff90c3fcd9c7780b8 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 17:41:41 -0400 Subject: [PATCH 5/9] fix build again and delete generation_id_recv, it was always none --- openvmm/openvmm_core/src/emuplat/uefi.rs | 2 +- openvmm/openvmm_core/src/worker/dispatch.rs | 15 ++++----------- openvmm/openvmm_defs/src/config.rs | 1 - openvmm/openvmm_entry/src/lib.rs | 4 +--- openvmm/openvmm_entry/src/ttrpc/mod.rs | 1 - petri/src/vm/openvmm/construct.rs | 1 - 6 files changed, 6 insertions(+), 18 deletions(-) diff --git a/openvmm/openvmm_core/src/emuplat/uefi.rs b/openvmm/openvmm_core/src/emuplat/uefi.rs index c2330d3276..855fd1f500 100644 --- a/openvmm/openvmm_core/src/emuplat/uefi.rs +++ b/openvmm/openvmm_core/src/emuplat/uefi.rs @@ -146,7 +146,7 @@ struct UefiWatchdogTimeoutReset { #[async_trait] impl WatchdogCallback for UefiWatchdogTimeoutReset { async fn on_timeout(&mut self) { - self.halt_vps.halt(vmcore::halt::HaltReason::Reset); + self.halt_vps.halt(vmm_core_defs::HaltReason::Reset); self.watchdog_send.send(()); } } diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index c457cc1e9b..f47061e792 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -201,7 +201,6 @@ impl Manifest { pci_chipset_devices: config.pci_chipset_devices, chipset_capabilities: config.chipset_capabilities, layout: config.layout, - generation_id_recv: config.generation_id_recv, rtc_delta_milliseconds: config.rtc_delta_milliseconds, automatic_guest_reset: config.automatic_guest_reset, efi_diagnostics_log_level: match config.efi_diagnostics_log_level { @@ -251,7 +250,6 @@ pub struct Manifest { pci_chipset_devices: Vec, chipset_capabilities: VmChipsetCapabilities, layout: vmm_core_defs::LayoutConfig, - generation_id_recv: Option>, rtc_delta_milliseconds: i64, automatic_guest_reset: bool, efi_diagnostics_log_level: LogLevel, @@ -1195,12 +1193,6 @@ impl InitializedVm { partition.clone().ioapic_routing(), )); - let generation_id_recv = cfg.generation_id_recv.unwrap_or_else(|| mesh::channel().1); - - let logger = Box::new(emuplat::firmware::MeshLogger::new( - cfg.firmware_event_send.clone(), - )); - let mapper = memory_manager.device_memory_mapper(); #[cfg_attr(not(guest_arch = "x86_64"), expect(unused_mut))] @@ -1232,8 +1224,10 @@ impl InitializedVm { // TODO: move mtrr replay to a resource. let halt_vps = halt_vps.clone(); deps_hyperv_firmware_pcat = Some(dev::HyperVFirmwarePcat { - logger, - generation_id_recv, + logger: Box::new(emuplat::firmware::MeshLogger::new( + cfg.firmware_event_send.clone(), + )), + generation_id_recv: mesh::channel().1, rom: Some(Box::new(rom)), replay_mtrrs: Box::new(move || halt_vps.replay_mtrrs()), config: { @@ -3336,7 +3330,6 @@ impl LoadedVm { chipset_high_mmio_size: 0, vtl2_chipset_mmio_size: 0, }, // TODO - generation_id_recv: None, // TODO rtc_delta_milliseconds: 0, // TODO automatic_guest_reset: self.inner.automatic_guest_reset, efi_diagnostics_log_level: Default::default(), diff --git a/openvmm/openvmm_defs/src/config.rs b/openvmm/openvmm_defs/src/config.rs index 1ccf871307..c3953879c2 100644 --- a/openvmm/openvmm_defs/src/config.rs +++ b/openvmm/openvmm_defs/src/config.rs @@ -58,7 +58,6 @@ pub struct Config { /// Memory layout sizing for the layout engine. Determines chipset MMIO /// range sizes; addresses are allocated dynamically by the resolver. pub layout: vmm_core_defs::LayoutConfig, - pub generation_id_recv: Option>, // This is used for testing. TODO: resourcify, and also store this in VMGS. pub rtc_delta_milliseconds: i64, /// allow the guest to reset without notifying the client diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index 08da3e6545..e4d996c355 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -1000,7 +1000,6 @@ async fn vm_config_from_command_line( }; let mut initial_generation_id = [0; 16]; getrandom::fill(&mut initial_generation_id).expect("rng failure"); - let (_send, generation_id_recv) = mesh::channel(); chipset = chipset.with_uefi(UefiManifest { config: UefiConfig { custom_uefi_vars: custom_uefi_vars.clone(), @@ -1013,7 +1012,7 @@ async fn vm_config_from_command_line( }, diagnostics_log_level: log_level, }, - generation_id_recv, + generation_id_recv: mesh::channel().1, vsm_config: false, time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { delta_milliseconds: 0, @@ -1755,7 +1754,6 @@ async fn vm_config_from_command_line( custom_uefi_vars, firmware_event_send: None, debugger_rpc: None, - generation_id_recv: None, rtc_delta_milliseconds: 0, automatic_guest_reset: !opt.halt_on_reset, efi_diagnostics_log_level: { diff --git a/openvmm/openvmm_entry/src/ttrpc/mod.rs b/openvmm/openvmm_entry/src/ttrpc/mod.rs index 8fc1eb398e..9718321cb7 100644 --- a/openvmm/openvmm_entry/src/ttrpc/mod.rs +++ b/openvmm/openvmm_entry/src/ttrpc/mod.rs @@ -613,7 +613,6 @@ impl VmService { pci_chipset_devices: chipset.pci_chipset_devices, chipset_capabilities: chipset.capabilities, layout: layout_config, - generation_id_recv: None, rtc_delta_milliseconds: 0, automatic_guest_reset: true, efi_diagnostics_log_level: Default::default(), diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 0a32551048..57f4fd2b55 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -551,7 +551,6 @@ impl PetriVmConfigOpenVmm { #[cfg(windows)] vpci_resources: vec![], debugger_rpc: None, - generation_id_recv: None, rtc_delta_milliseconds: 0, efi_diagnostics_log_level: match firmware .uefi_config() From 34b53971e4ba76269ca0d67b6cf0823c8b924dbf Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 17:50:08 -0400 Subject: [PATCH 6/9] move comment --- vm/devices/firmware/firmware_uefi/src/lib.rs | 10 ---------- vm/devices/firmware/firmware_uefi_resources/src/lib.rs | 9 +++++++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index c87569be69..61ddde4cc7 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -36,16 +36,6 @@ //! e.g: there's no reason for, say, UEFI generation ID services to directly //! share state with the UEFI watchdog service, or the event log service. As //! such, each is modeled as a separate struct + impl. -//! -//! ### `pub mod platform` -//! -//! A centralized place to expose various service-specific interface traits that -//! must be implemented by the "platform" hosting the UEFI device. -//! -//! This layer of abstraction allows the re-using the same UEFI emulator between -//! multiple VMMs (OpenVMM, Underhill, etc...), without tying the emulator to any -//! VMM specific infrastructure (via some kind of compile-time feature flag -//! infrastructure). #![expect(missing_docs)] #![forbid(unsafe_code)] diff --git a/vm/devices/firmware/firmware_uefi_resources/src/lib.rs b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs index e253ac94e7..0bf7f4f8ee 100644 --- a/vm/devices/firmware/firmware_uefi_resources/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs @@ -30,9 +30,14 @@ use vm_resource::ResourceKind; use vm_resource::kind::ChipsetDeviceHandleKind; use watchdog_core::platform::WatchdogPlatform; +/// A centralized place to expose various service-specific interface traits that +/// must be implemented by the "platform" hosting the UEFI device. +/// +/// This layer of abstraction allows the re-using the same UEFI emulator between +/// multiple VMMs (OpenVMM, Underhill, etc...), without tying the emulator to any +/// VMM specific infrastructure (via some kind of compile-time feature flag +/// infrastructure). pub mod platform { - //! Platform interfaces required by the UEFI device. - /// A UEFI event that should be surfaced to the host. #[derive(Debug)] pub enum UefiEvent { From a3395b606b662aa49e727eab446318c9ef5fcf6e Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 19:18:18 -0400 Subject: [PATCH 7/9] fix petri construction --- Cargo.lock | 1 + petri/Cargo.toml | 1 + petri/src/vm/openvmm/construct.rs | 54 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0118b52e92..34da10c0fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6083,6 +6083,7 @@ dependencies = [ "disk_vhd1", "disk_vhdmp", "fatfs", + "firmware_uefi_resources", "flate2", "framebuffer", "fs-err", diff --git a/petri/Cargo.toml b/petri/Cargo.toml index 14b599a273..7dd75431c7 100644 --- a/petri/Cargo.toml +++ b/petri/Cargo.toml @@ -17,6 +17,7 @@ petri_artifacts_vmm_test.workspace = true chipset_device_worker_defs.workspace = true chipset_resources.workspace = true diag_client.workspace = true +firmware_uefi_resources.workspace = true disk_vhd1.workspace = true openvmm_defs.workspace = true openvmm_helpers.workspace = true diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 57f4fd2b55..b5d8de99fd 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -349,6 +349,60 @@ impl PetriVmConfigOpenVmm { let mut vsock_listener = Some(vsock_listener); let vsock_path_string = vsock_path.to_string_lossy(); + // Configure the UEFI helper device on the chipset for Firmware::Uefi. + // OpenhclUefi uses BaseChipsetType::HclHost, so it does not need this. + if matches!(firmware, Firmware::Uefi { .. }) { + let uefi_cfg = firmware.uefi_config(); + let custom_uefi_vars = + uefi_cfg.map_or_else(Default::default, |c| match (arch, c.secure_boot_template) { + (MachineArch::X86_64, Some(SecureBootTemplate::MicrosoftWindows)) => { + hyperv_secure_boot_templates::x64::microsoft_windows() + } + ( + MachineArch::X86_64, + Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority), + ) => hyperv_secure_boot_templates::x64::microsoft_uefi_ca(), + (MachineArch::Aarch64, Some(SecureBootTemplate::MicrosoftWindows)) => { + hyperv_secure_boot_templates::aarch64::microsoft_windows() + } + ( + MachineArch::Aarch64, + Some(SecureBootTemplate::MicrosoftUefiCertificateAuthority), + ) => hyperv_secure_boot_templates::aarch64::microsoft_uefi_ca(), + (_, None) => Default::default(), + }); + let secure_boot = uefi_cfg.is_some_and(|c| c.secure_boot_enabled); + let log_level = match uefi_cfg + .map(|c| c.efi_diagnostics_log_level) + .unwrap_or_default() + { + EfiDiagnosticsLogLevel::Default => { + firmware_uefi_resources::LogLevel::make_default() + } + EfiDiagnosticsLogLevel::Info => firmware_uefi_resources::LogLevel::make_info(), + EfiDiagnosticsLogLevel::Full => firmware_uefi_resources::LogLevel::make_full(), + }; + chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest { + config: firmware_uefi_resources::UefiConfig { + custom_uefi_vars, + secure_boot, + initial_generation_id: [0; 16], + use_mmio: !matches!(arch, MachineArch::X86_64), + command_set: match arch { + MachineArch::X86_64 => firmware_uefi_resources::UefiCommandSet::X64, + MachineArch::Aarch64 => firmware_uefi_resources::UefiCommandSet::Aarch64, + }, + diagnostics_log_level: log_level, + }, + generation_id_recv: mesh::channel().1, + vsm_config: false, + time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { + delta_milliseconds: 0, + } + .into_resource(), + }); + } + let layout_config = chipset.layout_config(); let chipset = chipset .build() From 481a0c53664ab0f9c77bad6f45b8f2a9ca42869a Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 20:27:03 -0400 Subject: [PATCH 8/9] dedup uefi --- Cargo.lock | 2 ++ openvmm/openvmm_entry/src/lib.rs | 31 ++++------------- petri/src/vm/openvmm/construct.rs | 26 +++++---------- vmm_core/vm_manifest_builder/Cargo.toml | 2 ++ vmm_core/vm_manifest_builder/src/lib.rs | 44 +++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34da10c0fe..77d0111931 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9590,7 +9590,9 @@ name = "vm_manifest_builder" version = "0.0.0" dependencies = [ "chipset_resources", + "firmware_uefi_custom_vars", "firmware_uefi_resources", + "getrandom 0.4.2", "input_core", "mesh", "missing_dev_resources", diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index e4d996c355..31a44105f8 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -987,10 +987,6 @@ async fn vm_config_from_command_line( }; if opt.uefi { - use firmware_uefi_resources::UefiCommandSet; - use firmware_uefi_resources::UefiConfig; - use vm_manifest_builder::UefiManifest; - let log_level = match efi_diagnostics_log_level { EfiDiagnosticsLogLevelType::Default => { firmware_uefi_resources::LogLevel::make_default() @@ -998,27 +994,12 @@ async fn vm_config_from_command_line( EfiDiagnosticsLogLevelType::Info => firmware_uefi_resources::LogLevel::make_info(), EfiDiagnosticsLogLevelType::Full => firmware_uefi_resources::LogLevel::make_full(), }; - let mut initial_generation_id = [0; 16]; - getrandom::fill(&mut initial_generation_id).expect("rng failure"); - chipset = chipset.with_uefi(UefiManifest { - config: UefiConfig { - custom_uefi_vars: custom_uefi_vars.clone(), - secure_boot: opt.secure_boot, - initial_generation_id, - use_mmio: !matches!(arch, MachineArch::X86_64), - command_set: match arch { - MachineArch::X86_64 => UefiCommandSet::X64, - MachineArch::Aarch64 => UefiCommandSet::Aarch64, - }, - diagnostics_log_level: log_level, - }, - generation_id_recv: mesh::channel().1, - vsm_config: false, - time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { - delta_milliseconds: 0, - } - .into_resource(), - }); + chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new( + arch, + custom_uefi_vars.clone(), + opt.secure_boot, + log_level, + )); } // TODO: load from VMGS file if it exists diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index b5d8de99fd..8790da198c 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -382,25 +382,15 @@ impl PetriVmConfigOpenVmm { EfiDiagnosticsLogLevel::Info => firmware_uefi_resources::LogLevel::make_info(), EfiDiagnosticsLogLevel::Full => firmware_uefi_resources::LogLevel::make_full(), }; - chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest { - config: firmware_uefi_resources::UefiConfig { - custom_uefi_vars, - secure_boot, - initial_generation_id: [0; 16], - use_mmio: !matches!(arch, MachineArch::X86_64), - command_set: match arch { - MachineArch::X86_64 => firmware_uefi_resources::UefiCommandSet::X64, - MachineArch::Aarch64 => firmware_uefi_resources::UefiCommandSet::Aarch64, - }, - diagnostics_log_level: log_level, + chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new( + match arch { + MachineArch::X86_64 => vm_manifest_builder::MachineArch::X86_64, + MachineArch::Aarch64 => vm_manifest_builder::MachineArch::Aarch64, }, - generation_id_recv: mesh::channel().1, - vsm_config: false, - time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { - delta_milliseconds: 0, - } - .into_resource(), - }); + custom_uefi_vars, + secure_boot, + log_level, + )); } let layout_config = chipset.layout_config(); diff --git a/vmm_core/vm_manifest_builder/Cargo.toml b/vmm_core/vm_manifest_builder/Cargo.toml index 561e3418fb..af08ec0c41 100644 --- a/vmm_core/vm_manifest_builder/Cargo.toml +++ b/vmm_core/vm_manifest_builder/Cargo.toml @@ -8,7 +8,9 @@ rust-version.workspace = true [dependencies] chipset_resources.workspace = true +firmware_uefi_custom_vars.workspace = true firmware_uefi_resources.workspace = true +getrandom.workspace = true input_core.workspace = true missing_dev_resources.workspace = true serial_16550_resources.workspace = true diff --git a/vmm_core/vm_manifest_builder/src/lib.rs b/vmm_core/vm_manifest_builder/src/lib.rs index dd8830074f..0793902ac8 100644 --- a/vmm_core/vm_manifest_builder/src/lib.rs +++ b/vmm_core/vm_manifest_builder/src/lib.rs @@ -35,6 +35,9 @@ use chipset_resources::pm::DEFAULT_PM_PIO_BASE; use chipset_resources::pm::HyperVPowerManagementDeviceHandle; use chipset_resources::pm::PIIX4_PM_BDF; use chipset_resources::pm::Piix4PowerManagementDeviceHandle; +use firmware_uefi_custom_vars::CustomVars; +use firmware_uefi_resources::LogLevel; +use firmware_uefi_resources::UefiCommandSet; use firmware_uefi_resources::UefiConfig; use firmware_uefi_resources::UefiDeviceHandle; use input_core::MultiplexedInputHandle; @@ -85,6 +88,47 @@ pub struct UefiManifest { pub time_source: Resource, } +impl UefiManifest { + /// Construct a [`UefiManifest`] with sensible defaults for the given + /// architecture: + /// + /// - `command_set` and `use_mmio` are derived from `arch`. + /// - `initial_generation_id` is randomized. + /// - `generation_id_recv` is a disconnected receiver (no host updates). + /// - `vsm_config` is disabled. + /// - `time_source` is a [`SystemTimeClockHandle`] with no delta. + /// + /// [`SystemTimeClockHandle`]: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle + pub fn new( + arch: MachineArch, + custom_uefi_vars: CustomVars, + secure_boot: bool, + diagnostics_log_level: LogLevel, + ) -> Self { + let mut initial_generation_id = [0; 16]; + getrandom::fill(&mut initial_generation_id).expect("rng failure"); + Self { + config: UefiConfig { + custom_uefi_vars, + secure_boot, + initial_generation_id, + use_mmio: !matches!(arch, MachineArch::X86_64), + command_set: match arch { + MachineArch::X86_64 => UefiCommandSet::X64, + MachineArch::Aarch64 => UefiCommandSet::Aarch64, + }, + diagnostics_log_level, + }, + generation_id_recv: mesh::channel().1, + vsm_config: false, + time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { + delta_milliseconds: 0, + } + .into_resource(), + } + } +} + /// The VM's base chipset type, which determines the set of core devices (such /// as timers, interrupt controllers, and buses) that are present in the VM. pub enum BaseChipsetType { From 6e625bb2d741d94673e13a9025c5d841066586e8 Mon Sep 17 00:00:00 2001 From: Steven Malis Date: Thu, 21 May 2026 21:30:39 -0400 Subject: [PATCH 9/9] split storage apart, different handle kind --- openhcl/underhill_core/src/emuplat/uefi.rs | 53 ++++++++++++------- openhcl/underhill_core/src/worker.rs | 9 +++- openvmm/openvmm_core/src/emuplat/uefi.rs | 49 +++++++++++------ openvmm/openvmm_core/src/worker/dispatch.rs | 7 +-- openvmm/openvmm_entry/src/lib.rs | 6 +++ petri/src/vm/openvmm/construct.rs | 1 + .../firmware_uefi_resources/src/lib.rs | 19 +++++++ vmm_core/vm_manifest_builder/src/lib.rs | 8 ++- 8 files changed, 113 insertions(+), 39 deletions(-) diff --git a/openhcl/underhill_core/src/emuplat/uefi.rs b/openhcl/underhill_core/src/emuplat/uefi.rs index 8101a0ddd2..124afa2459 100644 --- a/openhcl/underhill_core/src/emuplat/uefi.rs +++ b/openhcl/underhill_core/src/emuplat/uefi.rs @@ -9,6 +9,7 @@ use crate::emuplat::non_volatile_store::VmgsBrokerNonVolatileStore; use crate::emuplat::watchdog::UnderhillWatchdogPlatform; use anyhow::Context as _; use async_trait::async_trait; +use firmware_uefi_resources::EphemeralNvramStorageHandle; use firmware_uefi_resources::ResolvedUefiLogger; use firmware_uefi_resources::ResolvedUefiNvramStorage; use firmware_uefi_resources::ResolvedUefiVsmConfig; @@ -17,6 +18,7 @@ use firmware_uefi_resources::UefiLoggerHandleKind; use firmware_uefi_resources::UefiNvramStorageHandleKind; use firmware_uefi_resources::UefiVsmConfigHandleKind; use firmware_uefi_resources::UefiWatchdogPlatformHandleKind; +use firmware_uefi_resources::VmgsNvramStorageHandle; use guest_emulation_transport::GuestEmulationTransportClient; use hcl_compat_uefi_nvram_storage::HclCompatNvram; use hcl_compat_uefi_nvram_storage::HclCompatNvramQuirks; @@ -60,19 +62,18 @@ impl ResolveResource for UnderhillUefiLo } } -/// Resolver that produces UEFI NVRAM storage backed by the host VMGS, or an -/// in-memory fallback. +/// Resolver that produces UEFI NVRAM storage backed by the host VMGS. pub struct UnderhillUefiNvramStorageResolver { - vmgs_client: Option, + vmgs_client: vmgs_broker::VmgsClient, } impl UnderhillUefiNvramStorageResolver { - pub fn new(vmgs_client: Option) -> Self { + pub fn new(vmgs_client: vmgs_broker::VmgsClient) -> Self { Self { vmgs_client } } } -impl ResolveResource +impl ResolveResource for UnderhillUefiNvramStorageResolver { type Output = ResolvedUefiNvramStorage; @@ -80,25 +81,41 @@ impl ResolveResource fn resolve( &self, - _resource: PlatformResource, + _resource: VmgsNvramStorageHandle, _input: (), ) -> Result { - let storage: Box = match &self.vmgs_client { - Some(vmgs) => Box::new(HclCompatNvram::new( - VmgsStorageBackendAdapter( - vmgs.as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) - .context("failed to instantiate UEFI NVRAM store")?, - ), - Some(HclCompatNvramQuirks { - skip_corrupt_vars_with_missing_null_term: true, - }), - )), - None => Box::new(InMemoryNvram::new()), - }; + let storage: Box = Box::new(HclCompatNvram::new( + VmgsStorageBackendAdapter( + self.vmgs_client + .as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) + .context("failed to instantiate UEFI NVRAM store")?, + ), + Some(HclCompatNvramQuirks { + skip_corrupt_vars_with_missing_null_term: true, + }), + )); Ok(ResolvedUefiNvramStorage(storage)) } } +/// Resolver that produces a fresh ephemeral in-memory UEFI NVRAM store. +pub struct EphemeralUefiNvramStorageResolver; + +impl ResolveResource + for EphemeralUefiNvramStorageResolver +{ + type Output = ResolvedUefiNvramStorage; + type Error = std::convert::Infallible; + + fn resolve( + &self, + _resource: EphemeralNvramStorageHandle, + _input: (), + ) -> Result { + Ok(ResolvedUefiNvramStorage(Box::new(InMemoryNvram::new()))) + } +} + /// Resolver that produces a fresh [`UnderhillWatchdogPlatform`] each time it /// is resolved, along with the matching receiver. #[expect(unused)] // One of these will be unused no matter what diff --git a/openhcl/underhill_core/src/worker.rs b/openhcl/underhill_core/src/worker.rs index ca1e920ba1..a60d79b6ae 100644 --- a/openhcl/underhill_core/src/worker.rs +++ b/openhcl/underhill_core/src/worker.rs @@ -2486,7 +2486,13 @@ async fn new_underhill_vm( // Register the platform resolvers used by the resource-model UEFI // device. resolver.add_resolver(UnderhillUefiLoggerResolver::new(get_client.clone())); - resolver.add_resolver(UnderhillUefiNvramStorageResolver::new(vmgs_client.clone())); + let nvram_storage = if let Some(vmgs_client) = vmgs_client.clone() { + resolver.add_resolver(UnderhillUefiNvramStorageResolver::new(vmgs_client)); + firmware_uefi_resources::VmgsNvramStorageHandle.into_resource() + } else { + resolver.add_resolver(EphemeralUefiNvramStorageResolver); + firmware_uefi_resources::EphemeralNvramStorageHandle.into_resource() + }; resolver.add_async_resolver(UnderhillUefiWatchdogPlatformResolver::new( get_client.clone(), partition.clone(), @@ -2504,6 +2510,7 @@ async fn new_underhill_vm( chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest { config, generation_id_recv, + nvram_storage, vsm_config: true, time_source: PlatformResource.into_resource(), }); diff --git a/openvmm/openvmm_core/src/emuplat/uefi.rs b/openvmm/openvmm_core/src/emuplat/uefi.rs index 855fd1f500..051dac9ec7 100644 --- a/openvmm/openvmm_core/src/emuplat/uefi.rs +++ b/openvmm/openvmm_core/src/emuplat/uefi.rs @@ -7,10 +7,12 @@ use crate::partition::HvlitePartition; use crate::vmgs_non_volatile_store::HvLiteVmgsNonVolatileStore; use anyhow::Context as _; use async_trait::async_trait; +use firmware_uefi_resources::EphemeralNvramStorageHandle; use firmware_uefi_resources::ResolvedUefiNvramStorage; use firmware_uefi_resources::ResolvedUefiWatchdogPlatform; use firmware_uefi_resources::UefiNvramStorageHandleKind; use firmware_uefi_resources::UefiWatchdogPlatformHandleKind; +use firmware_uefi_resources::VmgsNvramStorageHandle; use hcl_compat_uefi_nvram_storage::HclCompatNvram; use std::sync::Arc; use uefi_nvram_storage::VmmNvramStorage; @@ -25,19 +27,18 @@ use watchdog_core::platform::BaseWatchdogPlatform; use watchdog_core::platform::WatchdogCallback; use watchdog_core::platform::WatchdogPlatform; -/// Resolver that produces UEFI NVRAM storage backed by the host VMGS file (if -/// one is available) or by an in-memory fallback. +/// Resolver that produces UEFI NVRAM storage backed by the host VMGS file. pub struct VmgsUefiNvramStorageResolver { - vmgs_client: Option, + vmgs_client: vmgs_broker::VmgsClient, } impl VmgsUefiNvramStorageResolver { - pub fn new(vmgs_client: Option) -> Self { + pub fn new(vmgs_client: vmgs_broker::VmgsClient) -> Self { Self { vmgs_client } } } -impl ResolveResource +impl ResolveResource for VmgsUefiNvramStorageResolver { type Output = ResolvedUefiNvramStorage; @@ -45,23 +46,39 @@ impl ResolveResource fn resolve( &self, - _resource: PlatformResource, + _resource: VmgsNvramStorageHandle, _input: (), ) -> Result { - let storage: Box = match &self.vmgs_client { - Some(vmgs) => Box::new(HclCompatNvram::new( - VmgsStorageBackendAdapter( - vmgs.as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) - .context("failed to instantiate UEFI NVRAM store")?, - ), - None, - )), - None => Box::new(InMemoryNvram::new()), - }; + let storage: Box = Box::new(HclCompatNvram::new( + VmgsStorageBackendAdapter( + self.vmgs_client + .as_non_volatile_store(vmgs::FileId::BIOS_NVRAM, true) + .context("failed to instantiate UEFI NVRAM store")?, + ), + None, + )); Ok(ResolvedUefiNvramStorage(storage)) } } +/// Resolver that produces a fresh ephemeral in-memory UEFI NVRAM store. +pub struct EphemeralUefiNvramStorageResolver; + +impl ResolveResource + for EphemeralUefiNvramStorageResolver +{ + type Output = ResolvedUefiNvramStorage; + type Error = std::convert::Infallible; + + fn resolve( + &self, + _resource: EphemeralNvramStorageHandle, + _input: (), + ) -> Result { + Ok(ResolvedUefiNvramStorage(Box::new(InMemoryNvram::new()))) + } +} + /// Resolver that produces a fresh [`BaseWatchdogPlatform`] (and the matching /// receiver) for the UEFI watchdog on each resolution. #[expect(unused)] // One of these will be unused no matter what diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index f47061e792..fb5b09c25d 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -1205,9 +1205,10 @@ impl InitializedVm { resolver.add_resolver(emuplat::firmware::MeshLoggerResolver::new( cfg.firmware_event_send.clone(), )); - resolver.add_resolver(VmgsUefiNvramStorageResolver::new( - vmgs_client_inspect_handle.clone(), - )); + if let Some(vmgs_client) = vmgs_client_inspect_handle.clone() { + resolver.add_resolver(VmgsUefiNvramStorageResolver::new(vmgs_client)); + } + resolver.add_resolver(EphemeralUefiNvramStorageResolver); resolver.add_async_resolver(OpenvmmUefiWatchdogPlatformResolver::new( partition.clone(), halt_vps.clone(), diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index 31a44105f8..25ae5e344e 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -994,11 +994,17 @@ async fn vm_config_from_command_line( EfiDiagnosticsLogLevelType::Info => firmware_uefi_resources::LogLevel::make_info(), EfiDiagnosticsLogLevelType::Full => firmware_uefi_resources::LogLevel::make_full(), }; + let nvram_storage = if opt.vmgs.is_some() { + firmware_uefi_resources::VmgsNvramStorageHandle.into_resource() + } else { + firmware_uefi_resources::EphemeralNvramStorageHandle.into_resource() + }; chipset = chipset.with_uefi(vm_manifest_builder::UefiManifest::new( arch, custom_uefi_vars.clone(), opt.secure_boot, log_level, + nvram_storage, )); } diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index 8790da198c..1efb43eaf4 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -390,6 +390,7 @@ impl PetriVmConfigOpenVmm { custom_uefi_vars, secure_boot, log_level, + firmware_uefi_resources::EphemeralNvramStorageHandle.into_resource(), )); } diff --git a/vm/devices/firmware/firmware_uefi_resources/src/lib.rs b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs index 0bf7f4f8ee..974df91ed0 100644 --- a/vm/devices/firmware/firmware_uefi_resources/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi_resources/src/lib.rs @@ -187,6 +187,25 @@ impl CanResolveTo for UefiNvramStorageHandleKind { type Input<'a> = (); } +/// Handle for VMGS-backed UEFI NVRAM storage. Resolved by the platform's +/// VMGS-backed NVRAM storage resolver, which has access to the host VMGS +/// client. +#[derive(MeshPayload)] +pub struct VmgsNvramStorageHandle; + +impl ResourceId for VmgsNvramStorageHandle { + const ID: &'static str = "uefi_vmgs"; +} + +/// Handle for an ephemeral, in-memory UEFI NVRAM store. Resolves to a fresh +/// [`uefi_nvram_storage::in_memory::InMemoryNvram`] each time. +#[derive(MeshPayload)] +pub struct EphemeralNvramStorageHandle; + +impl ResourceId for EphemeralNvramStorageHandle { + const ID: &'static str = "uefi_ephemeral"; +} + /// Resource kind for the UEFI watchdog platform implementation. pub enum UefiWatchdogPlatformHandleKind {} diff --git a/vmm_core/vm_manifest_builder/src/lib.rs b/vmm_core/vm_manifest_builder/src/lib.rs index 0793902ac8..2b5cd35eaa 100644 --- a/vmm_core/vm_manifest_builder/src/lib.rs +++ b/vmm_core/vm_manifest_builder/src/lib.rs @@ -40,6 +40,7 @@ use firmware_uefi_resources::LogLevel; use firmware_uefi_resources::UefiCommandSet; use firmware_uefi_resources::UefiConfig; use firmware_uefi_resources::UefiDeviceHandle; +use firmware_uefi_resources::UefiNvramStorageHandleKind; use input_core::MultiplexedInputHandle; use missing_dev_resources::MissingDevHandle; use serial_16550_resources::Serial16550DeviceHandle; @@ -82,6 +83,8 @@ pub struct UefiManifest { pub config: UefiConfig, /// Channel receiver for guest generation ID updates. pub generation_id_recv: mesh::Receiver<[u8; 16]>, + /// NVRAM backing storage resource. + pub nvram_storage: Resource, /// Whether to wire up the platform VSM configuration resource. pub vsm_config: bool, /// Time source resource for UEFI time services. @@ -104,6 +107,7 @@ impl UefiManifest { custom_uefi_vars: CustomVars, secure_boot: bool, diagnostics_log_level: LogLevel, + nvram_storage: Resource, ) -> Self { let mut initial_generation_id = [0; 16]; getrandom::fill(&mut initial_generation_id).expect("rng failure"); @@ -120,6 +124,7 @@ impl UefiManifest { diagnostics_log_level, }, generation_id_recv: mesh::channel().1, + nvram_storage, vsm_config: false, time_source: chipset_resources::cmos_rtc_time_source::SystemTimeClockHandle { delta_milliseconds: 0, @@ -648,6 +653,7 @@ impl VmChipsetResult { let UefiManifest { config, generation_id_recv, + nvram_storage, vsm_config, time_source, } = uefi; @@ -657,7 +663,7 @@ impl VmChipsetResult { config, generation_id_recv, logger: PlatformResource.into_resource(), - nvram_storage: PlatformResource.into_resource(), + nvram_storage, watchdog_platform: PlatformResource.into_resource(), vsm_config: vsm_config.then(|| PlatformResource.into_resource()), time_source,