use alloc::{collections::BTreeMap, vec::Vec};
use ax_kspin::SpinNoIrq as Mutex;
use super::queue::VcpuInterruptQueue;
use crate::{AxTaskRef, AxVmResult, ax_err_type, irq::model::PendingVcpuInterrupt};
pub struct VcpuIrqDispatcher {
queue: VcpuInterruptQueue,
vcpu_tasks: Mutex<BTreeMap<usize, AxTaskRef>>,
#[cfg(all(test, feature = "host-test"))]
test_vcpu_cpu_ids: Mutex<BTreeMap<usize, usize>>,
}
impl VcpuIrqDispatcher {
pub fn new() -> Self {
Self {
queue: VcpuInterruptQueue::new(),
vcpu_tasks: Mutex::new(BTreeMap::new()),
#[cfg(all(test, feature = "host-test"))]
test_vcpu_cpu_ids: Mutex::new(BTreeMap::new()),
}
}
pub fn register_vcpu_task(&self, vcpu_id: usize, task: AxTaskRef) {
self.vcpu_tasks.lock().insert(vcpu_id, task);
}
#[cfg(all(test, feature = "host-test"))]
pub(crate) fn register_test_vcpu(&self, vcpu_id: usize, cpu_id: usize) {
self.test_vcpu_cpu_ids.lock().insert(vcpu_id, cpu_id);
}
#[cfg(all(test, feature = "host-test"))]
pub(crate) fn test_lookup_cpu_id(&self, vcpu_id: usize) -> AxVmResult<usize> {
self.lookup_cpu_id(vcpu_id)
}
pub fn unregister_vcpu_task(&self, vcpu_id: usize) {
self.vcpu_tasks.lock().remove(&vcpu_id);
self.queue.drain(vcpu_id);
#[cfg(all(test, feature = "host-test"))]
self.test_vcpu_cpu_ids.lock().remove(&vcpu_id);
}
pub fn enqueue(&self, vcpu_id: usize, interrupt: PendingVcpuInterrupt) -> AxVmResult<usize> {
let cpu_id = self.lookup_cpu_id(vcpu_id)?;
self.queue.push(vcpu_id, interrupt);
Ok(cpu_id)
}
fn lookup_cpu_id(&self, vcpu_id: usize) -> AxVmResult<usize> {
#[cfg(all(test, feature = "host-test"))]
{
if let Some(&cpu_id) = self.test_vcpu_cpu_ids.lock().get(&vcpu_id) {
return Ok(cpu_id);
}
}
let tasks = self.vcpu_tasks.lock();
tasks
.get(&vcpu_id)
.map(|t| t.cpu_id() as usize)
.ok_or_else(|| ax_err_type!(NotFound, format_args!("vCPU {vcpu_id} task not found")))
}
pub fn drain(&self, vcpu_id: usize) -> Vec<PendingVcpuInterrupt> {
self.queue.drain(vcpu_id)
}
}