vmm: refactor VM initialization into modular helper methods

Decompose the monolithic `new_from_memory_manager` function into
smaller, focused helper methods to improve code readability,
maintainability, and testability.

Changes:
- Extract `should_force_iommu()` to determine IOMMU requirements for
  confidential computing (TDX/SEV-SNP)
- Extract `should_stop_on_boot()` to check debug pause configuration
- Extract `create_cpu_manager()` to encapsulate CPU manager creation
  and CPUID population
- Extract `init_tdx_if_enabled()` for TDX-specific VM initialization
- Extract `create_device_manager()` to encapsulate device manager setup
- Extract `hypervisor_specific_init()` to orchestrate initialization
  sequences for different hypervisors (KVM, MSHV, SEV-SNP)
- Extract `init_sev_snp()` for SEV-SNP confidential VM setup
- Extract `init_mshv()` for MSHV hypervisor initialization
- Extract `init_kvm()` for KVM hypervisor initialization
- Extract `create_fw_cfg_if_enabled()` for fw_cfg device creation

This refactoring replaces complex nested `cfg_if!` blocks with cleaner
conditional method calls, providing clear separation between hypervisor-
specific initialization paths while preserving existing functionality.

No functional changes intended.
Issue: https://github.com/cloud-hypervisor/cloud-hypervisor/issues/7598

Signed-off-by: Muminul Islam <muislam@microsoft.com>
This commit is contained in:
Muminul Islam 2026-02-12 18:33:04 -08:00 committed by Rob Bradford
parent 03ef7d1991
commit 41b23229a5
2 changed files with 441 additions and 245 deletions

View file

@ -15,7 +15,9 @@ use std::io::{self, IsTerminal, Seek, SeekFrom, stdout};
use std::num::Wrapping;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
#[cfg(not(target_arch = "riscv64"))]
use std::path::Path;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
#[cfg(not(target_arch = "riscv64"))]

View file

