use core::{
marker::PhantomData,
sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};
use crate::ArmVcpuResult;
pub(crate) const HOST_IRQ_INTERFACE_GICV2_MMIO: u64 = 1;
pub(crate) const HOST_IRQ_INTERFACE_GICV3_SYSREG: u64 = 2;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ArmHostIrqConfig {
interface: u64,
cpu_interface_base: usize,
}
impl ArmHostIrqConfig {
pub const fn gicv2_mmio(cpu_interface_base: usize) -> ArmVcpuResult<Self> {
if cpu_interface_base == 0
|| !cpu_interface_base.is_multiple_of(core::mem::align_of::<u32>())
{
return Err(crate::ArmVcpuError::InvalidInput);
}
Ok(Self {
interface: HOST_IRQ_INTERFACE_GICV2_MMIO,
cpu_interface_base,
})
}
pub const fn gicv3_sysreg() -> Self {
Self {
interface: HOST_IRQ_INTERFACE_GICV3_SYSREG,
cpu_interface_base: 0,
}
}
pub(crate) const fn interface(self) -> u64 {
self.interface
}
pub(crate) const fn cpu_interface_base(self) -> usize {
self.cpu_interface_base
}
}
#[must_use = "dropping the guard restores the previous host IRQ mask"]
pub struct ArmHostIrqGuard {
restore_unmasked: bool,
_not_send: PhantomData<*mut ()>,
}
impl ArmHostIrqGuard {
const DAIF_IRQ_MASK: u64 = 1 << 7;
pub fn mask() -> Self {
let previous_daif: u64;
unsafe {
core::arch::asm!(
"mrs {previous_daif}, daif",
"msr daifset, #2",
previous_daif = out(reg) previous_daif,
options(nostack, preserves_flags)
);
}
Self {
restore_unmasked: previous_daif & Self::DAIF_IRQ_MASK == 0,
_not_send: PhantomData,
}
}
}
impl Drop for ArmHostIrqGuard {
fn drop(&mut self) {
if !self.restore_unmasked {
return;
}
unsafe {
core::arch::asm!("msr daifclr, #2", options(nostack, preserves_flags));
}
}
}
pub trait ArmHostOps {
fn inject_virtual_interrupt(vector: u32) -> ArmVcpuResult;
fn finish_pending_host_irq(raw_ack: u32) -> Option<usize>;
fn handle_current_host_irq();
}
static CURRENT_EL_IRQ_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut());
static CURRENT_EL_IRQ_HANDLER_USERS: AtomicUsize = AtomicUsize::new(0);
fn current_el_irq_handler_for<H: ArmHostOps>() {
H::handle_current_host_irq();
}
pub(crate) fn install_current_el_irq_handler<H: ArmHostOps>() {
let handler = current_el_irq_handler_for::<H> as *mut ();
match CURRENT_EL_IRQ_HANDLER.compare_exchange(
core::ptr::null_mut(),
handler,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {}
Err(existing) if existing == handler => {}
Err(_) => panic!("arm_vcpu current-EL IRQ handler was installed by another host type"),
}
CURRENT_EL_IRQ_HANDLER_USERS.fetch_add(1, Ordering::AcqRel);
}
pub(crate) fn clear_current_el_irq_handler() {
loop {
let users = CURRENT_EL_IRQ_HANDLER_USERS.load(Ordering::Acquire);
if users == 0 {
panic!("arm_vcpu current-EL IRQ handler was not installed");
}
if CURRENT_EL_IRQ_HANDLER_USERS
.compare_exchange(users, users - 1, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
if users == 1 {
CURRENT_EL_IRQ_HANDLER.store(core::ptr::null_mut(), Ordering::Release);
}
break;
}
}
}
pub(crate) fn handle_current_host_irq() {
let handler = CURRENT_EL_IRQ_HANDLER.load(Ordering::Acquire);
if handler.is_null() {
panic!("arm_vcpu current-EL IRQ handler is not installed");
}
let handler: fn() = unsafe { core::mem::transmute(handler) };
handler();
}