use alloc::sync::Arc;
use ax_errno::{AxResult, ax_err};
use axdevice::IrqResolver;
use axdevice_base::{InterruptTriggerMode, IrqLine, IrqLineId, IrqSink};
use axvm_types::VMInterruptMode;
#[cfg(target_arch = "riscv64")]
pub(crate) mod riscv;
pub struct InterruptFabric {
mode: VMInterruptMode,
sink: Option<Arc<dyn IrqSink>>,
}
impl InterruptFabric {
pub const fn new(mode: VMInterruptMode) -> Self {
Self { mode, sink: None }
}
pub fn with_sink(mode: VMInterruptMode, sink: Arc<dyn IrqSink>) -> AxResult<Self> {
if mode == VMInterruptMode::NoIrq {
return ax_err!(
InvalidInput,
"a VM configured with interrupt_mode=no_irq cannot install an IRQ backend"
);
}
Ok(Self {
mode,
sink: Some(sink),
})
}
pub const fn mode(&self) -> VMInterruptMode {
self.mode
}
pub const fn has_backend(&self) -> bool {
self.sink.is_some()
}
fn sink_for_line(&self, line: usize) -> AxResult<&Arc<dyn IrqSink>> {
let Some(sink) = &self.sink else {
if self.mode == VMInterruptMode::NoIrq {
return ax_err!(
InvalidInput,
format_args!("cannot signal IRQ line {line}: the VM interrupt mode is NoIrq")
);
}
return ax_err!(
Unsupported,
format_args!("cannot signal IRQ line {line}: no VM interrupt backend is installed")
);
};
Ok(sink)
}
pub fn set_level(&self, line: usize, asserted: bool) -> AxResult {
self.sink_for_line(line)?
.set_level(IrqLineId(line), asserted)
}
pub fn pulse(&self, line: usize) -> AxResult {
self.sink_for_line(line)?.pulse(IrqLineId(line))
}
pub(crate) fn validate_mode(&self, mode: VMInterruptMode) -> AxResult {
if self.mode != mode {
return ax_err!(
InvalidInput,
format_args!(
"interrupt fabric mode {:?} does not match VM interrupt mode {:?}",
self.mode, mode
)
);
}
Ok(())
}
}
impl Default for InterruptFabric {
fn default() -> Self {
Self::new(VMInterruptMode::NoIrq)
}
}
impl IrqResolver for InterruptFabric {
fn resolve_irq(&self, line: usize, trigger: InterruptTriggerMode) -> AxResult<IrqLine> {
Ok(IrqLine::new(
IrqLineId(line),
trigger,
self.sink_for_line(line)?.clone(),
))
}
}