@ -11,7 +11,7 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
//
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fs::{File, OpenOptions};
use std::io::{self, Seek, SeekFrom, Write};
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
@ -560,21 +560,10 @@ impl Vm {
let numa_nodes =
Self::create_numa_nodes(config.lock().unwrap().numa.as_deref(), &memory_manager)?;
#[cfg(feature = "tdx")]
let tdx_enabled = config.lock().unwrap().is_tdx_enabled();
#[cfg(feature = "sev_snp")]
let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled();
#[cfg(feature = "tdx")]
let force_iommu = tdx_enabled;
#[cfg(feature = "sev_snp")]
let force_iommu = sev_snp_enabled;
#[cfg(not(any(feature = "tdx", feature = "sev_snp")))]
let force_iommu = false;
// Determine if IOMMU should be forced based on confidential computing features
let force_iommu = Self::should_force_iommu(&config);
#[cfg(feature = "guest_debug")]
let stop_on_boot = config.lock().unwrap().gdb;
#[cfg(not(feature = "guest_debug"))]
let stop_on_boot = false;
let stop_on_boot = Self::should_stop_on_boot(&config);
let memory = memory_manager.lock().unwrap().guest_memory();
let io_bus = Arc::new(Bus::new());
@ -587,63 +576,26 @@ impl Vm {
mmio_bus: mmio_bus.clone(),
});
let cpus_config = { &config.lock().unwrap().cpus.clone() };
let cpu_manager = cpu::CpuManager::new(
cpus_config,
// Create CPU manager
let cpu_manager = Self::create_cpu_manager(
&config,
vm.clone(),
exit_evt.try_clone().map_err(Error::EventFdClone)?,
reset_evt.try_clone().map_err(Error::EventFdClone)?,
#[cfg(feature = "guest_debug")]
vm_debug_evt,
hypervisor.clone(),
&hypervisor,
seccomp_action.clone(),
vm_ops,
#[cfg(feature = "tdx")]
tdx_enabled,
&numa_nodes,
#[cfg(feature = "sev_snp")]
sev_snp_enabled,
)
.map_err(Error::CpuManager)?;
)?;
#[cfg(target_arch = "x86_64")]
cpu_manager
.lock()
.unwrap()
.populate_cpuid(
hypervisor.as_ref(),
#[cfg(feature = "tdx")]
tdx_enabled,
)
.map_err(Error::CpuManager)?;
// The initial TDX configuration must be done before the vCPUs are
// created
// Perform hypervisor-specific TDX initialization if enabled
#[cfg(feature = "tdx")]
if tdx_enabled {
let cpuid = cpu_manager.lock().unwrap().common_cpuid();
let max_vcpus = cpu_manager.lock().unwrap().max_vcpus();
vm.tdx_init(&cpuid, max_vcpus)
.map_err(Error::InitializeTdxVm)?;
}
Self::init_tdx_if_enabled(&config, &vm, &cpu_manager)?;
#[cfg(feature = "tdx")]
let dynamic = !tdx_enabled;
#[cfg(not(feature = "tdx"))]
let dynamic = true;
#[cfg(feature = "kvm")]
let is_kvm = matches!(
hypervisor.hypervisor_type(),
hypervisor::HypervisorType::Kvm
);
#[cfg(feature = "mshv")]
let is_mshv = matches!(
hypervisor.hypervisor_type(),
hypervisor::HypervisorType::Mshv
);
let device_manager = DeviceManager::new(
// Create device manager
let device_manager = Self::create_device_manager(
io_bus,
mmio_bus,
vm.clone(),
@ -659,190 +611,25 @@ impl Vm {
boot_id_list,
#[cfg(not(target_arch = "riscv64"))]
timestamp,
snapshot_from_id(snapshot, DEVICE_MANAGER_SNAPSHOT_ID),
dynamic,
)
.map_err(Error::DeviceManager)?;
snapshot,
&config,
)?;
// Initialize the VM now that we have created the device manager.
// For MSHV and non aarch64, we need to initialize the VM before creating vCPUs.
// For aarch64, we need to initialize the VM after creating interrupt controller.
// Push down write after the IC(Interrupt Controller) creation for MSHV aarch64.
#[cfg(all(feature = "mshv", not(target_arch = "aarch64")))]
{
if is_mshv {
vm.init().map_err(Error::InitializeVm)?;
}
}
#[cfg(feature = "sev_snp")]
if sev_snp_enabled {
cpu_manager
.lock()
.unwrap()
.create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID))
.map_err(Error::CpuManager)?;
// This initial SEV-SNP configuration must be done immediately after
// vCPUs are created. As part of this initialization we are
// transitioning the guest into secure state.
vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?;
}
#[cfg(feature = "sev_snp")]
// Loading the igvm file is pushed down here because
// igvm parser needs cpu_manager to retrieve cpuid leaf.
// Currently, Microsoft Hypervisor does not provide any
// Hypervisor specific common cpuid, we need to call get_cpuid_values
// per cpuid through cpu_manager.
let _load_payload_handle = if snapshot.is_none() && sev_snp_enabled {
Self::load_payload_async(
&memory_manager,
&config,
#[cfg(feature = "igvm")]
&cpu_manager,
#[cfg(feature = "sev_snp")]
sev_snp_enabled,
)?
} else {
None
};
#[cfg(feature = "mshv")]
{
if is_mshv {
let ic = device_manager
.lock()
.unwrap()
.create_interrupt_controller()
.map_err(Error::DeviceManager)?;
#[cfg(target_arch = "aarch64")]
vm.init().map_err(Error::InitializeVm)?;
device_manager
.lock()
.unwrap()
.create_devices(
console_info.clone(),
console_resize_pipe.clone(),
original_termios.clone(),
ic,
)
.map_err(Error::DeviceManager)?;
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "sev_snp")] {
if !sev_snp_enabled {
memory_manager
.lock()
.unwrap()
.allocate_address_space()
.map_err(Error::MemoryManager)?;
}
} else {
memory_manager
.lock()
.unwrap()
.allocate_address_space()
.map_err(Error::MemoryManager)?;
}
}
#[cfg(target_arch = "aarch64")]
memory_manager
.lock()
.unwrap()
.add_uefi_flash()
.map_err(Error::MemoryManager)?;
// First case is when sev_snp is enabled(compiled), but run time non-cvn
// guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot.
cfg_if::cfg_if! {
if #[cfg(feature = "sev_snp")] {
let _load_payload_handle = if snapshot.is_none() && !sev_snp_enabled {
Self::load_payload_async(
&memory_manager,
&config,
#[cfg(feature = "igvm")]
&cpu_manager,
#[cfg(feature = "sev_snp")]
sev_snp_enabled,
)?
} else {
None
};
} else {
let _load_payload_handle = if snapshot.is_none() {
Self::load_payload_async(
&memory_manager,
&config,
#[cfg(feature = "igvm")]
&cpu_manager,
)?
} else {
None
};
}
}
// First case is when sev_snp is enabled(compiled), but run time non-cvn
// guest boot. 2nd case is when sev_snp is not compiled in, KVM and MSHV regular guest boot.
cfg_if::cfg_if! {
if #[cfg(feature = "sev_snp")] {
if !sev_snp_enabled {
cpu_manager
.lock()
.unwrap()
.create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID))
.map_err(Error::CpuManager)?;
}
} else {
cpu_manager
.lock()
.unwrap()
.create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID))
.map_err(Error::CpuManager)?;
}
}
// For KVM, we need to create interrupt controller after we create boot vcpus.
// Because we restore GIC state from the snapshot as part of boot vcpu creation.
// This means that we need to create interrupt controller after we restore in case of KVM guests.
#[cfg(feature = "kvm")]
{
if is_kvm {
let ic = device_manager
.lock()
.unwrap()
.create_interrupt_controller()
.map_err(Error::DeviceManager)?;
vm.init().map_err(Error::InitializeVm)?;
device_manager
.lock()
.unwrap()
.create_devices(console_info, console_resize_pipe, original_termios, ic)
.map_err(Error::DeviceManager)?;
}
}
#[cfg(feature = "fw_cfg")]
{
let fw_cfg_config = config
.lock()
.unwrap()
.payload
.as_ref()
.is_some_and(|p| p.fw_cfg_config.is_some());
if fw_cfg_config {
device_manager
.lock()
.unwrap()
.create_fw_cfg_device()
.map_err(Error::DeviceManager)?;
}
}
// Perform hypervisor-specific initialization
let load_payload_handle = Self::hypervisor_specific_init(
&vm,
&memory_manager,
&cpu_manager,
&device_manager,
&config,
&hypervisor,
console_info.as_ref(),
console_resize_pipe.as_ref(),
&original_termios,
snapshot,
)?;
// Load kernel and initramfs files
#[cfg(feature = "tdx")]
let kernel = config
.lock()
@ -896,10 +683,417 @@ impl Vm {
#[cfg(not(target_arch = "riscv64"))]
hypervisor,
stop_on_boot,
load_payload_handle: _load_payload_handle,
load_payload_handle,
})
}
/// Determine if IOMMU should be forced based on confidential computing features.
fn should_force_iommu(_config: &Arc<Mutex<VmConfig>>) -> bool {
#[cfg(feature = "tdx")]
if _config.lock().unwrap().is_tdx_enabled() {
return true;
}
#[cfg(feature = "sev_snp")]
if _config.lock().unwrap().is_sev_snp_enabled() {
return true;
}
false
}
/// Determine if VM should stop on boot (for debugging).
fn should_stop_on_boot(config: &Arc<Mutex<VmConfig>>) -> bool {
#[cfg(feature = "guest_debug")]
{
config.lock().unwrap().gdb
}
#[cfg(not(feature = "guest_debug"))]
{
let _ = config;
false
}
}
/// Create and configure the CPU manager.
#[allow(clippy::too_many_arguments)]
fn create_cpu_manager(
config: &Arc<Mutex<VmConfig>>,
vm: Arc<dyn hypervisor::Vm>,
exit_evt: EventFd,
reset_evt: EventFd,
#[cfg(feature = "guest_debug")] vm_debug_evt: EventFd,
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
seccomp_action: SeccompAction,
vm_ops: Arc<dyn VmOps>,
numa_nodes: &NumaNodes,
) -> Result<Arc<Mutex<cpu::CpuManager>>> {
#[cfg(feature = "tdx")]
let tdx_enabled = config.lock().unwrap().is_tdx_enabled();
#[cfg(feature = "sev_snp")]
let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled();
let cpus_config = config.lock().unwrap().cpus.clone();
let cpu_manager = cpu::CpuManager::new(
&cpus_config,
vm,
exit_evt,
reset_evt,
#[cfg(feature = "guest_debug")]
vm_debug_evt,
hypervisor.clone(),
seccomp_action,
vm_ops,
#[cfg(feature = "tdx")]
tdx_enabled,
numa_nodes,
#[cfg(feature = "sev_snp")]
sev_snp_enabled,
)
.map_err(Error::CpuManager)?;
#[cfg(target_arch = "x86_64")]
cpu_manager
.lock()
.unwrap()
.populate_cpuid(
hypervisor.as_ref(),
#[cfg(feature = "tdx")]
tdx_enabled,
)
.map_err(Error::CpuManager)?;
Ok(cpu_manager)
}
/// Initialize TDX if enabled.
#[cfg(feature = "tdx")]
fn init_tdx_if_enabled(
config: &Arc<Mutex<VmConfig>>,
vm: &Arc<dyn hypervisor::Vm>,
cpu_manager: &Arc<Mutex<cpu::CpuManager>>,
) -> Result<()> {
if config.lock().unwrap().is_tdx_enabled() {
let cpuid = cpu_manager.lock().unwrap().common_cpuid();
let max_vcpus = cpu_manager.lock().unwrap().max_vcpus();
vm.tdx_init(&cpuid, max_vcpus)
.map_err(Error::InitializeTdxVm)?;
}
Ok(())
}
/// Create and configure the device manager.
#[allow(clippy::too_many_arguments)]
fn create_device_manager(
io_bus: Arc<Bus>,
mmio_bus: Arc<Bus>,
vm: Arc<dyn hypervisor::Vm>,
config: Arc<Mutex<VmConfig>>,
memory_manager: Arc<Mutex<MemoryManager>>,
cpu_manager: Arc<Mutex<cpu::CpuManager>>,
exit_evt: EventFd,
reset_evt: EventFd,
seccomp_action: SeccompAction,
numa_nodes: NumaNodes,
activate_evt: &EventFd,
force_iommu: bool,
boot_id_list: BTreeSet<String>,
#[cfg(not(target_arch = "riscv64"))] timestamp: Instant,
snapshot: Option<&Snapshot>,
_vm_config: &Arc<Mutex<VmConfig>>,
) -> Result<Arc<Mutex<DeviceManager>>> {
#[cfg(feature = "tdx")]
let dynamic = !_vm_config.lock().unwrap().is_tdx_enabled();
#[cfg(not(feature = "tdx"))]
let dynamic = true;
DeviceManager::new(
io_bus,
mmio_bus,
vm,
config,
memory_manager,
cpu_manager,
exit_evt,
reset_evt,
seccomp_action,
numa_nodes,
activate_evt,
force_iommu,
boot_id_list,
#[cfg(not(target_arch = "riscv64"))]
timestamp,
snapshot_from_id(snapshot, DEVICE_MANAGER_SNAPSHOT_ID),
dynamic,
)
.map_err(Error::DeviceManager)
}
/// Perform hypervisor-specific initialization.
///
/// This handles the different initialization sequences required by:
/// - KVM (x86_64, aarch64, riscv64)
/// - MSHV (x86_64, aarch64)
/// - SEV-SNP (MSHV with confidential computing)
#[allow(clippy::too_many_arguments)]
fn hypervisor_specific_init(
vm: &Arc<dyn hypervisor::Vm>,
memory_manager: &Arc<Mutex<MemoryManager>>,
cpu_manager: &Arc<Mutex<cpu::CpuManager>>,
device_manager: &Arc<Mutex<DeviceManager>>,
config: &Arc<Mutex<VmConfig>>,
hypervisor: &Arc<dyn hypervisor::Hypervisor>,
console_info: Option<&ConsoleInfo>,
console_resize_pipe: Option<&Arc<File>>,
original_termios: &Arc<Mutex<Option<termios>>>,
snapshot: Option<&Snapshot>,
) -> Result<Option<thread::JoinHandle<Result<EntryPoint>>>> {
#[cfg(feature = "mshv")]
let is_mshv = matches!(
hypervisor.hypervisor_type(),
hypervisor::HypervisorType::Mshv
);
#[cfg(feature = "kvm")]
let is_kvm = matches!(
hypervisor.hypervisor_type(),
hypervisor::HypervisorType::Kvm
);
#[cfg(feature = "sev_snp")]
let sev_snp_enabled = config.lock().unwrap().is_sev_snp_enabled();
// MSHV-specific initialization (non-aarch64)
#[cfg(all(feature = "mshv", not(target_arch = "aarch64")))]
if is_mshv {
vm.init().map_err(Error::InitializeVm)?;
}
// SEV-SNP specific initialization
#[cfg(feature = "sev_snp")]
if sev_snp_enabled {
return Self::init_sev_snp(
vm,
memory_manager,
cpu_manager,
device_manager,
config,
console_info,
console_resize_pipe,
original_termios,
snapshot,
);
}
// MSHV initialization (create interrupt controller and devices)
#[cfg(feature = "mshv")]
if is_mshv {
Self::init_mshv(
vm,
device_manager,
console_info,
console_resize_pipe,
original_termios,
)?;
}
// Allocate address space for non-SEV-SNP guests
memory_manager
.lock()
.unwrap()
.allocate_address_space()
.map_err(Error::MemoryManager)?;
// Add UEFI flash for aarch64
#[cfg(target_arch = "aarch64")]
memory_manager
.lock()
.unwrap()
.add_uefi_flash()
.map_err(Error::MemoryManager)?;
// Load payload asynchronously
let load_payload_handle = if snapshot.is_none() {
Self::load_payload_async(
memory_manager,
config,
#[cfg(feature = "igvm")]
cpu_manager,
#[cfg(feature = "sev_snp")]
false,
)?
} else {
None
};
// Create boot vCPUs
cpu_manager
.lock()
.unwrap()
.create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID))
.map_err(Error::CpuManager)?;
// KVM-specific initialization
#[cfg(feature = "kvm")]
if is_kvm {
Self::init_kvm(
vm,
device_manager,
console_info.cloned(),
console_resize_pipe.cloned(),
original_termios.clone(),
)?;
}
// Create fw_cfg device if configured
#[cfg(feature = "fw_cfg")]
Self::create_fw_cfg_if_enabled(config, device_manager)?;
Ok(load_payload_handle)
}
/// Initialize SEV-SNP specific components.
#[cfg(feature = "sev_snp")]
#[allow(clippy::too_many_arguments)]
fn init_sev_snp(
vm: &Arc<dyn hypervisor::Vm>,
memory_manager: &Arc<Mutex<MemoryManager>>,
cpu_manager: &Arc<Mutex<cpu::CpuManager>>,
device_manager: &Arc<Mutex<DeviceManager>>,
config: &Arc<Mutex<VmConfig>>,
console_info: Option<&ConsoleInfo>,
console_resize_pipe: Option<&Arc<File>>,
original_termios: &Arc<Mutex<Option<termios>>>,
snapshot: Option<&Snapshot>,
) -> Result<Option<thread::JoinHandle<Result<EntryPoint>>>> {
// Create boot vCPUs before SEV-SNP initialization
cpu_manager
.lock()
.unwrap()
.create_boot_vcpus(snapshot_from_id(snapshot, CPU_MANAGER_SNAPSHOT_ID))
.map_err(Error::CpuManager)?;
// Initialize SEV-SNP - transitions guest into secure state
vm.sev_snp_init().map_err(Error::InitializeSevSnpVm)?;
// Load payload for SEV-SNP (IGVM parser needs cpu_manager for cpuid)
let load_payload_handle = if snapshot.is_none() {
Self::load_payload_async(
memory_manager,
config,
#[cfg(feature = "igvm")]
cpu_manager,
true,
)?
} else {
None
};
// Create interrupt controller and devices for MSHV
let ic = device_manager
.lock()
.unwrap()
.create_interrupt_controller()
.map_err(Error::DeviceManager)?;
#[cfg(target_arch = "aarch64")]
vm.init().map_err(Error::InitializeVm)?;
device_manager
.lock()
.unwrap()
.create_devices(
console_info.cloned(),
console_resize_pipe.cloned(),
original_termios.clone(),
ic,
)
.map_err(Error::DeviceManager)?;
Ok(load_payload_handle)
}
/// Initialize MSHV-specific components.
#[cfg(feature = "mshv")]
fn init_mshv(
_vm: &Arc<dyn hypervisor::Vm>,
device_manager: &Arc<Mutex<DeviceManager>>,
console_info: Option<&ConsoleInfo>,
console_resize_pipe: Option<&Arc<File>>,
original_termios: &Arc<Mutex<Option<termios>>>,
) -> Result<()> {
let ic = device_manager
.lock()
.unwrap()
.create_interrupt_controller()
.map_err(Error::DeviceManager)?;
#[cfg(target_arch = "aarch64")]
_vm.init().map_err(Error::InitializeVm)?;
device_manager
.lock()
.unwrap()
.create_devices(
console_info.cloned(),
console_resize_pipe.cloned(),
original_termios.clone(),
ic,
)
.map_err(Error::DeviceManager)?;
Ok(())
}
/// Initialize KVM-specific components.
#[cfg(feature = "kvm")]
fn init_kvm(
vm: &Arc<dyn hypervisor::Vm>,
device_manager: &Arc<Mutex<DeviceManager>>,
console_info: Option<ConsoleInfo>,
console_resize_pipe: Option<Arc<File>>,
original_termios: Arc<Mutex<Option<termios>>>,
) -> Result<()> {
// For KVM, create interrupt controller after boot vcpus
// because GIC state is restored from snapshot during vcpu creation
let ic = device_manager
.lock()
.unwrap()
.create_interrupt_controller()
.map_err(Error::DeviceManager)?;
vm.init().map_err(Error::InitializeVm)?;
device_manager
.lock()
.unwrap()
.create_devices(console_info, console_resize_pipe, original_termios, ic)
.map_err(Error::DeviceManager)?;
Ok(())
}
/// Create fw_cfg device if enabled in configuration.
#[cfg(feature = "fw_cfg")]
fn create_fw_cfg_if_enabled(
config: &Arc<Mutex<VmConfig>>,
device_manager: &Arc<Mutex<DeviceManager>>,
) -> Result<()> {
let fw_cfg_enabled = config
.lock()
.unwrap()
.payload
.as_ref()
.is_some_and(|p| p.fw_cfg_config.is_some());
if fw_cfg_enabled {
device_manager
.lock()
.unwrap()
.create_fw_cfg_device()
.map_err(Error::DeviceManager)?;
}
Ok(())
}
#[cfg(feature = "fw_cfg")]
fn populate_fw_cfg(
fw_cfg_config: &FwCfgConfig,