use alloc::{collections::BTreeMap, vec::Vec};
use ax_kspin::SpinNoIrq as Mutex;
use crate::irq::model::PendingVcpuInterrupt;
pub(crate) struct VcpuInterruptQueue {
pending: Mutex<BTreeMap<usize, Vec<PendingVcpuInterrupt>>>,
}
impl VcpuInterruptQueue {
pub fn new() -> Self {
Self {
pending: Mutex::new(BTreeMap::new()),
}
}
pub fn push(&self, vcpu_id: usize, interrupt: PendingVcpuInterrupt) {
self.pending
.lock()
.entry(vcpu_id)
.or_default()
.push(interrupt);
}
pub fn drain(&self, vcpu_id: usize) -> Vec<PendingVcpuInterrupt> {
self.pending
.lock()
.get_mut(&vcpu_id)
.map(core::mem::take)
.unwrap_or_default()
}
}
#[cfg(all(test, feature = "host-test"))]
mod tests {
use alloc::vec;
use super::*;
use crate::irq::model::VirtualInterruptId;
fn edge(id: u32) -> PendingVcpuInterrupt {
PendingVcpuInterrupt {
id: VirtualInterruptId(id),
trigger: crate::InterruptTriggerMode::EdgeTriggered,
}
}
fn level(id: u32) -> PendingVcpuInterrupt {
PendingVcpuInterrupt {
id: VirtualInterruptId(id),
trigger: crate::InterruptTriggerMode::LevelTriggered,
}
}
#[test]
fn push_preserves_fifo_order() {
let q = VcpuInterruptQueue::new();
q.push(0, edge(10));
q.push(0, level(20));
q.push(0, edge(30));
let drained = q.drain(0);
assert_eq!(drained.len(), 3);
assert_eq!(drained[0], edge(10));
assert_eq!(drained[1], level(20));
assert_eq!(drained[2], edge(30));
}
#[test]
fn isolates_vcpus() {
let q = VcpuInterruptQueue::new();
q.push(0, edge(1));
q.push(1, edge(2));
assert_eq!(q.drain(0), vec![edge(1)]);
assert_eq!(q.drain(1), vec![edge(2)]);
}
#[test]
fn drain_empties_queue() {
let q = VcpuInterruptQueue::new();
q.push(0, edge(7));
assert_eq!(q.drain(0).len(), 1);
assert!(q.drain(0).is_empty());
}
#[test]
fn double_drain_returns_empty() {
let q = VcpuInterruptQueue::new();
q.push(0, edge(7));
q.drain(0);
assert!(q.drain(0).is_empty());
}
#[test]
fn trigger_mode_round_trips() {
let q = VcpuInterruptQueue::new();
q.push(0, edge(42));
q.push(0, level(43));
let drained = q.drain(0);
assert_eq!(drained.len(), 2);
assert_eq!(
drained[0].trigger,
crate::InterruptTriggerMode::EdgeTriggered
);
assert_eq!(
drained[1].trigger,
crate::InterruptTriggerMode::LevelTriggered
);
}
}