#[cfg(target_os = "macos")]
use crossbeam_channel::unbounded;
use crossbeam_channel::Sender;
use kernel::cmdline::Cmdline;
#[cfg(target_os = "macos")]
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::{self, IsTerminal, Read};
use std::os::fd::AsRawFd;
use std::os::fd::{BorrowedFd, FromRawFd};
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::{Arc, Mutex};
use super::{Error, Vmm};
#[cfg(target_arch = "x86_64")]
use crate::device_manager::legacy::PortIODeviceManager;
use crate::device_manager::mmio::MMIODeviceManager;
use crate::resources::{
DefaultVirtioConsoleConfig, PortConfig, TsiFlags, VirtioConsoleConfigMode, VmResources,
};
use crate::vmm_config::external_kernel::{ExternalKernel, KernelFormat};
#[cfg(feature = "net")]
use crate::vmm_config::net::NetBuilder;
#[cfg(target_arch = "x86_64")]
use devices::legacy::Cmos;
#[cfg(all(target_os = "linux", target_arch = "riscv64"))]
use devices::legacy::KvmAia;
#[cfg(target_arch = "x86_64")]
use devices::legacy::KvmIoapic;
use devices::legacy::Serial;
#[cfg(target_os = "macos")]
use devices::legacy::VcpuList;
#[cfg(target_os = "macos")]
use devices::legacy::{GicV3, HvfGicV3};
#[cfg(target_arch = "x86_64")]
use devices::legacy::{IoApic, IrqChipT};
use devices::legacy::{IrqChip, IrqChipDevice};
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
use devices::legacy::{KvmGicV2, KvmGicV3};
use devices::virtio::{port_io, MmioTransport, PortDescription, VirtioDevice, Vsock};
#[cfg(feature = "tee")]
use kbs_types::Tee;
use crate::device_manager;
#[cfg(target_os = "linux")]
use crate::signal_handler::register_sigint_handler;
#[cfg(target_os = "linux")]
use crate::signal_handler::register_sigwinch_handler;
use crate::terminal::{term_restore_mode, term_set_raw_mode};
#[cfg(feature = "blk")]
use crate::vmm_config::block::BlockBuilder;
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
use crate::vmm_config::fs::FsDeviceConfig;
use crate::vmm_config::kernel_cmdline::DEFAULT_KERNEL_CMDLINE;
#[cfg(target_os = "linux")]
use crate::vstate::KvmContext;
#[cfg(all(target_os = "linux", feature = "tee"))]
use crate::vstate::MeasuredRegion;
use crate::vstate::{Error as VstateError, Vcpu, VcpuConfig, Vm};
use arch::{ArchMemoryInfo, InitrdConfig};
use device_manager::shm::ShmManager;
#[cfg(feature = "gpu")]
use devices::virtio::display::DisplayInfo;
#[cfg(feature = "gpu")]
use devices::virtio::display::NoopDisplayBackend;
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
use devices::virtio::{fs::ExportTable, VirtioShmRegion};
use flate2::read::GzDecoder;
#[cfg(feature = "gpu")]
use krun_display::DisplayBackend;
#[cfg(feature = "gpu")]
use krun_display::IntoDisplayBackend;
#[cfg(feature = "amd-sev")]
use kvm_bindings::KVM_MAX_CPUID_ENTRIES;
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
#[cfg(target_arch = "x86_64")]
use linux_loader::loader::{self, KernelLoader};
use nix::unistd::isatty;
use polly::event_manager::{Error as EventManagerError, EventManager};
use utils::eventfd::EventFd;
use utils::worker_message::WorkerMessage;
#[cfg(all(target_arch = "x86_64", not(feature = "efi"), not(feature = "tee")))]
use vm_memory::mmap::MmapRegion;
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
use vm_memory::Address;
use vm_memory::Bytes;
#[cfg(not(feature = "aws-nitro"))]
use vm_memory::GuestMemory;
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
use vm_memory::GuestRegionMmap;
use vm_memory::{GuestAddress, GuestMemoryMmap};
#[cfg(target_arch = "aarch64")]
#[allow(dead_code)]
static EDK2_BINARY: &[u8] = include_bytes!(env!("KRUN_EDK2_BINARY_PATH"));
#[derive(Debug)]
pub enum StartMicrovmError {
AttachBlockDevice(io::Error),
#[cfg(target_os = "macos")]
CreateHvfIrqChip(hvf::Error),
#[cfg(target_os = "linux")]
CreateKvmIrqChip(kvm_ioctls::Error),
CreateRateLimiter(io::Error),
ElfOpenKernel(io::Error),
ElfLoadKernel(linux_loader::loader::Error),
FirmwareInvalidAddress(vm_memory::GuestMemoryError),
FirmwareRead(io::Error),
GuestMemoryMmap(String),
ImageBz2Decoder(io::Error),
ImageBz2Invalid,
ImageBz2LoadKernel(linux_loader::loader::Error),
ImageBz2OpenKernel(io::Error),
ImageGzDecoder(io::Error),
ImageGzInvalid,
ImageGzLoadKernel(linux_loader::loader::Error),
ImageGzOpenKernel(io::Error),
ImageZstdDecoder(io::Error),
ImageZstdInvalid,
ImageZstdLoadKernel(linux_loader::loader::Error),
ImageZstdOpenKernel(io::Error),
InitrdLoad,
InitrdRead(io::Error),
Internal(Error),
InvalidKernelBundle(vm_memory::mmap::MmapRegionError),
KernelCmdline(String),
KernelDoesNotFit(u64, usize),
KernelFormatUnsupported,
LoadCommandline(kernel::cmdline::Error),
MicroVMAlreadyRunning,
MissingKernelConfig,
MissingMemSizeConfig,
NetDeviceNotConfigured,
OpenBlockDevice(io::Error),
OpenConsoleFile(io::Error),
PeGzDecoder(io::Error),
PeGzOpenKernel(io::Error),
PeGzInvalid,
RawOpenKernel(io::Error),
RegisterBalloonDevice(device_manager::mmio::Error),
RegisterBlockDevice(device_manager::mmio::Error),
RegisterEvent(EventManagerError),
RegisterFsDevice(device_manager::mmio::Error),
RegisterConsoleDevice(device_manager::mmio::Error),
#[cfg(target_os = "linux")]
RegisterFsSigwinch(kvm_ioctls::Error),
RegisterGpuDevice(device_manager::mmio::Error),
RegisterInputDevice(device_manager::mmio::Error),
RegisterNetDevice(device_manager::mmio::Error),
RegisterRngDevice(device_manager::mmio::Error),
RegisterSndDevice(device_manager::mmio::Error),
RegisterVsockDevice(device_manager::mmio::Error),
SecureVirtAttest(VstateError),
SecureVirtPrepare(VstateError),
ShmConfig(device_manager::shm::Error),
ShmCreate(device_manager::shm::Error),
ShmHostAddr(vm_memory::GuestMemoryError),
InvalidTee,
}
impl std::convert::From<kernel::cmdline::Error> for StartMicrovmError {
fn from(e: kernel::cmdline::Error) -> StartMicrovmError {
StartMicrovmError::KernelCmdline(e.to_string())
}
}
impl Display for StartMicrovmError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
use self::StartMicrovmError::*;
match *self {
AttachBlockDevice(ref err) => {
write!(f, "Unable to attach block device to Vmm. Error: {err}")
}
#[cfg(target_os = "macos")]
CreateHvfIrqChip(ref err) => {
write!(f, "Cannot create HVF in-kernel IrqChip: {err}")
}
#[cfg(target_os = "linux")]
CreateKvmIrqChip(ref err) => {
write!(f, "Cannot create KVM in-kernel IrqChip: {err}")
}
CreateRateLimiter(ref err) => write!(f, "Cannot create RateLimiter: {err}"),
ElfOpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code: {err}")
}
ElfLoadKernel(ref err) => {
write!(f, "Cannot load the kernel into the VM: {err}")
}
FirmwareInvalidAddress(ref err) => {
write!(
f,
"The firmware can't be loaded into the guest memory: {err}"
)
}
FirmwareRead(ref err) => {
write!(f, "Cannot read firmware contents from file: {err}")
}
GuestMemoryMmap(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(f, "Invalid Memory Configuration: {err_msg}")
}
ImageBz2Decoder(ref err) => {
write!(f, "The BZIP2 decoder couldn't decompress the kernel. {err}")
}
ImageBz2Invalid => {
write!(f, "Cannot find compressed kernel in file.")
}
ImageBz2LoadKernel(ref err) => {
write!(
f,
"Cannot load the kernel from the uncompressed ELF data. {err}"
)
}
ImageBz2OpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code. {err}")
}
ImageGzDecoder(ref err) => {
write!(f, "The GZIP decoder couldn't decompress the kernel. {err}")
}
ImageGzInvalid => {
write!(f, "Cannot find compressed kernel in file.")
}
ImageGzLoadKernel(ref err) => {
write!(
f,
"Cannot load the kernel from the uncompressed ELF data. {err}"
)
}
ImageGzOpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code. {err}")
}
ImageZstdDecoder(ref err) => {
write!(f, "The ZSTD decoder couldn't decompress the kernel. {err}")
}
ImageZstdInvalid => {
write!(f, "Cannot find compressed kernel in file.")
}
ImageZstdLoadKernel(ref err) => {
write!(
f,
"Cannot load the kernel from the uncompressed ELF data. {err}"
)
}
ImageZstdOpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code. {err}")
}
InitrdLoad => write!(
f,
"Cannot load initrd due to an invalid memory configuration."
),
InitrdRead(ref err) => write!(f, "Cannot load initrd due to an invalid image: {err}"),
Internal(ref err) => write!(f, "Internal error while starting microVM: {err:?}"),
InvalidKernelBundle(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot inject the kernel into the guest memory due to a problem with the \
bundle. {err_msg}"
)
}
KernelCmdline(ref err) => write!(f, "Invalid kernel command line: {err}"),
KernelDoesNotFit(load_addr, size) => write!(
f,
"The kernel doesn't fit in the microVM memory (load_addr={load_addr}, size={size})"
),
KernelFormatUnsupported => {
write!(f, "The supplied kernel format is not supported.")
}
LoadCommandline(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(f, "Cannot load command line string. {err_msg}")
}
MicroVMAlreadyRunning => write!(f, "Microvm already running."),
MissingKernelConfig => write!(f, "Cannot start microvm without kernel configuration."),
MissingMemSizeConfig => {
write!(f, "Cannot start microvm without guest mem_size config.")
}
NetDeviceNotConfigured => {
write!(f, "The net device configuration is missing the tap device.")
}
OpenBlockDevice(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(f, "Cannot open the block device backing file. {err_msg}")
}
OpenConsoleFile(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(f, "Cannot open the console output file. {err_msg}")
}
PeGzDecoder(ref err) => {
write!(f, "The GZIP decoder couldn't decompress the kernel. {err}")
}
PeGzOpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code. {err}")
}
PeGzInvalid => {
write!(f, "Cannot find compressed kernel in file.")
}
RawOpenKernel(ref err) => {
write!(f, "Cannot open the file containing the kernel code: {err}")
}
RegisterBalloonDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Balloon Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterBlockDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Block Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterEvent(ref err) => write!(f, "Cannot register EventHandler. {err:?}"),
RegisterFsDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Fs Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterConsoleDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Console Device or add a device to the MMIO Bus. {err_msg}"
)
}
#[cfg(target_os = "linux")]
RegisterFsSigwinch(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot register SIGWINCH file descriptor for Fs Device. {err_msg}"
)
}
RegisterGpuDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Gpu Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterInputDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Input Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterNetDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Network Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterRngDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Rng Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterSndDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Snd Device or add a device to the MMIO Bus. {err_msg}"
)
}
RegisterVsockDevice(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize a MMIO Vsock Device or add a device to the MMIO Bus. {err_msg}"
)
}
SecureVirtAttest(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot attest the VM in the Secure Virtualization context. {err_msg}"
)
}
SecureVirtPrepare(ref err) => {
let mut err_msg = format!("{err}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Cannot initialize the Secure Virtualization backend. {err_msg}"
)
}
ShmHostAddr(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(
f,
"Error obtaining the host address of an SHM region. {err_msg}"
)
}
ShmConfig(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(f, "Error while configuring an SHM region. {err_msg}")
}
ShmCreate(ref err) => {
let mut err_msg = format!("{err:?}");
err_msg = err_msg.replace('\"', "");
write!(f, "Error while creating an SHM region. {err_msg}")
}
InvalidTee => {
write!(f, "TEE selected is not currently supported")
}
}
}
}
pub enum Payload {
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
KernelMmap,
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
KernelCopy,
ExternalKernel(ExternalKernel),
#[cfg(test)]
Empty,
Firmware,
#[cfg(feature = "tee")]
Tee,
}
fn choose_payload(vm_resources: &VmResources) -> Result<Payload, StartMicrovmError> {
if let Some(_kernel_bundle) = &vm_resources.kernel_bundle {
#[cfg(feature = "tee")]
if vm_resources.qboot_bundle.is_none() || vm_resources.initrd_bundle.is_none() {
return Err(StartMicrovmError::MissingKernelConfig);
}
#[cfg(feature = "tee")]
return Ok(Payload::Tee);
#[cfg(all(target_os = "linux", target_arch = "x86_64", not(feature = "tee")))]
return Ok(Payload::KernelMmap);
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
return Ok(Payload::KernelCopy);
} else if let Some(external_kernel) = vm_resources.external_kernel() {
Ok(Payload::ExternalKernel(external_kernel.clone()))
} else if cfg!(feature = "efi") || vm_resources.firmware_config.is_some() {
Ok(Payload::Firmware)
} else {
Err(StartMicrovmError::MissingKernelConfig)
}
}
pub fn build_microvm(
vm_resources: &super::resources::VmResources,
event_manager: &mut EventManager,
_shutdown_efd: Option<EventFd>,
_sender: Sender<WorkerMessage>,
) -> std::result::Result<Arc<Mutex<Vmm>>, StartMicrovmError> {
let payload = choose_payload(vm_resources)?;
let (guest_memory, arch_memory_info, mut _shm_manager, payload_config) = create_guest_memory(
vm_resources
.vm_config()
.mem_size_mib
.ok_or(StartMicrovmError::MissingMemSizeConfig)?,
vm_resources,
&payload,
)?;
let vcpu_config = vm_resources.vcpu_config();
#[allow(unused_mut)]
let mut kernel_cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE);
if let Some(cmdline) = payload_config.kernel_cmdline {
kernel_cmdline.insert_str(cmdline.as_str()).unwrap();
} else if let Some(cmdline) = &vm_resources.kernel_cmdline.prolog {
kernel_cmdline.insert_str(cmdline).unwrap();
} else {
kernel_cmdline.insert_str(DEFAULT_KERNEL_CMDLINE).unwrap();
}
if let Some(cmdline) = &vm_resources.kernel_cmdline.krun_env {
kernel_cmdline.insert_str(cmdline.as_str()).unwrap();
}
if let Some(kernel_console) = &vm_resources.kernel_console {
let cmdline = kernel_cmdline.as_str();
let console_start_idx = cmdline.find("console=").unwrap();
let console_end_idx = cmdline
.get(console_start_idx..)
.and_then(|s| s.find(" ").map(|i| i + console_start_idx));
let cmdline = cmdline.replace(
&cmdline[console_start_idx..console_end_idx.unwrap()],
format!("console={kernel_console}").as_str(),
);
kernel_cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE);
kernel_cmdline.insert_str(cmdline).unwrap();
}
#[cfg(not(feature = "tee"))]
#[allow(unused_mut)]
let mut vm = setup_vm(&guest_memory, vm_resources.nested_enabled)?;
#[cfg(feature = "tee")]
let (_kvm, vm) = {
let kvm = KvmContext::new()
.map_err(Error::KvmContext)
.map_err(StartMicrovmError::Internal)?;
let vm = setup_vm(
&kvm,
&guest_memory,
vm_resources,
#[cfg(feature = "tdx")]
_sender.clone(),
)?;
(kvm, vm)
};
#[cfg(feature = "tee")]
let tee = vm_resources.tee_config().tee;
#[cfg(feature = "amd-sev")]
let snp_launcher = match tee {
Tee::Snp => Some(
vm.snp_secure_virt_prepare(&guest_memory)
.map_err(StartMicrovmError::SecureVirtPrepare)?,
),
_ => None,
};
#[cfg(feature = "tdx")]
let mut tdx_launcher = match tee {
Tee::Tdx => vm
.tdx_secure_virt_prepare()
.map_err(StartMicrovmError::SecureVirtPrepare)?,
_ => panic!(),
};
#[cfg(all(feature = "tee", not(feature = "tdx")))]
let measured_regions = {
println!("Injecting and measuring memory regions. This may take a while.");
let qboot_size = if let Some(qboot_bundle) = &vm_resources.qboot_bundle {
qboot_bundle.size
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let (kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &vm_resources.kernel_bundle {
(kernel_bundle.guest_addr, kernel_bundle.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let (initrd_addr, initrd_size) = if let Some(initrd_config) = &payload_config.initrd_config
{
(initrd_config.address, initrd_config.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
vec![
MeasuredRegion {
guest_addr: arch::FIRMWARE_START,
host_addr: guest_memory
.get_host_address(GuestAddress(arch::FIRMWARE_START))
.unwrap() as u64,
size: qboot_size,
},
MeasuredRegion {
guest_addr: kernel_guest_addr,
host_addr: guest_memory
.get_host_address(GuestAddress(kernel_guest_addr))
.unwrap() as u64,
size: kernel_size,
},
MeasuredRegion {
guest_addr: initrd_addr.0,
host_addr: guest_memory.get_host_address(initrd_addr).unwrap() as u64,
size: initrd_size,
},
MeasuredRegion {
guest_addr: arch::x86_64::layout::ZERO_PAGE_START,
host_addr: guest_memory
.get_host_address(GuestAddress(arch::x86_64::layout::ZERO_PAGE_START))
.unwrap() as u64,
size: 4096,
},
]
};
#[cfg(feature = "tdx")]
let measured_regions = {
println!("Injecting and measuring memory regions. This may take a while.");
let qboot_size = if let Some(qboot_bundle) = &vm_resources.qboot_bundle {
qboot_bundle.size
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let m = vec![
MeasuredRegion {
guest_addr: 0,
host_addr: guest_memory.get_host_address(GuestAddress(0)).unwrap() as u64,
size: 0x8000_0000,
},
MeasuredRegion {
guest_addr: arch::FIRMWARE_START,
host_addr: guest_memory
.get_host_address(GuestAddress(arch::FIRMWARE_START))
.unwrap() as u64,
size: qboot_size,
},
];
m
};
let mut serial_devices = Vec::new();
if (cfg!(feature = "efi") || vm_resources.firmware_config.is_some())
&& !vm_resources.disable_implicit_console
{
serial_devices.push(setup_serial_device(
event_manager,
None,
None,
)?);
};
let mut serial_ttys = Vec::new();
for s in &vm_resources.serial_consoles {
let input: Option<Box<dyn devices::legacy::ReadableFd + Send>> = if s.input_fd >= 0 {
let file = unsafe { File::from_raw_fd(s.input_fd) };
if file.is_terminal() {
serial_ttys.push(unsafe { BorrowedFd::borrow_raw(file.as_raw_fd()) });
}
Some(Box::new(file))
} else {
None
};
let output: Option<Box<dyn io::Write + Send>> = if s.output_fd >= 0 {
Some(Box::new(unsafe { File::from_raw_fd(s.output_fd) }))
} else {
None
};
serial_devices.push(setup_serial_device(event_manager, input, output)?);
}
let exit_evt = EventFd::new(utils::eventfd::EFD_NONBLOCK)
.map_err(Error::EventFd)
.map_err(StartMicrovmError::Internal)?;
#[cfg(target_arch = "x86_64")]
let mut pio_device_manager = PortIODeviceManager::new(
Arc::new(Mutex::new(Cmos::new(
arch_memory_info.ram_below_gap,
arch_memory_info.ram_above_gap,
))),
serial_devices,
exit_evt
.try_clone()
.map_err(Error::EventFd)
.map_err(StartMicrovmError::Internal)?,
)
.map_err(Error::CreateLegacyDevice)
.map_err(StartMicrovmError::Internal)?;
#[allow(unused_mut)]
let mut mmio_device_manager = MMIODeviceManager::new(
&mut (arch::MMIO_MEM_START.clone()),
(arch::IRQ_BASE, arch::IRQ_MAX),
);
#[cfg(target_os = "macos")]
let vcpu_list = {
let cpu_count = vm_resources.vm_config().vcpu_count.unwrap();
Arc::new(VcpuList::new(cpu_count as u64))
};
let vcpus;
let intc: IrqChip;
#[cfg(target_arch = "x86_64")]
{
let ioapic: Box<dyn IrqChipT> = if vm_resources.split_irqchip {
Box::new(
IoApic::new(vm.fd(), _sender.clone())
.map_err(StartMicrovmError::CreateKvmIrqChip)?,
)
} else {
Box::new(KvmIoapic::new(vm.fd()).map_err(StartMicrovmError::CreateKvmIrqChip)?)
};
intc = Arc::new(Mutex::new(IrqChipDevice::new(ioapic)));
attach_legacy_devices(
&vm,
vm_resources.split_irqchip,
&mut pio_device_manager,
&mut mmio_device_manager,
Some(intc.clone()),
)?;
let kernel_boot = vm_resources.firmware_config.is_none() && !cfg!(feature = "tee");
vcpus = create_vcpus_x86_64(
&vm,
&vcpu_config,
&guest_memory,
payload_config.entry_addr,
&pio_device_manager.io_bus,
&exit_evt,
kernel_boot,
#[cfg(feature = "tee")]
_sender,
)
.map_err(StartMicrovmError::Internal)?;
}
#[cfg(feature = "tdx")]
{
for vcpu in &vcpus {
vcpu.tdx_secure_virt_prepare(&mut tdx_launcher);
}
vm.tdx_secure_virt_init_vcpus(&mut tdx_launcher).unwrap();
}
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
{
vcpus = create_vcpus_aarch64(
&vm,
&vcpu_config,
&arch_memory_info,
payload_config.entry_addr,
&exit_evt,
)
.map_err(StartMicrovmError::Internal)?;
intc = {
let vcpu_count = vm_resources.vm_config().vcpu_count.unwrap() as u64;
let gic = match KvmGicV3::new(vm.fd(), vcpu_count) {
Ok(gicv3) => IrqChipDevice::new(Box::new(gicv3)),
Err(_) => {
warn!("KVM GICv3 creation failed, falling back to KVM GICv2");
IrqChipDevice::new(Box::new(KvmGicV2::new(vm.fd(), vcpu_count)))
}
};
Arc::new(Mutex::new(gic))
};
attach_legacy_devices(
&vm,
&mut mmio_device_manager,
&mut kernel_cmdline,
intc.clone(),
serial_devices,
)?;
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
{
intc = {
let gic = match HvfGicV3::new(vm_resources.vm_config().vcpu_count.unwrap() as u64) {
Ok(hvfgic) => IrqChipDevice::new(Box::new(hvfgic)),
Err(_) => IrqChipDevice::new(Box::new(GicV3::new(vcpu_list.clone()))),
};
Arc::new(Mutex::new(gic))
};
vcpus = create_vcpus_aarch64(
&vm,
&vcpu_config,
&arch_memory_info,
payload_config.entry_addr,
&exit_evt,
vcpu_list.clone(),
vm_resources.nested_enabled,
)
.map_err(StartMicrovmError::Internal)?;
attach_legacy_devices(
&vm,
&mut mmio_device_manager,
&mut kernel_cmdline,
intc.clone(),
serial_devices,
event_manager,
_shutdown_efd,
)?;
}
#[cfg(all(target_arch = "riscv64", target_os = "linux"))]
{
vcpus = create_vcpus_riscv64(
&vm,
&vcpu_config,
&guest_memory,
payload_config.entry_addr,
&exit_evt,
)
.map_err(StartMicrovmError::Internal)?;
intc = Arc::new(Mutex::new(IrqChipDevice::new(Box::new(
KvmAia::new(vm.fd(), vm_resources.vm_config().vcpu_count.unwrap() as u32).unwrap(),
))));
attach_legacy_devices(
&vm,
&mut mmio_device_manager,
&mut kernel_cmdline,
intc.clone(),
serial_devices,
)?;
}
let exit_code = Arc::new(AtomicI32::new(i32::MAX));
let mut vmm = Vmm {
guest_memory,
arch_memory_info,
kernel_cmdline,
vcpus_handles: Vec::new(),
exit_evt,
exit_observers: Vec::new(),
exit_code: exit_code.clone(),
vm,
mmio_device_manager,
#[cfg(target_arch = "x86_64")]
pio_device_manager,
};
for serial_tty in serial_ttys {
setup_terminal_raw_mode(&mut vmm, Some(serial_tty), false);
}
#[cfg(not(feature = "tee"))]
attach_balloon_device(&mut vmm, event_manager, intc.clone())?;
#[cfg(not(feature = "tee"))]
attach_rng_device(&mut vmm, event_manager, intc.clone())?;
let mut console_id = 0;
if !vm_resources.disable_implicit_console {
attach_console_devices(
&mut vmm,
event_manager,
intc.clone(),
vm_resources,
None,
console_id,
)?;
console_id += 1;
}
for console_cfg in vm_resources.virtio_consoles.iter() {
attach_console_devices(
&mut vmm,
event_manager,
intc.clone(),
vm_resources,
Some(console_cfg),
console_id,
)?;
console_id += 1;
}
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
let export_table: Option<ExportTable> = if cfg!(feature = "gpu") {
Some(Default::default())
} else {
None
};
#[cfg(feature = "gpu")]
if let Some(virgl_flags) = vm_resources.gpu_virgl_flags {
let display_backend = vm_resources
.display_backend
.unwrap_or_else(|| NoopDisplayBackend::into_display_backend(None));
attach_gpu_device(
&mut vmm,
&mut _shm_manager,
#[cfg(not(feature = "tee"))]
export_table.clone(),
intc.clone(),
virgl_flags,
Box::from(&vm_resources.displays[..]),
display_backend,
#[cfg(target_os = "macos")]
_sender.clone(),
)?;
}
#[cfg(feature = "input")]
if !vm_resources.input_backends.is_empty() {
attach_input_devices(&mut vmm, &vm_resources.input_backends, intc.clone())?;
}
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
attach_fs_devices(
&mut vmm,
&vm_resources.fs,
&mut _shm_manager,
#[cfg(not(feature = "tee"))]
export_table,
intc.clone(),
exit_code,
#[cfg(target_os = "macos")]
_sender,
)?;
#[cfg(feature = "blk")]
attach_block_devices(&mut vmm, &vm_resources.block, intc.clone())?;
if let Some(vsock) = vm_resources.vsock.get() {
attach_unixsock_vsock_device(&mut vmm, vsock, event_manager, intc.clone())?;
let tsi_flags = vm_resources.vsock.tsi_flags();
if tsi_flags.contains(TsiFlags::HIJACK_INET) {
vmm.kernel_cmdline.insert_str("tsi_hijack")?;
}
if tsi_flags.contains(TsiFlags::HIJACK_UNIX) {
vmm.kernel_cmdline.insert_str("tsi_hijack_unix")?;
}
}
#[cfg(feature = "net")]
attach_net_devices(&mut vmm, &vm_resources.net, intc.clone())?;
#[cfg(feature = "net")]
if vm_resources.dhcp_client {
vmm.kernel_cmdline.insert_str("KRUN_DHCP=1")?;
}
#[cfg(feature = "snd")]
if vm_resources.snd_device {
attach_snd_device(&mut vmm, intc.clone())?;
}
if let Some(s) = &vm_resources.kernel_cmdline.epilog {
vmm.kernel_cmdline.insert_str(s).unwrap();
};
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
load_cmdline(&vmm)?;
vmm.configure_system(
vcpus.as_slice(),
&intc,
&payload_config.initrd_config,
&vm_resources.smbios_oem_strings,
)
.map_err(StartMicrovmError::Internal)?;
#[cfg(feature = "tee")]
{
match tee {
#[cfg(feature = "amd-sev")]
Tee::Snp => {
let cpuid = _kvm
.fd()
.get_supported_cpuid(KVM_MAX_CPUID_ENTRIES)
.map_err(VstateError::KvmCpuId)
.map_err(StartMicrovmError::SecureVirtAttest)?;
vmm.kvm_vm()
.snp_secure_virt_measure(
cpuid,
vmm.guest_memory(),
measured_regions,
snp_launcher.unwrap(),
)
.map_err(StartMicrovmError::SecureVirtAttest)?;
}
#[cfg(feature = "tdx")]
Tee::Tdx => {
vmm.kvm_vm()
.tdx_secure_virt_prepare_memory(&mut tdx_launcher, &measured_regions)
.unwrap();
vmm.kvm_vm()
.tdx_secure_virt_finalize_vm(tdx_launcher)
.map_err(StartMicrovmError::SecureVirtPrepare)?;
}
_ => return Err(StartMicrovmError::InvalidTee),
}
println!("Starting TEE/microVM.");
}
vmm.start_vcpus(vcpus)
.map_err(StartMicrovmError::Internal)?;
#[allow(clippy::arc_with_non_send_sync)]
let vmm = Arc::new(Mutex::new(vmm));
event_manager
.add_subscriber(vmm.clone())
.map_err(StartMicrovmError::RegisterEvent)?;
Ok(vmm)
}
fn load_external_kernel(
guest_mem: &GuestMemoryMmap,
arch_mem_info: &ArchMemoryInfo,
external_kernel: &ExternalKernel,
) -> std::result::Result<(GuestAddress, Option<InitrdConfig>, Option<String>), StartMicrovmError> {
let entry_addr = match external_kernel.format {
#[cfg(target_arch = "x86_64")]
KernelFormat::Raw => unreachable!(),
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
KernelFormat::Raw => {
let data: Vec<u8> = std::fs::read(external_kernel.path.clone())
.map_err(StartMicrovmError::RawOpenKernel)?;
guest_mem.write(&data, GuestAddress(0x8000_0000)).unwrap();
GuestAddress(0x8000_0000)
}
#[cfg(target_arch = "x86_64")]
KernelFormat::Elf => {
let mut file = File::options()
.read(true)
.write(false)
.open(external_kernel.path.clone())
.map_err(StartMicrovmError::ElfOpenKernel)?;
let load_result = loader::Elf::load(guest_mem, None, &mut file, None)
.map_err(StartMicrovmError::ElfLoadKernel)?;
load_result.kernel_load
}
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
KernelFormat::PeGz => {
let data: Vec<u8> = std::fs::read(external_kernel.path.clone())
.map_err(StartMicrovmError::PeGzOpenKernel)?;
if let Some(magic) = data
.windows(3)
.position(|window| window == [0x1f, 0x8b, 0x8])
{
debug!("Found GZIP header on PE file at: 0x{magic:x}");
let (_, compressed) = data.split_at(magic);
let mut gz = GzDecoder::new(compressed);
let mut kernel_data: Vec<u8> = Vec::new();
gz.read_to_end(&mut kernel_data)
.map_err(StartMicrovmError::PeGzDecoder)?;
guest_mem
.write(&kernel_data, GuestAddress(0x8000_0000))
.unwrap();
GuestAddress(0x8000_0000)
} else {
return Err(StartMicrovmError::PeGzInvalid);
}
}
#[cfg(target_arch = "x86_64")]
KernelFormat::ImageBz2 => {
let data: Vec<u8> = std::fs::read(external_kernel.path.clone())
.map_err(StartMicrovmError::ImageBz2OpenKernel)?;
if let Some(magic) = data
.windows(3)
.position(|window| window == [b'B', b'Z', b'h'])
{
debug!("Found BZIP2 header on Image file at: 0x{magic:x}");
let (_, compressed) = data.split_at(magic);
let mut kernel_data: Vec<u8> = Vec::new();
let mut bz2 = bzip2::read::BzDecoder::new(compressed);
bz2.read_to_end(&mut kernel_data)
.map_err(StartMicrovmError::ImageBz2Decoder)?;
let load_result = loader::Elf::load(
guest_mem,
None,
&mut std::io::Cursor::new(kernel_data),
None,
)
.map_err(StartMicrovmError::ImageBz2LoadKernel)?;
load_result.kernel_load
} else {
return Err(StartMicrovmError::ImageBz2Invalid);
}
}
#[cfg(target_arch = "x86_64")]
KernelFormat::ImageGz => {
let data: Vec<u8> = std::fs::read(external_kernel.path.clone())
.map_err(StartMicrovmError::ImageGzOpenKernel)?;
if let Some(magic) = data
.windows(3)
.position(|window| window == [0x1f, 0x8b, 0x8])
{
debug!("Found GZIP header on Image file at: 0x{magic:x}");
let (_, compressed) = data.split_at(magic);
let mut gz = GzDecoder::new(compressed);
let mut kernel_data: Vec<u8> = Vec::new();
gz.read_to_end(&mut kernel_data)
.map_err(StartMicrovmError::ImageGzDecoder)?;
let load_result = loader::Elf::load(
guest_mem,
None,
&mut std::io::Cursor::new(kernel_data),
None,
)
.map_err(StartMicrovmError::ImageGzLoadKernel)?;
load_result.kernel_load
} else {
return Err(StartMicrovmError::ImageGzInvalid);
}
}
#[cfg(target_arch = "x86_64")]
KernelFormat::ImageZstd => {
let data: Vec<u8> = std::fs::read(external_kernel.path.clone())
.map_err(StartMicrovmError::ImageZstdOpenKernel)?;
if let Some(magic) = data
.windows(4)
.position(|window| window == [0x28, 0xb5, 0x2f, 0xfd])
{
debug!("Found ZSTD header on Image file at: 0x{magic:x}");
let (_, zstd_data) = data.split_at(magic);
let mut kernel_data: Vec<u8> = Vec::new();
let _ = zstd::stream::copy_decode(zstd_data, &mut kernel_data);
let load_result = loader::Elf::load(
guest_mem,
None,
&mut std::io::Cursor::new(kernel_data),
None,
)
.map_err(StartMicrovmError::ImageZstdLoadKernel)?;
load_result.kernel_load
} else {
return Err(StartMicrovmError::ImageZstdInvalid);
}
}
_ => return Err(StartMicrovmError::KernelFormatUnsupported),
};
debug!("load_external_kernel: 0x{:x}", entry_addr.0);
let initrd_config = if let Some(initramfs_path) = &external_kernel.initramfs_path {
let data = std::fs::read(initramfs_path).map_err(StartMicrovmError::InitrdRead)?;
guest_mem
.write(&data, GuestAddress(arch_mem_info.initrd_addr))
.unwrap();
Some(InitrdConfig {
address: GuestAddress(arch_mem_info.initrd_addr),
size: data.len(),
})
} else {
None
};
Ok((entry_addr, initrd_config, external_kernel.cmdline.clone()))
}
fn load_payload(
_vm_resources: &VmResources,
guest_mem: GuestMemoryMmap,
_arch_mem_info: &ArchMemoryInfo,
payload: &Payload,
) -> std::result::Result<
(
GuestMemoryMmap,
GuestAddress,
Option<InitrdConfig>,
Option<String>,
),
StartMicrovmError,
> {
match payload {
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
Payload::KernelCopy => {
let (kernel_entry_addr, kernel_host_addr, kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &_vm_resources.kernel_bundle {
(
kernel_bundle.entry_addr,
kernel_bundle.host_addr,
kernel_bundle.guest_addr,
kernel_bundle.size,
)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let kernel_data =
unsafe { std::slice::from_raw_parts(kernel_host_addr as *mut u8, kernel_size) };
if kernel_guest_addr + kernel_size as u64 > _arch_mem_info.ram_last_addr {
return Err(StartMicrovmError::KernelDoesNotFit(
kernel_guest_addr,
kernel_size,
));
}
guest_mem
.write(kernel_data, GuestAddress(kernel_guest_addr))
.unwrap();
Ok((guest_mem, GuestAddress(kernel_entry_addr), None, None))
}
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
Payload::KernelMmap => {
let (kernel_entry_addr, kernel_host_addr, kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &_vm_resources.kernel_bundle {
(
kernel_bundle.entry_addr,
kernel_bundle.host_addr,
kernel_bundle.guest_addr,
kernel_bundle.size,
)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let kernel_region = unsafe {
MmapRegion::build_raw(kernel_host_addr as *mut u8, kernel_size, 0, 0)
.map_err(StartMicrovmError::InvalidKernelBundle)?
};
Ok((
guest_mem
.insert_region(Arc::new(
GuestRegionMmap::new(kernel_region, GuestAddress(kernel_guest_addr))
.ok_or_else(|| {
StartMicrovmError::GuestMemoryMmap(
"Failed to create GuestRegionMmap".to_string(),
)
})?,
))
.map_err(|e| StartMicrovmError::GuestMemoryMmap(format!("{e:?}")))?,
GuestAddress(kernel_entry_addr),
None,
None,
))
}
Payload::ExternalKernel(external_kernel) => {
let (entry_addr, initrd_config, cmdline) =
load_external_kernel(&guest_mem, _arch_mem_info, external_kernel)?;
Ok((guest_mem, entry_addr, initrd_config, cmdline))
}
#[cfg(test)]
Payload::Empty => Ok((guest_mem, GuestAddress(0), None, None)),
#[cfg(feature = "tee")]
Payload::Tee => {
let (kernel_host_addr, kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &_vm_resources.kernel_bundle {
(
kernel_bundle.host_addr,
kernel_bundle.guest_addr,
kernel_bundle.size,
)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let kernel_data =
unsafe { std::slice::from_raw_parts(kernel_host_addr as *mut u8, kernel_size) };
guest_mem
.write(kernel_data, GuestAddress(kernel_guest_addr))
.unwrap();
let (qboot_host_addr, qboot_size) =
if let Some(qboot_bundle) = &_vm_resources.qboot_bundle {
(qboot_bundle.host_addr, qboot_bundle.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let qboot_data =
unsafe { std::slice::from_raw_parts(qboot_host_addr as *mut u8, qboot_size) };
guest_mem
.write(qboot_data, GuestAddress(arch::FIRMWARE_START))
.unwrap();
let (initrd_host_addr, initrd_size) =
if let Some(initrd_bundle) = &_vm_resources.initrd_bundle {
(initrd_bundle.host_addr, initrd_bundle.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
let initrd_data =
unsafe { std::slice::from_raw_parts(initrd_host_addr as *mut u8, initrd_size) };
guest_mem
.write(initrd_data, GuestAddress(_arch_mem_info.initrd_addr))
.unwrap();
let initrd_config = InitrdConfig {
address: GuestAddress(_arch_mem_info.initrd_addr),
size: initrd_data.len(),
};
Ok((
guest_mem,
GuestAddress(arch::RESET_VECTOR),
Some(initrd_config),
None,
))
}
Payload::Firmware => Ok((guest_mem, GuestAddress(arch::RESET_VECTOR), None, None)),
}
}
pub struct PayloadConfig {
entry_addr: GuestAddress,
initrd_config: Option<InitrdConfig>,
kernel_cmdline: Option<String>,
}
pub fn create_guest_memory(
mem_size: usize,
vm_resources: &VmResources,
payload: &Payload,
) -> std::result::Result<
(GuestMemoryMmap, ArchMemoryInfo, ShmManager, PayloadConfig),
StartMicrovmError,
> {
let mem_size = mem_size << 20;
#[cfg(not(feature = "efi"))]
let (firmware_data, firmware_size) = if let Some(firmware) = &vm_resources.firmware_config {
let data = std::fs::read(firmware.path.clone()).map_err(StartMicrovmError::FirmwareRead)?;
let len = data.len();
(Some(data), Some(len))
} else {
(None, None)
};
#[cfg(feature = "efi")]
let (firmware_data, firmware_size) = (Some(EDK2_BINARY), Some(EDK2_BINARY.len()));
#[cfg(target_arch = "x86_64")]
let (arch_mem_info, mut arch_mem_regions) = match payload {
#[cfg(not(feature = "tee"))]
Payload::KernelMmap => {
let (kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &vm_resources.kernel_bundle {
(kernel_bundle.guest_addr, kernel_bundle.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
arch::arch_memory_regions(mem_size, Some(kernel_guest_addr), kernel_size, 0, None)
}
Payload::ExternalKernel(external_kernel) => arch::arch_memory_regions(
mem_size,
None,
0,
external_kernel.initramfs_size,
firmware_size,
),
#[cfg(feature = "tee")]
Payload::Tee => {
let (kernel_guest_addr, kernel_size) =
if let Some(kernel_bundle) = &vm_resources.kernel_bundle {
(kernel_bundle.guest_addr, kernel_bundle.size)
} else {
return Err(StartMicrovmError::MissingKernelConfig);
};
arch::arch_memory_regions(mem_size, Some(kernel_guest_addr), kernel_size, 0, None)
}
#[cfg(test)]
Payload::Empty => arch::arch_memory_regions(mem_size, None, 0, 0, None),
Payload::Firmware => arch::arch_memory_regions(mem_size, None, 0, 0, firmware_size),
};
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
let (arch_mem_info, mut arch_mem_regions) = match payload {
Payload::ExternalKernel(external_kernel) => {
arch::arch_memory_regions(mem_size, external_kernel.initramfs_size, None)
}
_ => arch::arch_memory_regions(mem_size, 0, firmware_size),
};
let mut shm_manager = ShmManager::new(&arch_mem_info);
#[cfg(not(feature = "tee"))]
for (index, fs) in vm_resources.fs.iter().enumerate() {
if let Some(shm_size) = fs.shm_size {
shm_manager
.create_fs_region(index, shm_size)
.map_err(StartMicrovmError::ShmCreate)?;
}
}
if vm_resources.gpu_virgl_flags.is_some() {
let size = vm_resources.gpu_shm_size.unwrap_or(1 << 33);
shm_manager
.create_gpu_region(size)
.map_err(StartMicrovmError::ShmCreate)?;
}
arch_mem_regions.extend(shm_manager.regions());
let guest_mem = GuestMemoryMmap::from_ranges(&arch_mem_regions)
.map_err(|e| StartMicrovmError::GuestMemoryMmap(format!("{e:?}")))?;
let (guest_mem, entry_addr, initrd_config, cmdline) =
load_payload(vm_resources, guest_mem, &arch_mem_info, payload)?;
if !matches!(payload, Payload::ExternalKernel(_)) {
if let Some(firmware_data) = firmware_data.as_ref() {
guest_mem
.write(firmware_data, GuestAddress(arch_mem_info.firmware_addr))
.map_err(StartMicrovmError::FirmwareInvalidAddress)?;
}
}
let payload_config = PayloadConfig {
entry_addr,
initrd_config,
kernel_cmdline: cmdline.clone(),
};
Ok((guest_mem, arch_mem_info, shm_manager, payload_config))
}
#[cfg(all(target_arch = "x86_64", not(feature = "tee")))]
fn load_cmdline(vmm: &Vmm) -> std::result::Result<(), StartMicrovmError> {
kernel::loader::load_cmdline(
vmm.guest_memory(),
GuestAddress(arch::x86_64::layout::CMDLINE_START),
&vmm.kernel_cmdline
.as_cstring()
.map_err(StartMicrovmError::LoadCommandline)?,
)
.map_err(StartMicrovmError::LoadCommandline)
}
#[cfg(all(target_os = "linux", not(feature = "tee")))]
pub(crate) fn setup_vm(
guest_memory: &GuestMemoryMmap,
_nested_enabled: bool,
) -> std::result::Result<Vm, StartMicrovmError> {
let kvm = KvmContext::new()
.map_err(Error::KvmContext)
.map_err(StartMicrovmError::Internal)?;
let mut vm = Vm::new(kvm.fd())
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
vm.memory_init(guest_memory, kvm.max_memslots())
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
Ok(vm)
}
#[cfg(all(target_os = "linux", feature = "tee"))]
pub(crate) fn setup_vm(
kvm: &KvmContext,
guest_memory: &GuestMemoryMmap,
resources: &super::resources::VmResources,
#[cfg(feature = "tdx")] _sender: Sender<WorkerMessage>,
) -> std::result::Result<Vm, StartMicrovmError> {
let mut vm = Vm::new(
kvm.fd(),
resources.tee_config(),
#[cfg(feature = "tdx")]
_sender,
)
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
vm.memory_init(guest_memory, kvm.max_memslots())
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
Ok(vm)
}
#[cfg(target_os = "macos")]
pub(crate) fn setup_vm(
guest_memory: &GuestMemoryMmap,
nested_enabled: bool,
) -> std::result::Result<Vm, StartMicrovmError> {
let mut vm = Vm::new(nested_enabled)
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
vm.memory_init(guest_memory)
.map_err(Error::Vm)
.map_err(StartMicrovmError::Internal)?;
Ok(vm)
}
pub fn setup_serial_device(
event_manager: &mut EventManager,
input: Option<Box<dyn devices::legacy::ReadableFd + Send>>,
out: Option<Box<dyn io::Write + Send>>,
) -> std::result::Result<Arc<Mutex<Serial>>, StartMicrovmError> {
let interrupt_evt = EventFd::new(utils::eventfd::EFD_NONBLOCK)
.map_err(Error::EventFd)
.map_err(StartMicrovmError::Internal)?;
let has_input = input.is_some();
let serial = Arc::new(Mutex::new(Serial::new(interrupt_evt, out, input)));
if has_input {
if let Err(e) = event_manager.add_subscriber(serial.clone()) {
warn!("Could not add serial input event to epoll: {e:?}");
}
}
Ok(serial)
}
#[cfg(target_arch = "x86_64")]
fn attach_legacy_devices(
vm: &Vm,
split_irqchip: bool,
pio_device_manager: &mut PortIODeviceManager,
mmio_device_manager: &mut MMIODeviceManager,
intc: Option<Arc<Mutex<IrqChipDevice>>>,
) -> std::result::Result<(), StartMicrovmError> {
pio_device_manager
.register_devices()
.map_err(Error::LegacyIOBus)
.map_err(StartMicrovmError::Internal)?;
if split_irqchip {
mmio_device_manager
.register_mmio_ioapic(intc)
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
}
macro_rules! register_irqfd_evt {
($evt: ident, $index: expr) => {{
vm.fd()
.register_irqfd(&pio_device_manager.$evt, $index)
.map_err(|e| {
Error::LegacyIOBus(device_manager::legacy::Error::EventFd(
io::Error::from_raw_os_error(e.errno()),
))
})
.map_err(StartMicrovmError::Internal)?;
}};
}
register_irqfd_evt!(com_evt_1, 4);
register_irqfd_evt!(com_evt_2, 3);
register_irqfd_evt!(com_evt_3, 4);
register_irqfd_evt!(com_evt_4, 3);
register_irqfd_evt!(kbd_evt, 1);
Ok(())
}
#[cfg(all(
any(target_arch = "aarch64", target_arch = "riscv64"),
target_os = "linux"
))]
fn attach_legacy_devices(
vm: &Vm,
mmio_device_manager: &mut MMIODeviceManager,
kernel_cmdline: &mut kernel::cmdline::Cmdline,
intc: IrqChip,
serial: Vec<Arc<Mutex<Serial>>>,
) -> std::result::Result<(), StartMicrovmError> {
for s in serial {
mmio_device_manager
.register_mmio_serial(vm.fd(), kernel_cmdline, intc.clone(), s)
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
}
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
mmio_device_manager
.register_mmio_rtc(vm.fd())
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
Ok(())
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
fn attach_legacy_devices(
vm: &Vm,
mmio_device_manager: &mut MMIODeviceManager,
kernel_cmdline: &mut kernel::cmdline::Cmdline,
intc: IrqChip,
serial: Vec<Arc<Mutex<Serial>>>,
event_manager: &mut EventManager,
shutdown_efd: Option<EventFd>,
) -> Result<(), StartMicrovmError> {
for s in serial {
mmio_device_manager
.register_mmio_serial(vm, kernel_cmdline, intc.clone(), s)
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
}
mmio_device_manager
.register_mmio_rtc(vm, intc.clone())
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
mmio_device_manager
.register_mmio_gic(vm, intc.clone())
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
if let Some(shutdown_efd) = shutdown_efd {
mmio_device_manager
.register_mmio_gpio(vm, intc.clone(), event_manager, shutdown_efd)
.map_err(Error::RegisterMMIODevice)
.map_err(StartMicrovmError::Internal)?;
}
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[allow(clippy::too_many_arguments)]
fn create_vcpus_x86_64(
vm: &Vm,
vcpu_config: &VcpuConfig,
guest_mem: &GuestMemoryMmap,
entry_addr: GuestAddress,
io_bus: &devices::Bus,
exit_evt: &EventFd,
kernel_boot: bool,
#[cfg(feature = "tee")] pm_sender: Sender<WorkerMessage>,
) -> super::Result<Vec<Vcpu>> {
let mut vcpus = Vec::with_capacity(vcpu_config.vcpu_count as usize);
for cpu_index in 0..vcpu_config.vcpu_count {
let mut vcpu = Vcpu::new_x86_64(
cpu_index,
vm.fd(),
vm.supported_cpuid().clone(),
vm.supported_msrs().clone(),
io_bus.clone(),
exit_evt.try_clone().map_err(Error::EventFd)?,
#[cfg(feature = "tee")]
pm_sender.clone(),
)
.map_err(Error::Vcpu)?;
vcpu.configure_x86_64(guest_mem, entry_addr, vcpu_config, kernel_boot)
.map_err(Error::Vcpu)?;
vcpus.push(vcpu);
}
Ok(vcpus)
}
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
fn create_vcpus_aarch64(
vm: &Vm,
vcpu_config: &VcpuConfig,
mem_info: &ArchMemoryInfo,
entry_addr: GuestAddress,
exit_evt: &EventFd,
) -> super::Result<Vec<Vcpu>> {
let mut vcpus = Vec::with_capacity(vcpu_config.vcpu_count as usize);
for cpu_index in 0..vcpu_config.vcpu_count {
let mut vcpu = Vcpu::new_aarch64(
cpu_index,
vm.fd(),
exit_evt.try_clone().map_err(Error::EventFd)?,
)
.map_err(Error::Vcpu)?;
vcpu.configure_aarch64(vm.fd(), mem_info, entry_addr)
.map_err(Error::Vcpu)?;
vcpus.push(vcpu);
}
Ok(vcpus)
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
fn create_vcpus_aarch64(
_vm: &Vm,
vcpu_config: &VcpuConfig,
mem_info: &ArchMemoryInfo,
entry_addr: GuestAddress,
exit_evt: &EventFd,
vcpu_list: Arc<VcpuList>,
nested_enabled: bool,
) -> super::Result<Vec<Vcpu>> {
let mut vcpus = Vec::with_capacity(vcpu_config.vcpu_count as usize);
let mut boot_senders: HashMap<u64, Sender<u64>> = HashMap::new();
for cpu_index in 0..vcpu_config.vcpu_count {
let (boot_sender, boot_receiver) = if cpu_index != 0 {
let (boot_sender, boot_receiver) = unbounded();
(Some(boot_sender), Some(boot_receiver))
} else {
(None, None)
};
let mut vcpu = Vcpu::new_aarch64(
cpu_index,
entry_addr,
boot_receiver,
exit_evt.try_clone().map_err(Error::EventFd)?,
vcpu_list.clone(),
nested_enabled,
)
.map_err(Error::Vcpu)?;
vcpu.configure_aarch64(mem_info).map_err(Error::Vcpu)?;
if let Some(boot_sender) = boot_sender {
boot_senders.insert(vcpu.get_mpidr(), boot_sender);
}
vcpus.push(vcpu);
}
vcpus[0].set_boot_senders(boot_senders);
Ok(vcpus)
}
#[cfg(all(target_arch = "riscv64", target_os = "linux"))]
fn create_vcpus_riscv64(
vm: &Vm,
vcpu_config: &VcpuConfig,
guest_mem: &GuestMemoryMmap,
entry_addr: GuestAddress,
exit_evt: &EventFd,
) -> super::Result<Vec<Vcpu>> {
let mut vcpus = Vec::with_capacity(vcpu_config.vcpu_count as usize);
for cpu_index in 0..vcpu_config.vcpu_count {
let mut vcpu = Vcpu::new_riscv64(
cpu_index,
vm.fd(),
exit_evt.try_clone().map_err(Error::EventFd)?,
)
.map_err(Error::Vcpu)?;
vcpu.configure_riscv64(vm.fd(), guest_mem, entry_addr)
.map_err(Error::Vcpu)?;
vcpus.push(vcpu);
}
Ok(vcpus)
}
fn attach_mmio_device(
vmm: &mut Vmm,
id: String,
intc: IrqChip,
device: Arc<Mutex<dyn VirtioDevice>>,
) -> std::result::Result<(), device_manager::mmio::Error> {
let mmio_device = MmioTransport::new(vmm.guest_memory().clone(), intc, device)?;
let type_id = mmio_device.locked_device().device_type();
let _cmdline = &mut vmm.kernel_cmdline;
#[cfg(target_os = "linux")]
let (_mmio_base, _irq) =
vmm.mmio_device_manager
.register_mmio_device(vmm.vm.fd(), mmio_device, type_id, id)?;
#[cfg(target_os = "macos")]
let (_mmio_base, _irq) =
vmm.mmio_device_manager
.register_mmio_device(mmio_device, type_id, id)?;
#[cfg(target_arch = "x86_64")]
vmm.mmio_device_manager
.add_device_to_cmdline(_cmdline, _mmio_base, _irq)?;
Ok(())
}
#[cfg(not(any(feature = "tee", feature = "aws-nitro")))]
fn attach_fs_devices(
vmm: &mut Vmm,
fs_devs: &[FsDeviceConfig],
shm_manager: &mut ShmManager,
#[cfg(not(feature = "tee"))] export_table: Option<ExportTable>,
intc: IrqChip,
exit_code: Arc<AtomicI32>,
#[cfg(target_os = "macos")] map_sender: Sender<WorkerMessage>,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
for (i, config) in fs_devs.iter().enumerate() {
let fs = Arc::new(Mutex::new(
devices::virtio::Fs::new(
config.fs_id.clone(),
config.shared_dir.clone(),
exit_code.clone(),
config.read_only,
config.virtual_entries.clone(),
)
.unwrap(),
));
let id = format!("{}{}", String::from(fs.lock().unwrap().id()), i);
if let Some(shm_region) = shm_manager.fs_region(i) {
fs.lock().unwrap().set_shm_region(VirtioShmRegion {
host_addr: vmm
.guest_memory
.get_host_address(shm_region.guest_addr)
.map_err(StartMicrovmError::ShmHostAddr)? as u64,
guest_addr: shm_region.guest_addr.raw_value(),
size: shm_region.size,
});
}
#[cfg(not(feature = "tee"))]
if let Some(export_table) = export_table.as_ref() {
fs.lock().unwrap().set_export_table(export_table.clone());
}
#[cfg(target_os = "macos")]
fs.lock().unwrap().set_map_sender(map_sender.clone());
attach_mmio_device(vmm, id, intc.clone(), fs).map_err(RegisterFsDevice)?;
}
Ok(())
}
fn autoconfigure_console_ports(
vmm: &mut Vmm,
vm_resources: &VmResources,
cfg: Option<&DefaultVirtioConsoleConfig>,
creating_implicit_console: bool,
) -> std::result::Result<Vec<PortDescription>, StartMicrovmError> {
use self::StartMicrovmError::*;
let mut console_output_path: Option<PathBuf> = None;
if let Some(path) = vm_resources.console_output.clone() {
if !vm_resources.disable_implicit_console && creating_implicit_console {
console_output_path = Some(path)
}
}
if let Some(console_output_path) = console_output_path {
let file = File::create(console_output_path).map_err(OpenConsoleFile)?;
let stdin_fd = unsafe { BorrowedFd::borrow_raw(STDIN_FILENO) };
let term_fd = if isatty(stdin_fd).is_ok_and(|v| v) {
port_io::term_fd(stdin_fd.as_raw_fd()).unwrap()
} else {
port_io::term_fixed_size(0, 0)
};
Ok(vec![PortDescription::console(
Some(port_io::input_empty().unwrap()),
Some(port_io::output_file(file).unwrap()),
term_fd,
)])
} else {
let (input_fd, output_fd, err_fd) = match cfg {
Some(c) => (c.input_fd, c.output_fd, c.err_fd),
None => (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO),
};
let input_is_terminal =
input_fd >= 0 && isatty(unsafe { BorrowedFd::borrow_raw(input_fd) }).unwrap_or(false);
let output_is_terminal =
output_fd >= 0 && isatty(unsafe { BorrowedFd::borrow_raw(output_fd) }).unwrap_or(false);
let error_is_terminal =
err_fd >= 0 && isatty(unsafe { BorrowedFd::borrow_raw(err_fd) }).unwrap_or(false);
let term_fd = if input_is_terminal {
Some(unsafe { BorrowedFd::borrow_raw(input_fd) })
} else if output_is_terminal {
Some(unsafe { BorrowedFd::borrow_raw(output_fd) })
} else if error_is_terminal {
Some(unsafe { BorrowedFd::borrow_raw(err_fd) })
} else {
None
};
let forwarding_sigint;
let console_input = if input_is_terminal && input_fd >= 0 {
forwarding_sigint = false;
Some(port_io::input_to_raw_fd_dup(input_fd).unwrap())
} else {
#[cfg(target_os = "linux")]
{
forwarding_sigint = true;
let sigint_input = port_io::PortInputSigInt::new();
let sigint_input_fd = sigint_input.sigint_evt().as_raw_fd();
register_sigint_handler(sigint_input_fd).map_err(RegisterFsSigwinch)?;
Some(Box::new(sigint_input) as _)
}
#[cfg(not(target_os = "linux"))]
{
forwarding_sigint = false;
Some(port_io::input_empty().unwrap())
}
};
let console_output = if output_is_terminal && output_fd >= 0 {
Some(port_io::output_to_raw_fd_dup(output_fd).unwrap())
} else {
Some(port_io::output_to_log_as_err())
};
let terminal_properties = term_fd
.map(|fd| port_io::term_fd(fd.as_raw_fd()).unwrap())
.unwrap_or_else(|| port_io::term_fixed_size(0, 0));
setup_terminal_raw_mode(vmm, term_fd, forwarding_sigint);
let mut ports = vec![PortDescription::console(
console_input,
console_output,
terminal_properties,
)];
if input_fd >= 0 && !input_is_terminal {
ports.push(PortDescription::input_pipe(
"krun-stdin",
port_io::input_to_raw_fd_dup(input_fd).unwrap(),
));
}
if output_fd >= 0 && !output_is_terminal {
ports.push(PortDescription::output_pipe(
"krun-stdout",
port_io::output_to_raw_fd_dup(output_fd).unwrap(),
));
};
if err_fd >= 0 && !error_is_terminal {
ports.push(PortDescription::output_pipe(
"krun-stderr",
port_io::output_to_raw_fd_dup(err_fd).unwrap(),
));
}
Ok(ports)
}
}
fn setup_terminal_raw_mode(
vmm: &mut Vmm,
term_fd: Option<BorrowedFd<'_>>,
handle_signals_by_terminal: bool,
) {
if let Some(term_fd) = term_fd {
match term_set_raw_mode(term_fd, handle_signals_by_terminal) {
Ok(old_mode) => {
let raw_fd = term_fd.as_raw_fd();
vmm.exit_observers.push(Arc::new(Mutex::new(move || {
if let Err(e) =
term_restore_mode(unsafe { BorrowedFd::borrow_raw(raw_fd) }, &old_mode)
{
log::error!("Failed to restore terminal mode: {e}")
}
})));
}
Err(e) => {
log::error!("Failed to set terminal to raw mode: {e}")
}
};
}
}
fn create_explicit_ports(
vmm: &mut Vmm,
port_configs: &[PortConfig],
) -> std::result::Result<Vec<PortDescription>, StartMicrovmError> {
let mut ports = Vec::with_capacity(port_configs.len());
for port_cfg in port_configs {
let port_desc = match port_cfg {
PortConfig::Tty { name, tty_fd } => {
assert!(*tty_fd > 0, "PortConfig::Tty must have a valid tty_fd");
let term_fd = unsafe { BorrowedFd::borrow_raw(*tty_fd) };
setup_terminal_raw_mode(vmm, Some(term_fd), false);
PortDescription {
name: name.clone().into(),
input: Some(port_io::input_to_raw_fd_dup(*tty_fd).unwrap()),
output: Some(port_io::output_to_raw_fd_dup(*tty_fd).unwrap()),
terminal: Some(port_io::term_fd(*tty_fd).unwrap()),
}
}
PortConfig::InOut {
name,
input_fd,
output_fd,
} => PortDescription {
name: name.clone().into(),
input: if *input_fd < 0 {
None
} else {
Some(port_io::input_to_raw_fd_dup(*input_fd).unwrap())
},
output: if *output_fd < 0 {
None
} else {
Some(port_io::output_to_raw_fd_dup(*output_fd).unwrap())
},
terminal: None,
},
};
ports.push(port_desc);
}
Ok(ports)
}
fn attach_console_devices(
vmm: &mut Vmm,
event_manager: &mut EventManager,
intc: IrqChip,
vm_resources: &VmResources,
cfg: Option<&VirtioConsoleConfigMode>,
id_number: u32,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
let creating_implicit_console = cfg.is_none();
let ports = match cfg {
None => autoconfigure_console_ports(vmm, vm_resources, None, creating_implicit_console)?,
Some(VirtioConsoleConfigMode::Autoconfigure(autocfg)) => autoconfigure_console_ports(
vmm,
vm_resources,
Some(autocfg),
creating_implicit_console,
)?,
Some(VirtioConsoleConfigMode::Explicit(ports)) => create_explicit_ports(vmm, ports)?,
};
let console = Arc::new(Mutex::new(devices::virtio::Console::new(ports).unwrap()));
vmm.exit_observers.push(console.clone());
event_manager
.add_subscriber(console.clone())
.map_err(RegisterEvent)?;
#[cfg(target_os = "linux")]
register_sigwinch_handler(console.lock().unwrap().get_sigwinch_fd())
.map_err(RegisterFsSigwinch)?;
attach_mmio_device(vmm, format!("hvc{id_number}"), intc, console)
.map_err(RegisterConsoleDevice)?;
Ok(())
}
#[cfg(feature = "net")]
fn attach_net_devices(
vmm: &mut Vmm,
net_devices: &NetBuilder,
intc: IrqChip,
) -> Result<(), StartMicrovmError> {
for net_device in net_devices.list.iter() {
let id = net_device.lock().unwrap().id().to_string();
attach_mmio_device(vmm, id, intc.clone(), net_device.clone())
.map_err(StartMicrovmError::RegisterNetDevice)?;
}
Ok(())
}
fn attach_unixsock_vsock_device(
vmm: &mut Vmm,
unix_vsock: &Arc<Mutex<Vsock>>,
event_manager: &mut EventManager,
intc: IrqChip,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
event_manager
.add_subscriber(unix_vsock.clone())
.map_err(RegisterEvent)?;
let id = String::from(unix_vsock.lock().unwrap().id());
attach_mmio_device(vmm, id, intc, unix_vsock.clone()).map_err(RegisterVsockDevice)?;
Ok(())
}
#[cfg(not(feature = "tee"))]
fn attach_balloon_device(
vmm: &mut Vmm,
event_manager: &mut EventManager,
intc: IrqChip,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
let balloon = Arc::new(Mutex::new(devices::virtio::Balloon::new().unwrap()));
event_manager
.add_subscriber(balloon.clone())
.map_err(RegisterEvent)?;
let id = String::from(balloon.lock().unwrap().id());
attach_mmio_device(vmm, id, intc.clone(), balloon).map_err(RegisterBalloonDevice)?;
Ok(())
}
#[cfg(feature = "blk")]
fn attach_block_devices(
vmm: &mut Vmm,
block_devs: &BlockBuilder,
intc: IrqChip,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
for block in block_devs.list.iter() {
let id = String::from(block.lock().unwrap().id());
attach_mmio_device(vmm, id, intc.clone(), block.clone()).map_err(RegisterBlockDevice)?;
}
Ok(())
}
#[cfg(not(feature = "tee"))]
fn attach_rng_device(
vmm: &mut Vmm,
event_manager: &mut EventManager,
intc: IrqChip,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
let rng = Arc::new(Mutex::new(devices::virtio::Rng::new().unwrap()));
event_manager
.add_subscriber(rng.clone())
.map_err(RegisterEvent)?;
let id = String::from(rng.lock().unwrap().id());
attach_mmio_device(vmm, id, intc.clone(), rng).map_err(RegisterRngDevice)?;
Ok(())
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn attach_gpu_device(
vmm: &mut Vmm,
shm_manager: &mut ShmManager,
#[cfg(not(feature = "tee"))] mut export_table: Option<ExportTable>,
intc: IrqChip,
virgl_flags: u32,
displays: Box<[DisplayInfo]>,
display_backend: DisplayBackend<'static>,
#[cfg(target_os = "macos")] map_sender: Sender<WorkerMessage>,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
let gpu = Arc::new(Mutex::new(
devices::virtio::Gpu::new(
virgl_flags,
displays,
display_backend,
#[cfg(target_os = "macos")]
map_sender,
)
.unwrap(),
));
let id = String::from(gpu.lock().unwrap().id());
if let Some(shm_region) = shm_manager.gpu_region() {
gpu.lock().unwrap().set_shm_region(VirtioShmRegion {
host_addr: vmm
.guest_memory
.get_host_address(shm_region.guest_addr)
.map_err(StartMicrovmError::ShmHostAddr)? as u64,
guest_addr: shm_region.guest_addr.raw_value(),
size: shm_region.size,
});
}
#[cfg(not(feature = "tee"))]
if let Some(export_table) = export_table.take() {
gpu.lock().unwrap().set_export_table(export_table);
}
attach_mmio_device(vmm, id, intc, gpu).map_err(RegisterGpuDevice)?;
Ok(())
}
#[cfg(feature = "input")]
fn attach_input_devices(
vmm: &mut Vmm,
input_backends: &[(
krun_input::InputConfigBackend<'static>,
krun_input::InputEventProviderBackend<'static>,
)],
intc: IrqChip,
) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
for (index, (config_backend, events_backend)) in input_backends.iter().enumerate() {
let input_device = Arc::new(Mutex::new(
devices::virtio::input::Input::new(*config_backend, *events_backend).unwrap(),
));
let id = format!("input{}", index);
attach_mmio_device(vmm, id, intc.clone(), input_device).map_err(RegisterInputDevice)?;
}
Ok(())
}
#[cfg(feature = "snd")]
fn attach_snd_device(vmm: &mut Vmm, intc: IrqChip) -> std::result::Result<(), StartMicrovmError> {
use self::StartMicrovmError::*;
let snd = Arc::new(Mutex::new(devices::virtio::Snd::new().unwrap()));
let id = String::from(snd.lock().unwrap().id());
attach_mmio_device(vmm, id, intc, snd).map_err(RegisterSndDevice)?;
Ok(())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::vmm_config::kernel_bundle::KernelBundle;
#[allow(unused)]
fn default_guest_memory(
mem_size_mib: usize,
) -> std::result::Result<
(GuestMemoryMmap, ArchMemoryInfo, ShmManager, PayloadConfig),
StartMicrovmError,
> {
let mut vm_resources = VmResources::default();
vm_resources.kernel_bundle = Some(KernelBundle {
host_addr: 0x1000,
guest_addr: 0x1000,
entry_addr: 0x1000,
size: 0x1000,
});
create_guest_memory(mem_size_mib, &vm_resources, &Payload::Empty)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_create_vcpus_x86_64() {
let vcpu_count = 2;
let vcpu_config = VcpuConfig {
vcpu_count,
ht_enabled: false,
cpu_template: None,
nested_enabled: false,
};
let (guest_memory, _arch_memory_info, _shm_manager, _payload_config) =
default_guest_memory(128).unwrap();
let vm = setup_vm(&guest_memory, false).unwrap();
let _kvmioapic = KvmIoapic::new(&vm.fd()).unwrap();
let entry_addr = GuestAddress(0);
let bus = devices::Bus::new();
let vcpu_vec = create_vcpus_x86_64(
&vm,
&vcpu_config,
&guest_memory,
entry_addr,
&bus,
&EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap(),
true,
)
.unwrap();
assert_eq!(vcpu_vec.len(), vcpu_count as usize);
}
#[test]
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
fn test_create_vcpus_aarch64() {
let (guest_memory, arch_memory_info, _shm_manager, _payload_config) =
default_guest_memory(128).unwrap();
let vm = setup_vm(&guest_memory, false).unwrap();
let vcpu_count = 2;
let vcpu_config = VcpuConfig {
vcpu_count,
ht_enabled: false,
cpu_template: None,
nested_enabled: false,
};
let entry_addr = GuestAddress(0);
let vcpu_vec = create_vcpus_aarch64(
&vm,
&vcpu_config,
&arch_memory_info,
entry_addr,
&EventFd::new(utils::eventfd::EFD_NONBLOCK).unwrap(),
)
.unwrap();
assert_eq!(vcpu_vec.len(), vcpu_count as usize);
}
#[test]
fn test_error_messages() {
use crate::builder::StartMicrovmError::*;
let err = AttachBlockDevice(io::Error::from_raw_os_error(0));
let _ = format!("{err}{err:?}");
let err = CreateRateLimiter(io::Error::from_raw_os_error(0));
let _ = format!("{err}{err:?}");
let err = Internal(Error::Serial(io::Error::from_raw_os_error(0)));
let _ = format!("{err}{err:?}");
let err = InvalidKernelBundle(vm_memory::mmap::MmapRegionError::InvalidPointer);
let _ = format!("{err}{err:?}");
let err = KernelCmdline(String::from("dummy --cmdline"));
let _ = format!("{err}{err:?}");
let err = LoadCommandline(kernel::cmdline::Error::TooLarge);
let _ = format!("{err}{err:?}");
let err = MicroVMAlreadyRunning;
let _ = format!("{err}{err:?}");
let err = MissingKernelConfig;
let _ = format!("{err}{err:?}");
let err = MissingMemSizeConfig;
let _ = format!("{err}{err:?}");
let err = NetDeviceNotConfigured;
let _ = format!("{err}{err:?}");
let err = OpenBlockDevice(io::Error::from_raw_os_error(0));
let _ = format!("{err}{err:?}");
let err = RegisterBlockDevice(device_manager::mmio::Error::EventFd(
io::Error::from_raw_os_error(0),
));
let _ = format!("{err}{err:?}");
let err = RegisterEvent(EventManagerError::EpollCreate(
io::Error::from_raw_os_error(0),
));
let _ = format!("{err}{err:?}");
let err = RegisterNetDevice(device_manager::mmio::Error::EventFd(
io::Error::from_raw_os_error(0),
));
let _ = format!("{err}{err:?}");
let err = RegisterVsockDevice(device_manager::mmio::Error::EventFd(
io::Error::from_raw_os_error(0),
));
let _ = format!("{err}{err:?}");
}
#[test]
fn test_kernel_cmdline_err_to_startuvm_err() {
let err = StartMicrovmError::from(kernel::cmdline::Error::HasSpace);
let _ = format!("{err}{err:?}");
}
}