pub(crate) mod hvc;
pub(crate) mod ivc;
pub(crate) mod vcpus;
mod dispatcher;
mod queue;
use std::sync::atomic::{AtomicUsize, Ordering};
#[allow(unused_imports)]
pub(crate) use dispatcher::VcpuIrqDispatcher;
use crate::{AxVmError, AxVmResult, StopReason, VmStatus, ax_err};
pub type VMRef = crate::AxVMRef;
pub type VCpuRef = crate::vm::AxVCpuRef;
static VMM: crate::HostWaitQueueHandle = crate::HostWaitQueueHandle::new();
static RUNNING_VM_COUNT: AtomicUsize = AtomicUsize::new(0);
pub fn init() {
info!("Initializing VMM...");
}
pub fn start() {
launch_all();
wait_for_all();
}
pub fn launch_all() -> std::vec::Vec<usize> {
info!("VMM starting, booting VMs...");
let mut started = std::vec::Vec::new();
for vm in crate::get_vm_list() {
match vm.start() {
Ok(_) => {
RUNNING_VM_COUNT.fetch_add(1, Ordering::Release);
vcpus::notify_primary_vcpu(vm.id());
started.push(vm.id());
info!("VM[{}] boot success", vm.id());
}
Err(err) => warn!("VM[{}] boot failed, error {:?}", vm.id(), err),
}
}
started
}
pub fn wait_for_all() {
crate::host::task::wait_queue_wait_until(&VMM, || {
let vm_count = RUNNING_VM_COUNT.load(Ordering::Acquire);
debug!("a VM exited, current running VM count: {vm_count}");
vm_count == 0
});
}
pub fn add_running_vm_count(count: usize) {
RUNNING_VM_COUNT.fetch_add(count, Ordering::Release);
}
pub fn sub_running_vm_count(count: usize) {
RUNNING_VM_COUNT.fetch_sub(count, Ordering::Release);
}
fn reset_starts_counted_runtime(previous_status: VmStatus) -> bool {
matches!(
previous_status,
VmStatus::Ready
| VmStatus::Running
| VmStatus::Paused
| VmStatus::Stopping
| VmStatus::Stopped
)
}
pub fn start_vm(vm_id: usize) -> AxVmResult {
let vm = vm_by_id(vm_id)?;
let status = vm.status();
if !matches!(status, VmStatus::Ready | VmStatus::Stopped) {
return ax_err!(BadState, "VM cannot be started from its current state");
}
vm.start()?;
add_running_vm_count(1);
vcpus::notify_primary_vcpu(vm_id);
Ok(())
}
pub fn notify_vm(vm_id: usize) -> AxVmResult {
let vm = vm_by_id(vm_id)?;
let vcpu_num = vm.vcpu_num();
vm.with_runtime(|runtime| {
notify_runtime_for_device_poll(runtime, vcpu_num);
Ok(())
})
}
fn notify_runtime_for_device_poll(runtime: &crate::vm::VmRuntimeHandle, vcpu_num: usize) {
if vcpu_num == 1 {
runtime.notify_device_poll();
} else {
runtime.notify_one();
}
}
pub fn stop_vm(vm_id: usize) -> AxVmResult {
let vm = vm_by_id(vm_id)?;
if matches!(vm.status(), VmStatus::Running) {
wait_until_vcpu_entered(|| vm.running_vcpu_count() > 0, || vm.stopping())?;
}
vm.stop(StopReason::Forced)?;
vcpus::notify_all_vcpus(vm_id);
Ok(())
}
pub(crate) fn wait_until_vcpu_entered(
vcpu_entered: impl Fn() -> bool,
vm_stopping: impl Fn() -> bool,
) -> AxVmResult {
const MAX_YIELDS: usize = 10_000;
for _ in 0..MAX_YIELDS {
if vcpu_entered() || vm_stopping() {
return Ok(());
}
crate::host::task::yield_now();
}
ax_err!(
BadState,
"vCPU task did not enter the guest before request-stop"
)
}
pub fn resume_vm(vm_id: usize) -> AxVmResult {
let vm = vm_by_id(vm_id)?;
vm.resume()?;
vcpus::notify_all_vcpus(vm_id);
Ok(())
}
pub fn reset_vm(vm_id: usize) -> AxVmResult {
let vm = vm_by_id(vm_id)?;
let previous_status = vm.status();
vm.reset()?;
if reset_starts_counted_runtime(previous_status) {
add_running_vm_count(1);
}
vcpus::notify_primary_vcpu(vm_id);
Ok(())
}
pub fn remove_vm(vm_id: usize) -> Option<VMRef> {
crate::manager::remove_existing_vm(vm_id)
}
pub fn register_vm(vm: VMRef) -> bool {
crate::manager::push_existing_vm(vm)
}
fn vm_by_id(vm_id: usize) -> AxVmResult<VMRef> {
crate::get_vm_by_id(vm_id).ok_or_else(|| missing_vm_error(vm_id))
}
const fn missing_vm_error(vm_id: usize) -> AxVmError {
AxVmError::VmNotFound { vm_id }
}
#[cfg(test)]
mod tests {
use std::{
cell::Cell,
sync::{Arc, atomic::AtomicBool},
};
use super::*;
#[test]
fn reset_counts_replacement_runtime_for_every_restartable_state() {
for status in [
VmStatus::Ready,
VmStatus::Running,
VmStatus::Paused,
VmStatus::Stopping,
VmStatus::Stopped,
] {
assert!(
reset_starts_counted_runtime(status),
"reset from {status:?} starts a fresh running runtime"
);
}
}
#[test]
fn missing_vm_is_reported_with_its_id() {
let vm_id = usize::MAX;
assert_eq!(missing_vm_error(vm_id), AxVmError::VmNotFound { vm_id });
}
#[test]
fn smp_notification_does_not_publish_a_shared_device_poll_request() {
let runtime = crate::vm::VmRuntimeHandle::new();
let observed_generation = runtime.notification_generation();
notify_runtime_for_device_poll(&runtime, 2);
assert!(!runtime.device_poll_requested());
assert_ne!(runtime.notification_generation(), observed_generation);
}
#[test]
fn single_vcpu_notification_publishes_a_device_poll_request() {
let runtime = crate::vm::VmRuntimeHandle::new();
notify_runtime_for_device_poll(&runtime, 1);
assert!(runtime.device_poll_requested());
}
#[test]
fn request_stop_wait_returns_immediately_once_a_vcpu_has_entered() {
assert!(wait_until_vcpu_entered(|| true, || false).is_ok());
}
#[test]
fn request_stop_wait_bails_out_when_vm_is_already_stopping() {
assert!(wait_until_vcpu_entered(|| false, || true).is_ok());
}
#[test]
fn request_stop_wait_times_out_instead_of_accepting_a_never_entering_vcpu() {
let err = wait_until_vcpu_entered(|| false, || false).unwrap_err();
assert!(matches!(err, AxVmError::InvalidState { .. }));
}
#[test]
fn request_stop_waits_for_vcpu_entry_when_stop_precedes_entry() {
let entered = Arc::new(AtomicBool::new(false));
let stopping = Arc::new(AtomicBool::new(false));
let first_poll = Arc::new(std::sync::Barrier::new(2));
let release_entered = Arc::new(std::sync::Barrier::new(2));
let entered_for_task = entered.clone();
let first_poll_for_task = first_poll.clone();
let release_entered_for_task = release_entered.clone();
let vcpu_task = std::thread::spawn(move || {
first_poll_for_task.wait();
release_entered_for_task.wait();
entered_for_task.store(true, Ordering::Release);
});
let entered_for_wait = entered.clone();
let stopping_for_wait = stopping.clone();
let poll_count = Cell::new(0);
let result = wait_until_vcpu_entered(
|| {
let is_entered = entered_for_wait.load(Ordering::Acquire);
if poll_count.get() == 0 {
poll_count.set(1);
first_poll.wait();
release_entered.wait();
}
is_entered
},
|| stopping_for_wait.load(Ordering::Acquire),
);
vcpu_task.join().unwrap();
assert!(
result.is_ok(),
"stop must wait for vCPU entry, not strand it"
);
assert!(
entered.load(Ordering::Acquire),
"vCPU task must have entered the guest run loop"
);
}
}