use alloc::format;
use ax_errno::{AxResult, ax_err_type};
use crate::{
AsVCpuTask, GuestPhysAddr, StopReason, VCpuTask, VmExit, VmStatus, VmVcpuState,
arch::{ArchOps, CurrentArch},
runtime::{VCpuRef, VMRef, sub_running_vm_count},
vm::VmRuntimeHandle,
};
const KERNEL_STACK_SIZE: usize = 0x40000;
fn wait(vm_vcpus: &VmRuntimeHandle) {
vm_vcpus.wait();
}
fn wait_for<F>(vm_vcpus: &VmRuntimeHandle, condition: F)
where
F: Fn() -> bool,
{
vm_vcpus.wait_until(condition);
}
pub(crate) fn notify_primary_vcpu(vm_id: usize) {
let Some(vm) = crate::get_vm_by_id(vm_id) else {
warn!("VM[{vm_id}] not found while notifying primary vCPU");
return;
};
if let Err(err) = vm.with_runtime(|runtime| {
runtime.notify_one();
Ok(())
}) {
warn!("VM[{vm_id}] vCPU runtime not found: {err:?}");
}
}
pub(crate) fn notify_all_vcpus(vm_id: usize) {
if let Some(vm) = crate::get_vm_by_id(vm_id) {
let _ = vm.with_runtime(|runtime| {
runtime.notify_all();
Ok(())
});
}
}
pub(crate) fn queue_interrupt(vm_id: usize, vcpu_id: usize, vector: usize) -> AxResult {
let vm = crate::get_vm_by_id(vm_id)
.ok_or_else(|| ax_err_type!(NotFound, format!("VM[{vm_id}] not found")))?;
if !matches!(vm.status(), VmStatus::Running | VmStatus::Paused) {
return Err(ax_err_type!(
BadState,
format!("VM[{vm_id}] is not accepting interrupts")
));
}
let cpu_id = vm.with_runtime(|runtime| runtime.queue_interrupt(vcpu_id, vector))?;
vm.with_runtime(|runtime| {
runtime.notify_all();
Ok(())
})?;
crate::host::task::send_ipi(cpu_id);
Ok(())
}
#[cfg(target_arch = "loongarch64")]
pub(crate) fn queue_external_interrupt(
vm_id: usize,
vcpu_id: usize,
vector: usize,
physical_irq: usize,
) -> AxResult {
let vm = crate::get_vm_by_id(vm_id)
.ok_or_else(|| ax_err_type!(NotFound, format!("VM[{vm_id}] not found")))?;
if !matches!(vm.status(), VmStatus::Running | VmStatus::Paused) {
return Err(ax_err_type!(
BadState,
format!("VM[{vm_id}] is not accepting interrupts")
));
}
let cpu_id =
vm.with_runtime(|runtime| runtime.queue_external_interrupt(vcpu_id, vector, physical_irq))?;
vm.with_runtime(|runtime| {
runtime.notify_all();
Ok(())
})?;
crate::host::task::send_ipi(cpu_id);
Ok(())
}
pub(crate) fn inject_pending_interrupts(vm_id: usize, vcpu_id: usize, vcpu: &VCpuRef) {
let Some(vm) = crate::get_vm_by_id(vm_id) else {
warn!("VM[{vm_id}] not found, cannot drain VCpu[{vcpu_id}] interrupts");
return;
};
let Ok(interrupts) = vm.with_runtime(|runtime| Ok(runtime.drain_pending_interrupts(vcpu_id)))
else {
warn!("VM[{vm_id}] vCPU runtime not found, cannot drain VCpu[{vcpu_id}] interrupts");
return;
};
for interrupt in interrupts {
CurrentArch::inject_pending_interrupt(&vm, vcpu, interrupt);
}
}
pub(crate) fn cleanup_vm_vcpus(vm_id: usize) {
if let Some(vm) = crate::get_vm_by_id(vm_id)
&& let Err(err) = vm.with_runtime(|runtime| {
runtime.join_all_vcpu_tasks(vm_id);
Ok(())
})
{
warn!("VM[{vm_id}] vCPU runtime cleanup skipped: {err:?}");
}
}
fn mark_vcpu_running(vm: &VMRef) {
let _ = vm.with_runtime(|runtime| {
runtime.mark_vcpu_running();
Ok(())
});
}
fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) -> AxResult {
let vcpu = vm
.vcpu_list()
.get(vcpu_id)
.cloned()
.ok_or_else(|| ax_err_type!(NotFound, format!("vCPU {vcpu_id} not found")))?;
if vcpu.state() != VmVcpuState::Free {
return Err(ax_err_type!(
BadState,
format!("vCPU {} invalid state {:?}", vcpu.id(), vcpu.state())
));
}
vcpu.set_entry(entry_point)?;
CurrentArch::set_vcpu_on_args(&vcpu, vcpu_id, arg);
let vcpu_task = alloc_vcpu_task(&vm, vcpu);
vm.with_runtime(|runtime| {
runtime.add_vcpu_task(vcpu_id, vcpu_task);
Ok(())
})?;
Ok(())
}
pub(crate) fn alloc_vcpu_task(vm: &VMRef, vcpu: VCpuRef) -> crate::AxTaskRef {
crate::host::task::spawn_task(build_vcpu_task(vm, vcpu))
}
pub(crate) fn build_vcpu_task(vm: &VMRef, vcpu: VCpuRef) -> crate::TaskInner {
info!("Spawning task for VM[{}] VCpu[{}]", vm.id(), vcpu.id());
let mut vcpu_task = crate::TaskInner::new(
vcpu_run,
format!("VM[{}]-VCpu[{}]", vm.id(), vcpu.id()),
KERNEL_STACK_SIZE,
);
if let Some(phys_cpu_set) = vcpu.phys_cpu_set() {
vcpu_task.set_cpumask(crate::host::task::cpu_mask_from_raw_bits(
vcpu_task_cpu_mask(vm.id(), vcpu.id(), phys_cpu_set),
));
}
let inner = VCpuTask::new(vm, vcpu);
*vcpu_task.task_ext_mut() = Some(crate::AxTaskExt::from_impl(inner));
info!(
"VCpu task {} created {:?}",
vcpu_task.id_name(),
vcpu_task.cpumask()
);
vcpu_task
}
fn vcpu_task_cpu_mask(vm_id: usize, vcpu_id: usize, requested_mask: usize) -> usize {
let enabled_mask = crate::percpu::enabled_cpu_mask();
if enabled_mask == 0 {
warn!(
"VM[{vm_id}] VCpu[{vcpu_id}] has no initialized host CPU mask; using requested mask \
{requested_mask:#x}"
);
return requested_mask;
}
let initialized_requested_mask = requested_mask & enabled_mask;
if initialized_requested_mask != 0 {
if initialized_requested_mask != requested_mask {
warn!(
"VM[{vm_id}] VCpu[{vcpu_id}] requested host CPU mask {requested_mask:#x}, but \
only {initialized_requested_mask:#x} is initialized for AxVM"
);
}
return initialized_requested_mask;
}
let fallback_mask = enabled_mask & enabled_mask.wrapping_neg();
warn!(
"VM[{vm_id}] VCpu[{vcpu_id}] requested host CPU mask {requested_mask:#x}, but none of \
those CPUs initialized AxVM; using initialized host CPU mask {fallback_mask:#x}"
);
fallback_mask
}
fn vcpu_run() {
let curr = crate::host::task::current_task();
let vm = curr.as_vcpu_task().vm();
let vcpu = curr.as_vcpu_task().vcpu.clone();
let vm_id = vm.id();
let vcpu_id = vcpu.id();
let Ok(runtime) = vm.with_runtime(|runtime| Ok(runtime.clone())) else {
warn!("VM[{vm_id}] vCPU runtime not found, VCpu[{vcpu_id}] exiting");
return;
};
info!("VM[{}] VCpu[{}] waiting for running", vm.id(), vcpu.id());
wait_for(&runtime, || vm.running());
info!("VM[{}] VCpu[{}] running...", vm.id(), vcpu.id());
CurrentArch::before_first_run(&vm, &vcpu);
mark_vcpu_running(&vm);
loop {
inject_pending_interrupts(vm_id, vcpu_id, &vcpu);
CurrentArch::before_vcpu_run(&vm, &vcpu);
match vm.run_vcpu(vcpu_id) {
Ok(exit_reason) => match exit_reason {
VmExit::Hypercall { nr, args } => {
debug!("Hypercall [{nr}] args {args:x?}");
use crate::runtime::hvc::HyperCall;
match HyperCall::new(vm.clone(), nr, args) {
Ok(hypercall) => {
let ret_val = match hypercall.execute() {
Ok(ret_val) => ret_val as isize,
Err(err) => {
warn!("Hypercall [{nr:#x}] failed: {err:?}");
-1
}
};
vcpu.set_return_value(ret_val as usize);
}
Err(err) => {
warn!("Hypercall [{nr:#x}] failed: {err:?}");
}
}
}
VmExit::FailEntry {
hardware_entry_failure_reason,
} => {
warn!(
"VM[{vm_id}] VCpu[{vcpu_id}] run failed with exit code \
{hardware_entry_failure_reason}"
);
}
VmExit::ExternalInterrupt { vector } => {
debug!("VM[{vm_id}] run VCpu[{vcpu_id}] get irq {vector}");
CurrentArch::after_external_interrupt(&vm, &vcpu, vector as usize);
}
VmExit::PreemptionTimer => {
CurrentArch::after_preemption_timer(&vm, &vcpu);
}
VmExit::InterruptEnd { vector } => {
CurrentArch::after_interrupt_end(&vm, &vcpu, vector);
}
VmExit::Halt => {
debug!("VM[{vm_id}] run VCpu[{vcpu_id}] Halt");
if CurrentArch::handle_halt(&runtime) {
continue;
}
}
VmExit::Idle => {
trace!("VM[{vm_id}] run VCpu[{vcpu_id}] Idle");
CurrentArch::handle_idle(&vm, &vcpu);
}
VmExit::Nothing => {}
VmExit::CpuDown { _state } => {
warn!("VM[{vm_id}] run VCpu[{vcpu_id}] CpuDown state {_state:#x}");
wait(&runtime)
}
VmExit::CpuUp {
target_cpu,
entry_point,
arg,
} => {
info!(
"VM[{vm_id}]'s VCpu[{vcpu_id}] try to boot target_cpu [{target_cpu}] \
entry_point={entry_point:x} arg={arg:#x}"
);
let vcpu_mappings = vm.get_vcpu_affinities_pcpu_ids();
let Some(target_vcpu_id) =
vcpu_mappings.iter().find_map(|(vcpu_id, _, phys_id)| {
(*phys_id == target_cpu as usize).then_some(*vcpu_id)
})
else {
warn!("Physical CPU ID {target_cpu} not found in VM configuration");
vcpu.set_return_value(usize::MAX);
continue;
};
match vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg as _) {
Ok(()) => {
CurrentArch::set_cpu_up_success(&vcpu);
}
Err(err) => {
warn!("Failed to boot VM[{vm_id}] VCpu[{target_vcpu_id}]: {err:?}");
vcpu.set_return_value(usize::MAX);
}
}
}
VmExit::SystemDown => {
warn!("VM[{vm_id}] run VCpu[{vcpu_id}] SystemDown");
if let Err(err) = vm.stop(StopReason::SystemDown) {
warn!("VM[{vm_id}] shutdown failed: {err:?}");
}
notify_all_vcpus(vm_id);
}
VmExit::SendIPI {
target_cpu,
target_cpu_aux,
send_to_all,
send_to_self,
vector,
} => {
debug!(
"VM[{vm_id}] run VCpu[{vcpu_id}] SendIPI, target_cpu={target_cpu:#x}, \
target_cpu_aux={target_cpu_aux:#x}, vector={vector}",
);
let targets = CurrentArch::ipi_targets(
&vm,
vcpu_id,
target_cpu,
target_cpu_aux,
send_to_all,
send_to_self,
);
if targets.is_empty() {
warn!(
"VM[{vm_id}] SendIPI has no target: target_cpu={target_cpu:#x}, \
target_cpu_aux={target_cpu_aux:#x}"
);
continue;
}
if targets.get(vcpu_id) {
crate::inject_current_vcpu_interrupt(vector as _)
.expect("failed to inject self IPI into current vCPU");
}
let mut remote_targets = targets;
remote_targets.set(vcpu_id, false);
if !remote_targets.is_empty()
&& let Err(err) = vm.inject_interrupt_to_vcpu(remote_targets, vector as _)
{
warn!(
"Failed to inject interrupt {vector} to VM[{vm_id}] targets \
{remote_targets:?}: {err:?}"
);
}
}
e => {
warn!("VM[{vm_id}] run VCpu[{vcpu_id}] unhandled vmexit: {e:?}");
}
},
Err(err) => {
error!("VM[{vm_id}] run VCpu[{vcpu_id}] get error {err:?}");
if let Err(err) = vm.stop(StopReason::Fault(format!("{err:?}"))) {
warn!("VM[{vm_id}] shutdown failed after vCPU error: {err:?}");
}
notify_all_vcpus(vm_id);
}
}
if vm.suspending() {
debug!(
"VM[{}] VCpu[{}] is suspended, waiting for resume...",
vm_id, vcpu_id
);
wait_for(&runtime, || !vm.suspending());
info!("VM[{}] VCpu[{}] resumed from suspend", vm_id, vcpu_id);
continue;
}
if vm.stopping() {
warn!(
"VM[{}] VCpu[{}] stopping because of VM stopping",
vm_id, vcpu_id
);
if runtime.mark_vcpu_exiting() {
info!("VM[{vm_id}] VCpu[{vcpu_id}] last VCpu exiting, decreasing running VM count");
if let Err(err) = vm.finish_stop() {
warn!("VM[{vm_id}] finish stop failed: {err:?}");
}
info!("VM[{}] state changed to Stopped", vm_id);
CurrentArch::on_last_vcpu_exit(vm_id);
sub_running_vm_count(1);
crate::host::task::wait_queue_wake(&super::VMM, 1);
}
break;
}
}
info!("VM[{}] VCpu[{}] exiting...", vm_id, vcpu_id);
}