#[macro_use]
extern crate log;
pub mod builder;
pub(crate) mod device_manager;
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 terminal;
pub mod worker;
#[cfg(target_os = "macos")]
use macos::vstate;
use std::fmt::{Display, Formatter};
use std::io;
use std::os::unix::io::AsRawFd;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
#[cfg(target_os = "linux")]
use std::time::Duration;
#[cfg(target_arch = "x86_64")]
use crate::device_manager::legacy::PortIODeviceManager;
use crate::device_manager::mmio::MMIODeviceManager;
#[cfg(target_os = "linux")]
use crate::vstate::VcpuEvent;
use crate::vstate::{Vcpu, VcpuHandle, VcpuResponse, Vm};
use arch::{ArchMemoryInfo, InitrdConfig};
#[cfg(target_os = "macos")]
use crossbeam_channel::Sender;
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
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(target_arch = "x86_64")]
CreateLegacyDevice(device_manager::legacy::Error),
EventFd(io::Error),
EventManager(event_manager::Error),
#[cfg(target_arch = "x86_64")]
I8042Error(devices::legacy::I8042DeviceError),
KernelFile(io::Error),
KvmContext(vstate::Error),
#[cfg(target_arch = "x86_64")]
LegacyIOBus(device_manager::legacy::Error),
LoadCommandline(kernel::cmdline::Error),
RegisterMMIODevice(device_manager::mmio::Error),
Serial(io::Error),
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
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(target_arch = "x86_64")]
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:?}"),
#[cfg(target_arch = "x86_64")]
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(target_arch = "x86_64")]
LegacyIOBus(e) => write!(f, "Cannot add devices to the legacy 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(any(target_arch = "aarch64", target_arch = "riscv64"))]
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(target_arch = "x86_64")]
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, mut vcpus: Vec<Vcpu>) -> Result<()> {
let vcpu_count = vcpus.len();
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());
self.vcpus_handles
.push(vcpu.start_threaded().map_err(Error::VcpuHandle)?);
}
self.resume_vcpus()?;
Ok(())
}
#[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(())
}
pub fn configure_system(
&self,
vcpus: &[Vcpu],
_intc: &IrqChip,
initrd: &Option<InitrdConfig>,
_smbios_oem_strings: &Option<Vec<String>>,
) -> Result<()> {
#[cfg(target_arch = "x86_64")]
{
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,
)
.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(target_arch = "riscv64")]
{
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
}
#[cfg(target_arch = "x86_64")]
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 stop(&mut self, exit_code: i32) {
info!("Vmm is stopping.");
for observer in &self.exit_observers {
observer
.lock()
.expect("Poisoned mutex for exit observer")
.on_vmm_exit();
}
unsafe {
libc::_exit(exit_code);
}
}
pub fn kvm_vm(&self) -> &Vm {
&self.vm
}
#[cfg(target_os = "macos")]
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 = "macos")]
pub fn remove_mapping(&self, reply_sender: Sender<bool>, guest_addr: u64, len: u64) {
self.vm.remove_mapping(reply_sender, guest_addr, len);
}
}
impl Subscriber for Vmm {
fn process(&mut self, event: &EpollEvent, _: &mut EventManager) {
let source = event.fd();
let event_set = event.event_set();
if source == self.exit_evt.as_raw_fd() && 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_evt.as_raw_fd() as u64,
)]
}
}