#[macro_use]
extern crate log;
pub mod builder;
pub(crate) mod device_manager;
#[cfg(unix)]
pub mod exit_signal;
pub mod resources;
#[cfg(target_os = "linux")]
pub mod signal_handler;
pub mod vmm_config;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
use crate::linux::vstate;
#[cfg(target_os = "macos")]
mod macos;
mod metrics;
#[cfg(unix)]
mod terminal;
#[cfg(target_os = "windows")]
mod windows;
pub mod worker;
#[cfg(target_os = "macos")]
use macos::vstate;
#[cfg(target_os = "windows")]
use windows::vstate;
use std::fmt::{Display, Formatter};
use std::io;
#[cfg(unix)]
use std::os::fd::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use std::time::Duration;
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
use crate::device_manager::legacy::PortIODeviceManager;
use crate::device_manager::mmio::MMIODeviceManager;
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::vstate::VcpuEvent;
use crate::vstate::{Vcpu, VcpuHandle, VcpuResponse, Vm};
use crate::resources::VcpuPlacementResult;
use arch::{ArchMemoryInfo, InitrdConfig};
#[cfg(any(target_os = "macos", target_os = "windows"))]
use crossbeam_channel::Sender;
#[cfg(all(
any(target_arch = "aarch64", target_arch = "riscv64"),
any(target_arch = "aarch64", not(target_os = "windows"))
))]
use devices::fdt;
use devices::legacy::IrqChip;
use devices::virtio::VmmExitObserver;
use devices::{BusDevice, DeviceType};
use kernel::cmdline::Cmdline as KernelCmdline;
use polly::event_manager::{self, EventManager, Subscriber};
use utils::epoll::{EpollEvent, EventSet};
use utils::eventfd::EventFd;
use vm_memory::GuestMemoryMmap;
pub const FC_EXIT_CODE_OK: u8 = 0;
pub const FC_EXIT_CODE_GENERIC_ERROR: u8 = 1;
pub const FC_EXIT_CODE_UNEXPECTED_ERROR: u8 = 2;
pub const FC_EXIT_CODE_BAD_SYSCALL: u8 = 148;
pub const FC_EXIT_CODE_SIGBUS: u8 = 149;
pub const FC_EXIT_CODE_SIGSEGV: u8 = 150;
pub const FC_EXIT_CODE_BAD_CONFIGURATION: u8 = 152;
pub const FC_EXIT_CODE_ARG_PARSING: u8 = 153;
#[derive(Debug)]
pub enum Error {
ConfigureSystem(arch::Error),
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
CreateLegacyDevice(device_manager::legacy::Error),
EventFd(io::Error),
EventManager(event_manager::Error),
I8042Error(devices::legacy::I8042DeviceError),
KernelFile(io::Error),
KvmContext(vstate::Error),
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
LegacyIOBus(device_manager::legacy::Error),
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
LegacyPioBus(devices::BusError),
LoadCommandline(kernel::cmdline::Error),
RegisterMMIODevice(device_manager::mmio::Error),
Serial(io::Error),
#[cfg(all(
any(target_arch = "aarch64", target_arch = "riscv64"),
any(target_arch = "aarch64", not(target_os = "windows"))
))]
SetupFDT(devices::fdt::Error),
TimerFd(io::Error),
Vcpu(vstate::Error),
VcpuEvent(vstate::Error),
VcpuHandle(vstate::Error),
VcpuResume,
VcpuSpawn(std::io::Error),
Vm(vstate::Error),
VmmObserverInit(utils::errno::Error),
VmmObserverTeardown(utils::errno::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
use self::Error::*;
match self {
ConfigureSystem(e) => write!(f, "System configuration error: {e:?}"),
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
CreateLegacyDevice(e) => write!(f, "Error creating legacy device: {e:?}"),
EventFd(e) => write!(f, "Event fd error: {e}"),
EventManager(e) => write!(f, "Event manager error: {e:?}"),
I8042Error(e) => write!(f, "I8042 error: {e}"),
KernelFile(e) => write!(f, "Cannot access kernel file: {e}"),
KvmContext(e) => write!(f, "Failed to validate KVM support: {e:?}"),
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
LegacyIOBus(e) => write!(f, "Cannot add devices to the legacy I/O Bus. {e}"),
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
LegacyPioBus(e) => write!(f, "Cannot add devices to the legacy port I/O bus. {e}"),
LoadCommandline(e) => write!(f, "Cannot load command line: {e}"),
RegisterMMIODevice(e) => write!(f, "Cannot add a device to the MMIO Bus. {e}"),
Serial(e) => write!(f, "Error writing to the serial console: {e:?}"),
#[cfg(all(
any(target_arch = "aarch64", target_arch = "riscv64"),
any(target_arch = "aarch64", not(target_os = "windows"))
))]
SetupFDT(e) => write!(f, "Error generating or writing FDT: {e:?}"),
TimerFd(e) => write!(f, "Error creating timer fd: {e}"),
Vcpu(e) => write!(f, "Vcpu error: {e}"),
VcpuEvent(e) => write!(f, "Cannot send event to vCPU. {e:?}"),
VcpuHandle(e) => write!(f, "Cannot create a vCPU handle. {e}"),
VcpuResume => write!(f, "vCPUs resume failed."),
VcpuSpawn(e) => write!(f, "Cannot spawn Vcpu thread: {e}"),
Vm(e) => write!(f, "Vm error: {e}"),
VmmObserverInit(e) => write!(
f,
"Error thrown by observer object on Vmm initialization: {e}"
),
VmmObserverTeardown(e) => {
write!(f, "Error thrown by observer object on Vmm teardown: {e}")
}
}
}
}
pub trait VmmEventsObserver {
fn on_vmm_boot(&mut self) -> std::result::Result<(), utils::errno::Error> {
Ok(())
}
fn on_vmm_stop(&mut self) -> std::result::Result<(), utils::errno::Error> {
Ok(())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Vmm {
guest_memory: GuestMemoryMmap,
arch_memory_info: ArchMemoryInfo,
kernel_cmdline: KernelCmdline,
vcpus_handles: Vec<VcpuHandle>,
exit_evt: EventFd,
vm: Vm,
exit_observers: Vec<Arc<Mutex<dyn VmmExitObserver>>>,
exit_code: Arc<AtomicI32>,
mmio_device_manager: MMIODeviceManager,
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
pio_device_manager: PortIODeviceManager,
}
impl Vmm {
pub fn get_bus_device(
&self,
device_type: DeviceType,
device_id: &str,
) -> Option<&Mutex<dyn BusDevice>> {
self.mmio_device_manager.get_device(device_type, device_id)
}
pub fn start_vcpus(&mut self, vcpus: Vec<Vcpu>) -> Result<()> {
self.start_vcpus_paused(vcpus)?;
self.resume_vcpus()?;
Ok(())
}
pub fn start_vcpus_paused(&mut self, mut vcpus: Vec<Vcpu>) -> Result<Vec<VcpuPlacementResult>> {
let vcpu_count = vcpus.len();
let mut placement = Vec::with_capacity(vcpu_count);
Vcpu::register_kick_signal_handler();
self.vcpus_handles.reserve(vcpu_count);
for mut vcpu in vcpus.drain(..) {
vcpu.set_mmio_bus(self.mmio_device_manager.bus.clone());
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
let (handle, result) = vcpu.start_threaded().map_err(Error::VcpuHandle)?;
self.vcpus_handles.push(handle);
placement.push(result);
}
#[cfg(target_os = "macos")]
{
let vcpu_index = vcpu.cpu_index();
self.vcpus_handles
.push(vcpu.start_threaded().map_err(Error::VcpuHandle)?);
placement.push(VcpuPlacementResult::Inherited {
vcpu_index,
requested_host_cpu: None,
reason: None,
});
}
}
Ok(placement)
}
#[cfg(target_os = "linux")]
pub fn resume_vcpus(&mut self) -> Result<()> {
for handle in self.vcpus_handles.iter() {
handle
.send_event(VcpuEvent::Resume)
.map_err(Error::VcpuEvent)?;
}
for handle in self.vcpus_handles.iter() {
match handle
.response_receiver()
.recv_timeout(Duration::from_millis(1000))
{
Ok(VcpuResponse::Resumed) => (),
_ => return Err(Error::VcpuResume),
}
}
Ok(())
}
#[cfg(target_os = "macos")]
pub fn resume_vcpus(&mut self) -> Result<()> {
Ok(())
}
#[cfg(target_os = "windows")]
pub fn resume_vcpus(&mut self) -> Result<()> {
for handle in self.vcpus_handles.iter() {
handle
.send_event(VcpuEvent::Resume)
.map_err(Error::VcpuEvent)?;
}
for handle in self.vcpus_handles.iter() {
match handle
.response_receiver()
.recv_timeout(Duration::from_millis(1000))
{
Ok(VcpuResponse::Resumed) => (),
_ => return Err(Error::VcpuResume),
}
}
Ok(())
}
pub fn configure_system(
&self,
vcpus: &[Vcpu],
_intc: &IrqChip,
initrd: &Option<InitrdConfig>,
_smbios_oem_strings: &Option<Vec<String>>,
) -> Result<()> {
#[cfg(all(target_os = "windows", not(target_arch = "aarch64")))]
let _ = (vcpus, initrd);
#[cfg(target_arch = "x86_64")]
{
let ioapic_num_pins = _intc.lock().unwrap().num_pins();
let cmdline_len = if cfg!(feature = "tee") {
arch::x86_64::layout::CMDLINE_SEV_SIZE
} else {
self.kernel_cmdline.len() + 1
};
arch::x86_64::configure_system(
&self.guest_memory,
&self.arch_memory_info,
vm_memory::GuestAddress(arch::x86_64::layout::CMDLINE_START),
cmdline_len,
initrd,
vcpus.len() as u8,
ioapic_num_pins,
)
.map_err(Error::ConfigureSystem)?;
}
#[cfg(target_arch = "aarch64")]
{
let vcpu_mpidr = vcpus.iter().map(|cpu| cpu.get_mpidr()).collect();
fdt::create_fdt(
&self.guest_memory,
&self.arch_memory_info,
vcpu_mpidr,
self.kernel_cmdline.as_str(),
self.mmio_device_manager.get_device_info(),
_intc,
initrd,
)
.map_err(Error::SetupFDT)?;
}
#[cfg(target_arch = "aarch64")]
{
arch::aarch64::configure_system(
&self.guest_memory,
&self.arch_memory_info,
_smbios_oem_strings,
)
.map_err(Error::ConfigureSystem)?;
}
#[cfg(all(target_arch = "riscv64", not(target_os = "windows")))]
{
fdt::create_fdt(
&self.guest_memory,
&self.arch_memory_info,
vcpus.len() as u32,
self.kernel_cmdline.as_str(),
self.mmio_device_manager.get_device_info(),
_intc,
initrd,
)
.map_err(Error::SetupFDT)?;
arch::riscv64::configure_system(&self.guest_memory, _smbios_oem_strings)
.map_err(Error::ConfigureSystem)?;
}
Ok(())
}
pub fn guest_memory(&self) -> &GuestMemoryMmap {
&self.guest_memory
}
pub fn add_exit_observer(&mut self, observer: impl VmmExitObserver + 'static) {
self.exit_observers.push(Arc::new(Mutex::new(observer)));
}
#[cfg(all(target_arch = "x86_64", not(target_os = "windows")))]
pub fn send_ctrl_alt_del(&mut self) -> Result<()> {
self.pio_device_manager
.i8042
.lock()
.expect("i8042 lock was poisoned")
.trigger_ctrl_alt_del()
.map_err(Error::I8042Error)
}
pub fn notify_exit_observers(&mut self, exit_code: i32) {
for observer in &self.exit_observers {
let obs = Arc::clone(observer);
if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
obs.lock()
.expect("Poisoned mutex for exit observer")
.on_vmm_exit(exit_code);
})) {
error!("Exit observer panicked: {e:?}");
}
}
}
pub fn stop(&mut self, exit_code: i32) {
info!("Vmm is stopping.");
self.notify_exit_observers(exit_code);
#[cfg(unix)]
unsafe {
libc::_exit(exit_code);
}
#[cfg(windows)]
std::process::exit(exit_code);
}
pub fn kvm_vm(&self) -> &Vm {
&self.vm
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub fn add_mapping(
&self,
reply_sender: Sender<bool>,
host_addr: u64,
guest_addr: u64,
len: u64,
) {
self.vm
.add_mapping(reply_sender, host_addr, guest_addr, len);
}
#[cfg(target_os = "windows")]
pub fn add_mapping_with_writable(
&self,
reply_sender: Sender<bool>,
host_addr: u64,
guest_addr: u64,
len: u64,
writable: bool,
) {
self.vm
.add_mapping_with_writable(reply_sender, host_addr, guest_addr, len, writable);
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub fn remove_mapping(&self, reply_sender: Sender<bool>, guest_addr: u64, len: u64) {
self.vm.remove_mapping(reply_sender, guest_addr, len);
}
#[cfg(unix)]
fn is_exit_event_source(&self, source: std::os::fd::RawFd) -> bool {
source == self.exit_evt.as_raw_fd()
}
#[cfg(windows)]
fn is_exit_event_source(&self, source: RawHandle) -> bool {
source == self.exit_evt.as_raw_handle()
}
#[cfg(unix)]
fn exit_event_token(&self) -> u64 {
self.exit_evt.as_raw_fd() as u64
}
#[cfg(windows)]
fn exit_event_token(&self) -> u64 {
self.exit_evt.as_raw_handle() as usize as u64
}
}
impl Subscriber for Vmm {
fn process(&mut self, event: &EpollEvent, _: &mut EventManager) {
let source = event.fd();
let event_set = event.event_set();
if self.is_exit_event_source(source) && event_set == EventSet::IN {
let _ = self.exit_evt.read();
let vcpu_exit_code = self
.vcpus_handles
.iter()
.find_map(|handle| match handle.response_receiver().try_recv() {
Ok(VcpuResponse::Exited(exit_code)) => Some(exit_code),
_ => None,
})
.unwrap_or(FC_EXIT_CODE_OK);
let vmm_exit_code = self.exit_code.load(Ordering::SeqCst);
let exit_code = if vmm_exit_code != i32::MAX {
debug!("using vmm exit code: {vmm_exit_code}");
vmm_exit_code
} else {
debug!("using vcpu exit code: {vcpu_exit_code}");
vcpu_exit_code as i32
};
self.stop(exit_code);
} else {
error!("Spurious EventManager event for handler: Vmm");
}
}
fn interest_list(&self) -> Vec<EpollEvent> {
vec![EpollEvent::new(EventSet::IN, self.exit_event_token())]
}
}