use arm_gic::{
IntId, InterruptGroup, Trigger, UniqueMmioPointer,
gicv3::{GicCpuInterface, GicDistributorContext, GicRedistributorContext, GicRedistributorIterator, GicV3},
};
use core::ptr::NonNull;
use patina::error::EfiError;
use patina::{
arch::aarch64::{AArch64El, get_current_el},
read_sysreg, write_sysreg,
};
const MPIDR_AFFINITY_MASK: u64 = 0x0000_00ff_00ff_ffff;
#[allow(dead_code)]
#[derive(PartialEq)]
pub enum GicVersion {
ArmGicV2 = 2,
ArmGicV3 = 3,
}
#[allow(dead_code)]
fn get_control_system_reg_enable() -> u64 {
let current_el = get_current_el();
#[allow(clippy::match_same_arms)]
match current_el {
AArch64El::EL2 => read_sysreg!(ICC_SRE_EL2),
AArch64El::EL1 => read_sysreg!(ICC_SRE_EL1),
}
}
#[allow(dead_code)]
fn set_control_system_reg_enable(icc_sre: u64) -> u64 {
let current_el = get_current_el();
#[allow(clippy::match_same_arms)]
match current_el {
AArch64El::EL2 => {
write_sysreg!(reg ICC_SRE_EL2, icc_sre);
}
AArch64El::EL1 => {
write_sysreg!(reg ICC_SRE_EL1, icc_sre);
}
}
get_control_system_reg_enable()
}
#[allow(dead_code)]
fn get_system_gic_version() -> GicVersion {
let pfr0_el1 = read_sysreg!(ID_AA64PFR0_EL1);
if (pfr0_el1 & (0xf << 24)) == 0 {
return GicVersion::ArmGicV2;
}
let mut icc_sre = get_control_system_reg_enable();
if icc_sre & 0x1 == 1 {
return GicVersion::ArmGicV3;
}
icc_sre |= 0x1;
icc_sre = set_control_system_reg_enable(icc_sre);
if icc_sre & 0x1 == 1 {
return GicVersion::ArmGicV3;
}
GicVersion::ArmGicV2
}
pub struct AArch64InterruptInitializer {
gic_v3: GicV3<'static>,
cpu_r_idx: usize,
}
impl AArch64InterruptInitializer {
pub unsafe fn new(gicd_base: *mut u64, gicr_base: *mut u64) -> Result<Self, EfiError> {
let gic_v = get_system_gic_version();
if gic_v == GicVersion::ArmGicV2 {
debug_assert!(false, "GICv2 is not supported");
return Err(EfiError::Unsupported);
}
let (gicd, gicr) = unsafe {
let gicd = UniqueMmioPointer::new(NonNull::new(gicd_base.cast()).ok_or(EfiError::InvalidParameter)?);
let gicr = NonNull::new(gicr_base.cast()).ok_or(EfiError::InvalidParameter)?;
(gicd, gicr)
};
let mut cpu_r_idx = usize::MAX;
let mut r_count = 0;
let mpidr = read_sysreg!(MPIDR_EL1) & MPIDR_AFFINITY_MASK;
log::debug!("Current CPU MPIDR: {mpidr:#x}");
for (index, redistributor) in unsafe { GicRedistributorIterator::new(gicr, true) }.enumerate() {
r_count = index + 1;
if redistributor.typer().core_mpidr() == mpidr {
if cpu_r_idx != usize::MAX {
log::error!(
"Multiple redistributors found for current cpu mpidr {mpidr:#x} at index {cpu_r_idx} and {index}"
);
return Err(EfiError::DeviceError);
}
cpu_r_idx = index;
}
log::debug!(
"Redistributor Index: {}, MPIDR: {:#x} {}",
index,
redistributor.typer().core_mpidr(),
if redistributor.typer().core_mpidr() == mpidr { "(Current CPU)" } else { "" }
);
}
log::info!("Total Redistributors: {r_count}, Current CPU Redistributor Index: {cpu_r_idx}");
if cpu_r_idx == usize::MAX {
log::error!("Failed to find redistributor for current cpu");
return Err(EfiError::DeviceError);
}
let mut gic_v3 = unsafe { GicV3::new(gicd, gicr, r_count, true) };
gic_v3.init_cpu(cpu_r_idx);
gic_v3.redistributor(cpu_r_idx).map_err(|_| EfiError::DeviceError)?.configure_default_settings();
gic_v3.distributor().configure_default_settings();
GicCpuInterface::enable_group1(true);
write_sysreg!(reg ICC_BPR1_EL1, 0x7u64);
GicCpuInterface::set_priority_mask(0xff);
Ok(Self { gic_v3, cpu_r_idx })
}
fn source_to_intid(&self, interrupt_source: u64) -> Result<IntId, EfiError> {
let int_id: u32 = interrupt_source.try_into().map_err(|_| EfiError::InvalidParameter)?;
let int_id = match int_id {
x if x < IntId::SGI_COUNT => IntId::sgi(x),
x if x < IntId::SGI_COUNT + IntId::PPI_COUNT => IntId::ppi(x - IntId::SGI_COUNT),
x => {
let int_id = IntId::spi(x - IntId::SGI_COUNT - IntId::PPI_COUNT);
if self.gic_v3.typer().num_spis() < int_id.into() {
Err(EfiError::InvalidParameter)?;
}
int_id
}
};
Ok(int_id)
}
pub fn enable_interrupt_source(&mut self, interrupt_source: u64) -> Result<(), EfiError> {
self.gic_v3
.enable_interrupt(self.source_to_intid(interrupt_source)?, Some(self.cpu_r_idx), true)
.map_err(|_| EfiError::InvalidParameter)
}
pub fn disable_interrupt_source(&mut self, interrupt_source: u64) -> Result<(), EfiError> {
self.gic_v3
.enable_interrupt(self.source_to_intid(interrupt_source)?, Some(self.cpu_r_idx), false)
.map_err(|_| EfiError::InvalidParameter)
}
const MAX_REDISTRIBUTOR_PPI: usize = GicRedistributorContext::ireg_count(96); const MAX_DISTRIBUTOR_SPI: usize = GicDistributorContext::ireg_count(988); const MAX_DISTRIBUTOR_ESPI: usize = GicDistributorContext::ireg_e_count(1024); pub fn get_interrupt_source_state(&mut self, interrupt_source: u64) -> Result<bool, EfiError> {
let index = (interrupt_source / 32) as usize;
let bit = 1 << (interrupt_source % 32);
let int_id = self.source_to_intid(interrupt_source)?;
if int_id.is_private() {
let redistributor = self.gic_v3.redistributor(self.cpu_r_idx).expect("Invalid redistributor");
let mut context = GicRedistributorContext::<{ Self::MAX_REDISTRIBUTOR_PPI }>::default();
redistributor.save(&mut context).map_err(|_| EfiError::DeviceError)?;
Ok(context.isenabler().get(index).ok_or(EfiError::InvalidParameter)? & bit != 0)
} else {
let distributor = self.gic_v3.distributor();
let mut context =
GicDistributorContext::<{ Self::MAX_DISTRIBUTOR_SPI }, { Self::MAX_DISTRIBUTOR_ESPI }>::default();
distributor.save(&mut context).map_err(|_| EfiError::DeviceError)?;
Ok(context.isenabler().get(index).ok_or(EfiError::InvalidParameter)? & bit != 0)
}
}
pub fn end_of_interrupt(&self, interrupt_source: u64) -> Result<(), EfiError> {
GicCpuInterface::end_interrupt(self.source_to_intid(interrupt_source)?, InterruptGroup::Group1);
Ok(())
}
pub fn get_trigger_type(&mut self, interrupt_source: u64) -> Result<Trigger, EfiError> {
let index = (interrupt_source / 16) as usize;
let bit = 1 << (interrupt_source % 16);
let int_id = self.source_to_intid(interrupt_source)?;
let level = if int_id.is_private() {
let redistributor = self.gic_v3.redistributor(self.cpu_r_idx).expect("Invalid redistributor");
let mut context = GicRedistributorContext::<{ Self::MAX_REDISTRIBUTOR_PPI }>::default();
redistributor.save(&mut context).map_err(|_| EfiError::DeviceError)?;
context.icfgr().get(index).ok_or(EfiError::InvalidParameter)? & bit != 0
} else {
let distributor = self.gic_v3.distributor();
let mut context =
GicDistributorContext::<{ Self::MAX_DISTRIBUTOR_SPI }, { Self::MAX_DISTRIBUTOR_ESPI }>::default();
distributor.save(&mut context).map_err(|_| EfiError::DeviceError)?;
context.icfgr().get(index).ok_or(EfiError::InvalidParameter)? & bit != 0
};
Ok(if level { Trigger::Level } else { Trigger::Edge })
}
pub fn set_trigger_type(&mut self, interrupt_source: u64, trigger_type: Trigger) -> Result<(), EfiError> {
self.gic_v3
.set_trigger(self.source_to_intid(interrupt_source)?, Some(self.cpu_r_idx), trigger_type)
.map_err(|_| EfiError::InvalidParameter)
}
pub fn max_int(&self) -> u32 {
self.gic_v3.typer().num_spis()
}
}