use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use ax_plat::irq::{IrqId, IrqSource, LoongArchHvIrqIf};
use super::IrqIfImpl;
const LOONGARCH_MAX_IRQ_COUNT: usize = 256;
const IRQ_ROUTE_NONE: usize = 0;
const IRQ_TARGET_NONE: usize = usize::MAX;
const IRQ_TARGET_VM_SHIFT: usize = 32;
const LOONGARCH_IRQ_TRACE_LIMIT: usize = 80;
static VIRTUAL_IRQ_INJECTOR: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
static GUEST_IRQ_ROUTES: [AtomicUsize; LOONGARCH_MAX_IRQ_COUNT] =
[const { AtomicUsize::new(IRQ_ROUTE_NONE) }; LOONGARCH_MAX_IRQ_COUNT];
static GUEST_IRQ_TARGETS: [AtomicUsize; LOONGARCH_MAX_IRQ_COUNT] =
[const { AtomicUsize::new(IRQ_TARGET_NONE) }; LOONGARCH_MAX_IRQ_COUNT];
static IRQ_MISS_LOGS: AtomicUsize = AtomicUsize::new(0);
static IRQ_INJECT_LOGS: AtomicUsize = AtomicUsize::new(0);
fn resolve_physical_irq(physical_irq: usize) -> Result<IrqId, ax_plat::irq::IrqError> {
let gsi = u32::try_from(physical_irq).map_err(|_| ax_plat::irq::IrqError::InvalidIrq)?;
somehal::irq::resolve_irq_source(IrqSource::AcpiGsi(gsi))
}
fn set_physical_irq_enabled(physical_irq: usize, enabled: bool) {
match resolve_physical_irq(physical_irq) {
Ok(irq) => {
if let Err(err) = somehal::irq::irq_set_enable(irq, enabled) {
warn!(
"LoongArch guest IRQ route failed to set physical IRQ {physical_irq} \
({irq:?}) enabled={enabled}: {err:?}"
);
}
}
Err(err) => {
warn!(
"LoongArch guest IRQ route failed to resolve physical IRQ {physical_irq}: {err:?}"
);
}
}
}
#[impl_plat_interface]
impl LoongArchHvIrqIf for IrqIfImpl {
fn register_virtual_irq_injector(injector: fn(usize, usize, usize, usize)) {
VIRTUAL_IRQ_INJECTOR.store(injector as *mut (), Ordering::Release);
debug!("LoongArch dynamic platform virtual IRQ injector registered");
}
fn register_guest_irq_route(
physical_irq: usize,
vm_id: usize,
vcpu_id: usize,
guest_vector: usize,
) {
if physical_irq >= LOONGARCH_MAX_IRQ_COUNT {
warn!("LoongArch guest IRQ route ignored: physical IRQ {physical_irq} out of range");
return;
}
let target = (vm_id << IRQ_TARGET_VM_SHIFT) | vcpu_id;
let previous_target = GUEST_IRQ_TARGETS[physical_irq].load(Ordering::Acquire);
if previous_target != IRQ_TARGET_NONE && previous_target != target {
warn!(
"LoongArch guest IRQ route conflict: physical_irq={} is already routed to encoded \
target {:#x}, ignoring VM[{}] VCpu[{}]",
physical_irq, previous_target, vm_id, vcpu_id
);
return;
}
GUEST_IRQ_TARGETS[physical_irq].store(target, Ordering::Release);
GUEST_IRQ_ROUTES[physical_irq].store(guest_vector + 1, Ordering::Release);
set_physical_irq_enabled(physical_irq, true);
debug!(
"LoongArch dynamic guest IRQ route: physical_irq={}, target=VM[{}] VCpu[{}], \
guest_vector={}",
physical_irq, vm_id, vcpu_id, guest_vector
);
}
fn unregister_guest_irq_routes(vm_id: usize) {
for physical_irq in 0..LOONGARCH_MAX_IRQ_COUNT {
let target = GUEST_IRQ_TARGETS[physical_irq].load(Ordering::Acquire);
if target == IRQ_TARGET_NONE || (target >> IRQ_TARGET_VM_SHIFT) != vm_id {
continue;
}
GUEST_IRQ_ROUTES[physical_irq].store(IRQ_ROUTE_NONE, Ordering::Release);
GUEST_IRQ_TARGETS[physical_irq].store(IRQ_TARGET_NONE, Ordering::Release);
set_physical_irq_enabled(physical_irq, false);
debug!("LoongArch dynamic guest IRQ route removed: physical_irq={physical_irq}");
}
}
}
pub(super) fn inject_virtual_irq(physical_irq: usize) -> bool {
if physical_irq >= LOONGARCH_MAX_IRQ_COUNT {
if IRQ_MISS_LOGS.fetch_add(1, Ordering::Relaxed) < LOONGARCH_IRQ_TRACE_LIMIT {
trace!(
"LoongArch guest IRQ route miss: physical_irq={} out of range",
physical_irq
);
}
return false;
}
let encoded_vector = GUEST_IRQ_ROUTES[physical_irq].load(Ordering::Acquire);
if encoded_vector == IRQ_ROUTE_NONE {
if IRQ_MISS_LOGS.fetch_add(1, Ordering::Relaxed) < LOONGARCH_IRQ_TRACE_LIMIT {
trace!(
"LoongArch guest IRQ route miss: physical_irq={} has no route",
physical_irq
);
}
return false;
}
let encoded_target = GUEST_IRQ_TARGETS[physical_irq].load(Ordering::Acquire);
if encoded_target == IRQ_TARGET_NONE {
if IRQ_MISS_LOGS.fetch_add(1, Ordering::Relaxed) < LOONGARCH_IRQ_TRACE_LIMIT {
trace!(
"LoongArch guest IRQ route miss: physical_irq={} has no target",
physical_irq
);
}
return false;
}
let injector = VIRTUAL_IRQ_INJECTOR.load(Ordering::Acquire);
if injector.is_null() {
warn!("LoongArch virtual IRQ injector is not registered");
return false;
}
let guest_vector = encoded_vector - 1;
let vm_id = encoded_target >> IRQ_TARGET_VM_SHIFT;
let vcpu_id = encoded_target & ((1usize << IRQ_TARGET_VM_SHIFT) - 1);
if IRQ_INJECT_LOGS.fetch_add(1, Ordering::Relaxed) < LOONGARCH_IRQ_TRACE_LIMIT {
trace!(
"LoongArch guest IRQ inject: physical_irq={} -> VM[{}] VCpu[{}] guest_vector={}",
physical_irq, vm_id, vcpu_id, guest_vector
);
}
unsafe {
core::mem::transmute::<*mut (), fn(usize, usize, usize, usize)>(injector)(
vm_id,
vcpu_id,
guest_vector,
physical_irq,
);
}
true
}