From 83a46ab68e0ac5a24b8732a64dff546b4571948b Mon Sep 17 00:00:00 2001 From: Jiong Wang Date: Tue, 9 Jun 2026 18:52:50 -0700 Subject: [PATCH 1/2] openvmm: support launching UEFI from IGVM Add an explicit typed IGVM CLI form for UEFI payloads: --igvm type=uefi,path=/path/to/file.bin This lets OpenVMM distinguish UEFI IGVM payloads from the legacy generic IGVM path, while preserving existing --igvm FILE behavior. UEFI boot requires OpenVMM to provide the firmware config blob that is already used by direct --uefi boot. Reuse that config blob construction for UEFI IGVM launches, write it to CONFIG_BLOB_GPA_BASE, and on x64 pass that GPA in R12 when the IGVM did not already provide R12. Make typed UEFI IGVMs imply the fixed-address/file-address IGVM path and reject incompatible explicit relocation settings. Also add the non-isolated x64 UEFI guest config to the UEFI IGVM manifest. This is an incremental step toward using OpenVMM to launch IGVM-packaged test payloads, which is needed for the CCA OpenHCL bring-up path. --- openvmm/openvmm_core/src/worker/dispatch.rs | 54 +++++++- .../src/worker/vm_loaders/uefi.rs | 116 +++++++++++------- openvmm/openvmm_defs/src/config.rs | 1 + openvmm/openvmm_entry/src/cli_args.rs | 98 ++++++++++++++- openvmm/openvmm_entry/src/lib.rs | 25 +++- vm/loader/manifests/uefi-x64.json | 12 +- 6 files changed, 249 insertions(+), 57 deletions(-) diff --git a/openvmm/openvmm_core/src/worker/dispatch.rs b/openvmm/openvmm_core/src/worker/dispatch.rs index 638f128030..6056443426 100644 --- a/openvmm/openvmm_core/src/worker/dispatch.rs +++ b/openvmm/openvmm_core/src/worker/dispatch.rs @@ -1382,7 +1382,7 @@ impl InitializedVm { #[cfg_attr(not(guest_arch = "x86_64"), expect(unused_mut))] let mut deps_hyperv_firmware_pcat = None; match &cfg.load_mode { - LoadMode::Uefi { .. } => { + LoadMode::Uefi { .. } | LoadMode::Igvm { uefi: true, .. } => { use emuplat::uefi::*; // Register the platform-specific resolvers used by the UEFI // device. @@ -3042,11 +3042,14 @@ impl LoadedVmInner { file: _, ref cmdline, vtl2_base_address, + uefi, com_serial, } => { let madt = acpi_builder.build_madt(); let srat = acpi_builder.build_srat(); let slit = acpi_builder.build_slit(); + let mcfg = (!self.pcie_host_bridges.is_empty()).then(|| acpi_builder.build_mcfg()); + let pptt = cache_topology.is_some().then(|| acpi_builder.build_pptt()); const ENTROPY_SIZE: usize = 64; let mut entropy = [0u8; ENTROPY_SIZE]; getrandom::fill(&mut entropy).unwrap(); @@ -3071,7 +3074,54 @@ impl LoadedVmInner { entropy: Some(&entropy), chipset_mmio: self.chipset_mmio, }; - super::vm_loaders::igvm::load_igvm(params)? + let (mut regs, initial_page_vis) = super::vm_loaders::igvm::load_igvm(params)?; + + // The non-isolated UEFI IGVM file path uses the same fixed UEFI + // config GPA as direct UEFI. Supply the config blob OpenVMM + // normally builds for direct UEFI. + if uefi { + let uefi_config = super::vm_loaders::uefi::build_config_blob( + &self.processor_topology, + &self.mem_layout, + &self.pcie_host_bridges, + &super::vm_loaders::uefi::UefiLoadSettings { + debugging: false, + battery: false, + memory_protections: false, + frontpage: true, + tpm: false, + guest_watchdog: self.chipset_capabilities.with_guest_watchdog, + vpci_boot: false, + serial: com_serial.is_some(), + uefi_console_mode: None, + default_boot_always_attempt: false, + bios_guid: guid::Guid::new_random(), + vmbus: self.vmbus_server.is_some(), + }, + &self.chipset_mmio, + &madt, + &srat, + slit.as_deref(), + mcfg.as_deref(), + pptt.as_deref(), + )?; + self.gm + .write_at(loader::uefi::CONFIG_BLOB_GPA_BASE, &uefi_config.complete()) + .context("failed to patch UEFI config blob for IGVM")?; + + // x64 also receives the GPA in R12 if IGVM omitted it. + #[cfg(guest_arch = "x86_64")] + if !regs + .iter() + .any(|reg| matches!(reg, loader::importer::X86Register::R12(_))) + { + regs.push(loader::importer::X86Register::R12( + loader::uefi::CONFIG_BLOB_GPA_BASE, + )); + } + } + + (regs, initial_page_vis) } #[expect(clippy::allow_attributes)] diff --git a/openvmm/openvmm_core/src/worker/vm_loaders/uefi.rs b/openvmm/openvmm_core/src/worker/vm_loaders/uefi.rs index a2dad64ac2..fe227049fa 100644 --- a/openvmm/openvmm_core/src/worker/vm_loaders/uefi.rs +++ b/openvmm/openvmm_core/src/worker/vm_loaders/uefi.rs @@ -50,50 +50,18 @@ pub struct UefiLoadSettings { pub vmbus: bool, } -/// All inputs needed by [`load_uefi`]. -pub struct LoadUefiParams<'a> { - pub firmware: &'a std::fs::File, - pub gm: &'a GuestMemory, - pub processor_topology: &'a ProcessorTopology, - pub mem_layout: &'a MemoryLayout, - pub pcie_host_bridges: &'a [PcieHostBridge], - pub settings: UefiLoadSettings, - pub chipset_mmio: &'a ChipsetMmioRanges, - pub madt: &'a [u8], - pub srat: &'a [u8], - pub slit: Option<&'a [u8]>, - pub mcfg: Option<&'a [u8]>, - pub pptt: Option<&'a [u8]>, -} - -/// Loads the UEFI firmware. -pub fn load_uefi(params: &LoadUefiParams<'_>) -> Result, Error> { - let LoadUefiParams { - firmware, - gm, - processor_topology, - mem_layout, - pcie_host_bridges, - ref settings, - chipset_mmio, - madt, - srat, - slit, - mcfg, - pptt, - } = *params; - - let mut loaded_image; - let mut firmware = firmware; - let image = { - loaded_image = Vec::new(); - firmware.rewind().map_err(Error::Firmware)?; - firmware - .read_to_end(&mut loaded_image) - .map_err(Error::Firmware)?; - loaded_image.as_slice() - }; - +pub fn build_config_blob( + processor_topology: &ProcessorTopology, + mem_layout: &MemoryLayout, + pcie_host_bridges: &[PcieHostBridge], + settings: &UefiLoadSettings, + chipset_mmio: &ChipsetMmioRanges, + madt: &[u8], + srat: &[u8], + slit: Option<&[u8]>, + mcfg: Option<&[u8]>, + pptt: Option<&[u8]>, +) -> Result { let mut entropy = [0; 64]; getrandom::fill(&mut entropy).expect("rng failure"); @@ -231,6 +199,66 @@ pub fn load_uefi(params: &LoadUefiParams<'_>) -> Result, Error> { ); } + Ok(cfg) +} + +/// All inputs needed by [`load_uefi`]. +pub struct LoadUefiParams<'a> { + pub firmware: &'a std::fs::File, + pub gm: &'a GuestMemory, + pub processor_topology: &'a ProcessorTopology, + pub mem_layout: &'a MemoryLayout, + pub pcie_host_bridges: &'a [PcieHostBridge], + pub settings: UefiLoadSettings, + pub chipset_mmio: &'a ChipsetMmioRanges, + pub madt: &'a [u8], + pub srat: &'a [u8], + pub slit: Option<&'a [u8]>, + pub mcfg: Option<&'a [u8]>, + pub pptt: Option<&'a [u8]>, +} + +/// Loads the UEFI firmware. +pub fn load_uefi(params: &LoadUefiParams<'_>) -> Result, Error> { + let LoadUefiParams { + firmware, + gm, + processor_topology, + mem_layout, + pcie_host_bridges, + ref settings, + chipset_mmio, + madt, + srat, + slit, + mcfg, + pptt, + } = *params; + + let mut loaded_image; + let mut firmware = firmware; + let image = { + loaded_image = Vec::new(); + firmware.rewind().map_err(Error::Firmware)?; + firmware + .read_to_end(&mut loaded_image) + .map_err(Error::Firmware)?; + loaded_image.as_slice() + }; + + let cfg = build_config_blob( + processor_topology, + mem_layout, + pcie_host_bridges, + settings, + chipset_mmio, + madt, + srat, + slit, + mcfg, + pptt, + )?; + let mut loader = Loader::new(gm.clone(), mem_layout, hvdef::Vtl::Vtl0); loader::uefi::load( diff --git a/openvmm/openvmm_defs/src/config.rs b/openvmm/openvmm_defs/src/config.rs index b4211b0697..85256b30a4 100644 --- a/openvmm/openvmm_defs/src/config.rs +++ b/openvmm/openvmm_defs/src/config.rs @@ -145,6 +145,7 @@ pub enum LoadMode { file: File, cmdline: String, vtl2_base_address: Vtl2BaseAddressType, + uefi: bool, com_serial: Option, }, None, diff --git a/openvmm/openvmm_entry/src/cli_args.rs b/openvmm/openvmm_entry/src/cli_args.rs index 6bc8430928..ca0b20a34e 100644 --- a/openvmm/openvmm_entry/src/cli_args.rs +++ b/openvmm/openvmm_entry/src/cli_args.rs @@ -642,13 +642,20 @@ options: pub pcat_firmware: Option, /// boot IGVM file - #[clap(long, conflicts_with("kernel"), value_name = "FILE")] - pub igvm: Option, + /// + /// Use `--igvm FILE` for the legacy OpenHCL IGVM path, or + /// `--igvm type=uefi,path=FILE` for a fixed-address UEFI firmware IGVM. + #[clap( + long, + conflicts_with("kernel"), + value_name = "FILE|type=TYPE,path=FILE" + )] + pub igvm: Option, /// specify igvm vtl2 relocation type /// (absolute=\, disable, auto=\, vtl2=\,) - #[clap(long, requires("igvm"), default_value = "auto=filesize", value_parser = parse_vtl2_relocation)] - pub igvm_vtl2_relocation_type: Vtl2BaseAddressType, + #[clap(long, requires("igvm"), value_parser = parse_vtl2_relocation)] + pub igvm_vtl2_relocation_type: Option, /// add a virtio_9p device (e.g. myfs,C:\) /// @@ -1226,6 +1233,67 @@ impl Options { } Ok(()) } + + /// Validates IGVM-specific option combinations. + pub fn validate_igvm_options(&self) -> anyhow::Result<()> { + if self.igvm.as_ref().is_some_and(|igvm| igvm.uefi) + && self + .igvm_vtl2_relocation_type + .is_some_and(|relocation| !matches!(relocation, Vtl2BaseAddressType::File)) + { + anyhow::bail!("--igvm type=uefi implies --igvm-vtl2-relocation-type disable"); + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct IgvmCli { + pub path: PathBuf, + pub uefi: bool, +} + +impl FromStr for IgvmCli { + type Err = String; + + fn from_str(s: &str) -> Result { + if !s.starts_with("type=") && !s.starts_with("path=") { + return Ok(Self { + path: s + .parse() + .map_err(|err| format!("invalid IGVM path: {err}"))?, + uefi: false, + }); + } + + let mut path = None; + let mut uefi = false; + for part in s.split(',') { + let (key, value) = part + .split_once('=') + .ok_or_else(|| format!("invalid IGVM option `{part}`"))?; + match key { + "type" => match value { + "uefi" => uefi = true, + other => return Err(format!("unsupported IGVM type `{other}`")), + }, + "path" => { + if value.is_empty() { + return Err("`path=` requires a file path".into()); + } + path = Some( + value + .parse() + .map_err(|err| format!("invalid IGVM path: {err}"))?, + ); + } + other => return Err(format!("unknown IGVM option `{other}`")), + } + } + + let path = path.ok_or_else(|| "`path=` is required".to_string())?; + Ok(Self { path, uefi }) + } } #[derive(Clone, Debug, PartialEq)] @@ -3533,6 +3601,28 @@ mod tests { use std::path::Path; + #[test] + fn test_igvm_cli_legacy_path() { + let igvm = IgvmCli::from_str("openhcl.bin").unwrap(); + assert_eq!(igvm.path, Path::new("openhcl.bin")); + assert!(!igvm.uefi); + } + + #[test] + fn test_igvm_cli_uefi_type() { + let igvm = IgvmCli::from_str("type=uefi,path=openhcl-uefi.bin").unwrap(); + assert_eq!(igvm.path, Path::new("openhcl-uefi.bin")); + assert!(igvm.uefi); + } + + #[test] + fn test_igvm_cli_errors() { + assert!(IgvmCli::from_str("type=openhcl,path=openhcl.bin").is_err()); + assert!(IgvmCli::from_str("type=uefi").is_err()); + assert!(IgvmCli::from_str("type=uefi,path=").is_err()); + assert!(IgvmCli::from_str("type=uefi,path=openhcl.bin,foo=bar").is_err()); + } + #[test] fn test_parse_file_opts() { // file: prefix with create diff --git a/openvmm/openvmm_entry/src/lib.rs b/openvmm/openvmm_entry/src/lib.rs index b3a25cde50..71aeacbd7b 100644 --- a/openvmm/openvmm_entry/src/lib.rs +++ b/openvmm/openvmm_entry/src/lib.rs @@ -89,6 +89,7 @@ use openvmm_defs::config::VirtioBus; use openvmm_defs::config::VmbusConfig; use openvmm_defs::config::VpAssignment; use openvmm_defs::config::VpciDeviceConfig; +use openvmm_defs::config::Vtl2BaseAddressType; use openvmm_defs::config::Vtl2Config; use openvmm_defs::rpc::VmRpc; use openvmm_defs::worker::VM_WORKER; @@ -426,6 +427,7 @@ async fn vm_config_from_command_line( } opt.validate_memory_options()?; + opt.validate_igvm_options()?; const MAX_PROCESSOR_COUNT: u32 = 1024; @@ -1051,9 +1053,19 @@ async fn vm_config_from_command_line( || serial3_cfg.is_some(); let has_com3 = serial2_cfg.is_some(); + let igvm_uses_uefi_helper = opt.igvm.as_ref().is_some_and(|igvm| igvm.uefi); + let igvm_vtl2_relocation_type = opt.igvm_vtl2_relocation_type.unwrap_or({ + if igvm_uses_uefi_helper { + Vtl2BaseAddressType::File + } else { + Vtl2BaseAddressType::MemoryLayout { size: None } + } + }); let mut chipset = VmManifestBuilder::new( - if opt.igvm.is_some() { + if igvm_uses_uefi_helper { + BaseChipsetType::HypervGen2Uefi + } else if opt.igvm.is_some() { BaseChipsetType::HclHost } else if opt.pcat { BaseChipsetType::HypervGen1 @@ -1137,7 +1149,7 @@ async fn vm_config_from_command_line( EfiDiagnosticsLogLevelCli::Full => EfiDiagnosticsLogLevelType::Full, }; - if opt.uefi { + if opt.uefi || igvm_uses_uefi_helper { let log_level = match efi_diagnostics_log_level { EfiDiagnosticsLogLevelType::Default => { firmware_uefi_resources::LogLevel::make_default() @@ -1180,8 +1192,8 @@ async fn vm_config_from_command_line( // memory come from the snapshot directory. load_mode = LoadMode::None; with_hv = true; - } else if let Some(path) = &opt.igvm { - let file = fs_err::File::open(path) + } else if let Some(igvm) = &opt.igvm { + let file = fs_err::File::open(&igvm.path) .context("failed to open igvm file")? .into(); let cmdline = opt.cmdline.join(" "); @@ -1190,7 +1202,8 @@ async fn vm_config_from_command_line( load_mode = LoadMode::Igvm { file, cmdline, - vtl2_base_address: opt.igvm_vtl2_relocation_type, + vtl2_base_address: igvm_vtl2_relocation_type, + uefi: igvm.uefi, com_serial: has_com3.then(|| SerialInformation { io_port: ComPort::Com3.io_port(), irq: ComPort::Com3.irq().into(), @@ -2663,7 +2676,7 @@ async fn run_control_inner( ged_rpc: resources.ged_rpc.clone(), vm_rpc: vm_rpc.clone(), paravisor_diag: Some(paravisor_diag), - igvm_path: opt.igvm.clone(), + igvm_path: opt.igvm.as_ref().map(|igvm| igvm.path.clone()), memory_backing_file: opt.memory_backing_file().cloned(), memory: opt.memory_size(), processors: opt.processors, diff --git a/vm/loader/manifests/uefi-x64.json b/vm/loader/manifests/uefi-x64.json index eb12b06d41..719176c0f9 100644 --- a/vm/loader/manifests/uefi-x64.json +++ b/vm/loader/manifests/uefi-x64.json @@ -1,6 +1,16 @@ { "guest_arch": "x64", "guest_configs": [ + { + "guest_svn": 1, + "max_vtl": 0, + "isolation_type": "none", + "image": { + "uefi": { + "config_type": "igvm" + } + } + }, { "guest_svn": 1, "max_vtl": 0, @@ -35,4 +45,4 @@ } } ] -} \ No newline at end of file +} From a8387fd34522f11eef77c26712b41a841f6af832 Mon Sep 17 00:00:00 2001 From: Jiong Wang Date: Thu, 11 Jun 2026 15:14:06 -0700 Subject: [PATCH 2/2] fix bug exposed by test pipeline --- petri/src/vm/openvmm/construct.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/petri/src/vm/openvmm/construct.rs b/petri/src/vm/openvmm/construct.rs index a9cd674676..a23dd2d431 100644 --- a/petri/src/vm/openvmm/construct.rs +++ b/petri/src/vm/openvmm/construct.rs @@ -1012,6 +1012,7 @@ impl PetriVmConfigSetupCore<'_> { file, cmdline: cmdline.unwrap_or_default(), vtl2_base_address, + uefi: false, com_serial: Some(SerialInformation { io_port: ComPort::Com3.io_port(), irq: ComPort::Com3.irq().into(),