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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions openvmm/openvmm_core/src/worker/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand All @@ -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)]
Expand Down
116 changes: 72 additions & 44 deletions openvmm/openvmm_core/src/worker/vm_loaders/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Register>, 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<config::Blob, Error> {
let mut entropy = [0; 64];
getrandom::fill(&mut entropy).expect("rng failure");
Comment on lines 65 to 66

Expand Down Expand Up @@ -231,6 +199,66 @@ pub fn load_uefi(params: &LoadUefiParams<'_>) -> Result<Vec<Register>, 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<Vec<Register>, 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(
Expand Down
1 change: 1 addition & 0 deletions openvmm/openvmm_defs/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ pub enum LoadMode {
file: File,
cmdline: String,
vtl2_base_address: Vtl2BaseAddressType,
uefi: bool,
com_serial: Option<SerialInformation>,
},
None,
Expand Down
98 changes: 94 additions & 4 deletions openvmm/openvmm_entry/src/cli_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,13 +642,20 @@ options:
pub pcat_firmware: Option<PathBuf>,

/// boot IGVM file
#[clap(long, conflicts_with("kernel"), value_name = "FILE")]
pub igvm: Option<PathBuf>,
///
/// 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<IgvmCli>,

/// specify igvm vtl2 relocation type
/// (absolute=\<addr\>, disable, auto=\<filesize,or memory size\>, vtl2=\<filesize,or memory size\>,)
#[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<Vtl2BaseAddressType>,
Comment thread
jiong-microsoft marked this conversation as resolved.

/// add a virtio_9p device (e.g. myfs,C:\)
///
Expand Down Expand Up @@ -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");
}
Comment thread
jiong-microsoft marked this conversation as resolved.
Ok(())
}
Comment thread
jiong-microsoft marked this conversation as resolved.
}

#[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<Self, Self::Err> {
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,
});
}
Comment thread
jiong-microsoft marked this conversation as resolved.

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)]
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading