diff --git a/Cargo.lock b/Cargo.lock index 174e48d921..3237343f0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2799,6 +2799,7 @@ dependencies = [ "tracing", "ucs2 0.0.0", "uefi_nvram_storage", + "vmcore", "wchar", "zerocopy 0.8.24", ] @@ -7276,10 +7277,12 @@ dependencies = [ "async-trait", "guid", "inspect", + "mesh_protobuf", "pal_async", "thiserror 2.0.12", "ucs2 0.0.0", "uefi_specs", + "vmcore", "wchar", "zerocopy 0.8.24", ] diff --git a/openhcl/underhill_core/Cargo.toml b/openhcl/underhill_core/Cargo.toml index 06ad516633..8936b56092 100644 --- a/openhcl/underhill_core/Cargo.toml +++ b/openhcl/underhill_core/Cargo.toml @@ -56,7 +56,7 @@ hyperv_ic_resources.workspace = true hyperv_secure_boot_templates.workspace = true hyperv_uefi_custom_vars_json.workspace = true framebuffer.workspace = true -hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect"] } +hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] } get_helpers.workspace = true get_protocol.workspace = true guest_emulation_transport.workspace = true diff --git a/openvmm/hvlite_core/Cargo.toml b/openvmm/hvlite_core/Cargo.toml index e80b283b44..75caf4e562 100644 --- a/openvmm/hvlite_core/Cargo.toml +++ b/openvmm/hvlite_core/Cargo.toml @@ -52,10 +52,10 @@ disk_backend.workspace = true firmware_pcat.workspace = true firmware_uefi_custom_vars.workspace = true firmware_uefi.workspace = true -uefi_nvram_storage.workspace = true +uefi_nvram_storage = { workspace = true, features = ["save_restore"] } framebuffer.workspace = true get_resources.workspace = true -hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect"] } +hcl_compat_uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] } ide.workspace = true floppy.workspace = true input_core.workspace = true diff --git a/vm/devices/firmware/firmware_uefi/Cargo.toml b/vm/devices/firmware/firmware_uefi/Cargo.toml index dca17b1d6d..9be74decd0 100644 --- a/vm/devices/firmware/firmware_uefi/Cargo.toml +++ b/vm/devices/firmware/firmware_uefi/Cargo.toml @@ -22,7 +22,7 @@ fuzzing = [] [dependencies] firmware_uefi_custom_vars.workspace = true -uefi_nvram_storage = { workspace = true, features = ["inspect"] } +uefi_nvram_storage = { workspace = true, features = ["inspect", "save_restore"] } uefi_specs.workspace = true uefi_nvram_specvars.workspace = true generation_id.workspace = true diff --git a/vm/devices/firmware/firmware_uefi/src/lib.rs b/vm/devices/firmware/firmware_uefi/src/lib.rs index 93529c4f31..82273ac8e5 100644 --- a/vm/devices/firmware/firmware_uefi/src/lib.rs +++ b/vm/devices/firmware/firmware_uefi/src/lib.rs @@ -74,7 +74,7 @@ use std::convert::TryInto; use std::ops::RangeInclusive; use std::task::Context; use thiserror::Error; -use uefi_nvram_storage::InspectableNvramStorage; +use uefi_nvram_storage::VmmNvramStorage; use vmcore::device_state::ChangeDeviceState; use vmcore::vmtime::VmTimeSource; use watchdog_core::platform::WatchdogPlatform; @@ -129,7 +129,7 @@ pub struct UefiConfig { /// Various runtime objects used by the UEFI device + underlying services. pub struct UefiRuntimeDeps<'a> { pub gm: GuestMemory, - pub nvram_storage: Box, + pub nvram_storage: Box, pub logger: Box, pub vmtime: &'a VmTimeSource, pub watchdog_platform: Box, 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 a438b3af4a..d6e8b4ee99 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/mod.rs @@ -24,7 +24,7 @@ use inspect::Inspect; use std::borrow::Cow; use std::fmt::Debug; use thiserror::Error; -use uefi_nvram_storage::InspectableNvramStorage; +use uefi_nvram_storage::VmmNvramStorage; use uefi_specs::uefi::common::EfiStatus; use uefi_specs::uefi::nvram::EfiVariableAttributes; use zerocopy::IntoBytes; @@ -67,12 +67,12 @@ pub struct NvramServices { // Sub-emulators #[inspect(flatten)] - services: NvramSpecServices>, + services: NvramSpecServices>, } impl NvramServices { pub async fn new( - nvram_storage: Box, + nvram_storage: Box, custom_vars: CustomVars, secure_boot_enabled: bool, vsm_config: Option>, @@ -622,15 +622,14 @@ mod save_restore { mod state { use crate::service::nvram::NvramSpecServices; use mesh::payload::Protobuf; - use uefi_nvram_storage::InspectableNvramStorage; + use uefi_nvram_storage::VmmNvramStorage; use vmcore::save_restore::SaveRestore; #[derive(Protobuf)] #[mesh(package = "firmware.uefi.nvram")] pub struct SavedState { #[mesh(1)] - pub services: - > as SaveRestore>::SavedState, + pub services: > as SaveRestore>::SavedState, } } diff --git a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs index ac2459c7b2..58503a7c38 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/mod.rs @@ -23,9 +23,9 @@ use ucs2::Ucs2LeSlice; use ucs2::Ucs2ParseError; use uefi_nvram_specvars::signature_list; use uefi_nvram_specvars::signature_list::ParseSignatureLists; -use uefi_nvram_storage::InspectableNvramStorage; use uefi_nvram_storage::NextVariable; use uefi_nvram_storage::NvramStorageError; +use uefi_nvram_storage::VmmNvramStorage; use uefi_specs::uefi::common::EfiStatus; use uefi_specs::uefi::nvram::EfiVariableAttributes; use uefi_specs::uefi::time::EFI_TIME; @@ -224,12 +224,12 @@ impl RuntimeState { /// `NvramError` type provides additional context as to what error occurred in /// OpenVMM (i.e: for logging purposes). #[derive(Debug, Inspect)] -pub struct NvramSpecServices { +pub struct NvramSpecServices { storage: S, runtime_state: RuntimeState, } -impl NvramSpecServices { +impl NvramSpecServices { /// Construct a new NvramServices instance from an existing storage backend. pub fn new(storage: S) -> NvramSpecServices { NvramSpecServices { @@ -1453,6 +1453,8 @@ mod save_restore { mod state { use mesh::payload::Protobuf; + use uefi_nvram_storage::in_memory::InMemoryNvram; + use vmcore::save_restore::SaveRestore; #[derive(Protobuf)] #[mesh(package = "firmware.uefi.nvram.spec")] @@ -1470,10 +1472,12 @@ mod save_restore { pub struct SavedState { #[mesh(1)] pub runtime_state: SavedRuntimeState, + #[mesh(2)] + pub storage: ::SavedState, } } - impl SaveRestore for NvramSpecServices { + impl SaveRestore for NvramSpecServices { type SavedState = state::SavedState; fn save(&mut self) -> Result { @@ -1483,17 +1487,22 @@ mod save_restore { RuntimeState::Boot => state::SavedRuntimeState::Boot, RuntimeState::Runtime => state::SavedRuntimeState::Runtime, }, + storage: self.storage.save()?, }) } fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> { - let state::SavedState { runtime_state } = state; + let state::SavedState { + runtime_state, + storage, + } = state; self.runtime_state = match runtime_state { state::SavedRuntimeState::PreBoot => RuntimeState::PreBoot, state::SavedRuntimeState::Boot => RuntimeState::Boot, state::SavedRuntimeState::Runtime => RuntimeState::Runtime, }; + self.storage.restore(storage)?; Ok(()) } @@ -1526,7 +1535,7 @@ mod test { } #[async_trait::async_trait] - impl NvramServicesTestExt for NvramSpecServices { + impl NvramServicesTestExt for NvramSpecServices { async fn set_test_var(&mut self, name: &[u8], attr: u32, data: &[u8]) -> NvramResult<()> { let vendor = Guid::default(); diff --git a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/nvram_services_ext.rs b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/nvram_services_ext.rs index 9ac393600a..422cbc5528 100644 --- a/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/nvram_services_ext.rs +++ b/vm/devices/firmware/firmware_uefi/src/service/nvram/spec_services/nvram_services_ext.rs @@ -6,7 +6,7 @@ use super::NvramResult; use super::NvramSpecServices; use guid::Guid; use ucs2::Ucs2LeSlice; -use uefi_nvram_storage::InspectableNvramStorage; +use uefi_nvram_storage::VmmNvramStorage; use uefi_specs::uefi::common::EfiStatus; /// Extension trait around `NvramServices` that makes it easier to use the API @@ -56,7 +56,7 @@ pub trait NvramServicesExt { } #[async_trait::async_trait] -impl NvramServicesExt for NvramSpecServices { +impl NvramServicesExt for NvramSpecServices { async fn get_variable( &mut self, vendor: Guid, diff --git a/vm/devices/firmware/hcl_compat_uefi_nvram_storage/Cargo.toml b/vm/devices/firmware/hcl_compat_uefi_nvram_storage/Cargo.toml index 31f3e4814d..38ba42c6a3 100644 --- a/vm/devices/firmware/hcl_compat_uefi_nvram_storage/Cargo.toml +++ b/vm/devices/firmware/hcl_compat_uefi_nvram_storage/Cargo.toml @@ -10,9 +10,11 @@ rust-version.workspace = true default = [] inspect = ["dep:inspect", "uefi_nvram_storage/inspect"] +save_restore = [ "dep:vmcore", "uefi_nvram_storage/save_restore"] [dependencies] uefi_nvram_storage.workspace = true +vmcore = { workspace = true, optional = true } cvm_tracing.workspace = true guid.workspace = true diff --git a/vm/devices/firmware/hcl_compat_uefi_nvram_storage/src/lib.rs b/vm/devices/firmware/hcl_compat_uefi_nvram_storage/src/lib.rs index a4db5ba2b2..ffbe479d96 100644 --- a/vm/devices/firmware/hcl_compat_uefi_nvram_storage/src/lib.rs +++ b/vm/devices/firmware/hcl_compat_uefi_nvram_storage/src/lib.rs @@ -94,6 +94,9 @@ pub struct HclCompatNvram { // reduced allocator pressure #[cfg_attr(feature = "inspect", inspect(skip))] // internal bookkeeping - not worth inspecting nvram_buf: Vec, + + // whether the NVRAM has been loaded, either from storage or saved state + loaded: bool, } /// "Quirks" to take into account when loading/storing nvram blob data. @@ -129,6 +132,8 @@ impl HclCompatNvram { in_memory: in_memory::InMemoryNvram::new(), nvram_buf: Vec::new(), + + loaded: false, } } @@ -146,10 +151,12 @@ impl HclCompatNvram { } async fn lazy_load_from_storage_inner(&mut self) -> Result<(), NvramStorageError> { - if !self.nvram_buf.is_empty() { + if self.loaded { return Ok(()); } + tracing::info!("loading uefi nvram from storage"); + let nvram_buf = self .storage .restore() @@ -288,11 +295,13 @@ impl HclCompatNvram { )); } + self.loaded = true; Ok(()) } /// Dump in-memory nvram to the underlying storage device. async fn flush_storage(&mut self) -> Result<(), NvramStorageError> { + tracing::info!("flushing uefi nvram to storage"); self.nvram_buf.clear(); for in_memory::VariableEntry { @@ -473,6 +482,30 @@ impl NvramStorage for HclCompatNvram { } } +#[cfg(feature = "save_restore")] +mod save_restore { + use super::*; + use vmcore::save_restore::RestoreError; + use vmcore::save_restore::SaveError; + use vmcore::save_restore::SaveRestore; + + impl SaveRestore for HclCompatNvram { + type SavedState = ::SavedState; + + fn save(&mut self) -> Result { + self.in_memory.save() + } + + fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> { + if state.nvram.is_some() { + self.in_memory.restore(state)?; + self.loaded = true; + } + Ok(()) + } + } +} + #[cfg(test)] mod test { use super::storage_backend::StorageBackend; diff --git a/vm/devices/firmware/uefi_nvram_storage/Cargo.toml b/vm/devices/firmware/uefi_nvram_storage/Cargo.toml index e894d3be77..6bfcc45e55 100644 --- a/vm/devices/firmware/uefi_nvram_storage/Cargo.toml +++ b/vm/devices/firmware/uefi_nvram_storage/Cargo.toml @@ -10,12 +10,15 @@ rust-version.workspace = true default = [] inspect = ["dep:inspect", "uefi_specs/inspect"] +save_restore = ["inspect", "dep:mesh_protobuf", "dep:vmcore"] [dependencies] guid.workspace = true inspect = { workspace = true, optional = true } +mesh_protobuf = { workspace = true, optional = true } ucs2.workspace = true uefi_specs.workspace = true +vmcore = { workspace = true, optional = true } async-trait.workspace = true thiserror.workspace = true diff --git a/vm/devices/firmware/uefi_nvram_storage/src/in_memory.rs b/vm/devices/firmware/uefi_nvram_storage/src/in_memory.rs index 9819d2d130..a74b03b884 100644 --- a/vm/devices/firmware/uefi_nvram_storage/src/in_memory.rs +++ b/vm/devices/firmware/uefi_nvram_storage/src/in_memory.rs @@ -185,6 +185,86 @@ impl NvramStorage for InMemoryNvram { } } +#[cfg(feature = "save_restore")] +mod save_restore { + use super::*; + use vmcore::save_restore::RestoreError; + use vmcore::save_restore::SaveError; + use vmcore::save_restore::SaveRestore; + + mod state { + use guid::Guid; + use uefi_specs::uefi::time::EFI_TIME; + use vmcore::save_restore::SavedStateRoot; + + #[derive(mesh_protobuf::Protobuf)] + #[mesh(package = "nvram_entry")] + pub struct NvramEntry { + #[mesh(1)] + pub vendor: Guid, + #[mesh(2)] + pub name: Vec, + #[mesh(3)] + pub data: Vec, + #[mesh(4, encoding = "mesh_protobuf::encoding::ZeroCopyEncoding")] + pub timestamp: EFI_TIME, + #[mesh(5)] + pub attr: u32, + } + + #[derive(mesh_protobuf::Protobuf, SavedStateRoot)] + #[mesh(package = "firmware.in_memory_nvram")] + pub struct SavedState { + #[mesh(1)] + pub nvram: Option>, + } + } + + impl SaveRestore for InMemoryNvram { + type SavedState = state::SavedState; + + fn save(&mut self) -> Result { + Ok(state::SavedState { + nvram: Some( + self.nvram + .iter() + .map(|(k, v)| state::NvramEntry { + vendor: k.vendor, + name: k.name.clone().into_inner(), + data: v.data.clone(), + timestamp: v.timestamp, + attr: v.attr, + }) + .collect(), + ), + }) + } + + fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> { + if let state::SavedState { nvram: Some(nvram) } = state { + self.nvram = nvram + .into_iter() + .map::, _>(|e| { + Ok(( + VariableKey { + vendor: e.vendor, + name: Ucs2LeVec::from_vec_with_nul(e.name) + .map_err(|e| RestoreError::InvalidSavedState(e.into()))?, + }, + Variable { + data: e.data, + timestamp: e.timestamp, + attr: e.attr, + }, + )) + }) + .collect::, _>>()?; + } + Ok(()) + } + } +} + /// A collection of test-implementation helpers that operate on a generic /// implementation of [`NvramStorage`] pub mod impl_agnostic_tests { diff --git a/vm/devices/firmware/uefi_nvram_storage/src/lib.rs b/vm/devices/firmware/uefi_nvram_storage/src/lib.rs index 857d301e85..36926d3020 100644 --- a/vm/devices/firmware/uefi_nvram_storage/src/lib.rs +++ b/vm/devices/firmware/uefi_nvram_storage/src/lib.rs @@ -11,8 +11,8 @@ pub use uefi_specs::uefi::time::EFI_TIME; pub mod in_memory; use guid::Guid; -#[cfg(feature = "inspect")] -pub use inspect_ext::InspectableNvramStorage; +#[cfg(feature = "save_restore")] +pub use save_restore::VmmNvramStorage; use std::fmt::Debug; use thiserror::Error; use ucs2::Ucs2LeSlice; @@ -169,18 +169,26 @@ impl NvramStorage for Box { } } -/// Defines a trait that combines NvramStorage and Inspect -#[cfg(feature = "inspect")] -mod inspect_ext { +/// Defines a trait that combines NvramStorage, Inspect, and SaveRestore +#[cfg(feature = "save_restore")] +mod save_restore { use super::*; use inspect::Inspect; + use vmcore::save_restore::SaveRestore; - /// Extends [`NvramStorage`] with a bound on [`Inspect`] - pub trait InspectableNvramStorage: NvramStorage + Inspect {} - impl InspectableNvramStorage for T {} + type NvramSavedState = ::SavedState; + + pub trait VmmNvramStorage: + NvramStorage + Inspect + SaveRestore + { + } + impl VmmNvramStorage for T where + T: NvramStorage + Inspect + SaveRestore + { + } #[async_trait::async_trait] - impl NvramStorage for Box { + impl NvramStorage for Box { async fn get_variable( &mut self, name: &Ucs2LeSlice, @@ -229,4 +237,19 @@ mod inspect_ext { (**self).next_variable(name_vendor).await } } + + impl SaveRestore for Box> { + type SavedState = NvramSavedState; + + fn save(&mut self) -> Result { + (**self).save() + } + + fn restore( + &mut self, + state: Self::SavedState, + ) -> Result<(), vmcore::save_restore::RestoreError> { + (**self).restore(state) + } + } } diff --git a/vmm_core/vmotherboard/src/base_chipset.rs b/vmm_core/vmotherboard/src/base_chipset.rs index c21214cc91..4d3488d480 100644 --- a/vmm_core/vmotherboard/src/base_chipset.rs +++ b/vmm_core/vmotherboard/src/base_chipset.rs @@ -1318,7 +1318,7 @@ pub mod options { /// Interface to log UEFI BIOS events pub logger: Box, /// Interface for storing/retrieving UEFI NVRAM variables - pub nvram_storage: Box, + 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