286 lines
7.9 KiB
Rust
286 lines
7.9 KiB
Rust
//! ARM Generic Interrupt Controller v2 driver
|
|
|
|
use core::sync::atomic::Ordering;
|
|
|
|
use aarch64_cpu::asm::barrier;
|
|
use abi::error::Error;
|
|
use alloc::sync::Arc;
|
|
use device_api::{
|
|
device::{Device, DeviceInitContext},
|
|
interrupt::{
|
|
ExternalInterruptController, FixedInterruptTable, FullIrq, InterruptHandler,
|
|
InterruptTable, IpiDeliveryTarget, IpiMessage, Irq, IrqLevel, IrqOptions, IrqTrigger,
|
|
IrqVector, LocalInterruptController,
|
|
},
|
|
};
|
|
use device_tree::{
|
|
driver::{device_tree_driver, DeviceTreeInterruptController, Node, ProbeContext},
|
|
DeviceTreePropertyRead, TProp,
|
|
};
|
|
use kernel_arch_aarch64::{GicInterface, CPU_COUNT};
|
|
use libk::{arch::Cpu, device::register_external_interrupt_controller, task::cpu_index};
|
|
use libk_mm::{
|
|
address::PhysicalAddress,
|
|
device::{DeviceMemoryIo, RawDeviceMemoryMapping},
|
|
};
|
|
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
|
|
|
|
use self::{gicc::Gicc, gicd::Gicd};
|
|
|
|
use super::AArch64;
|
|
|
|
const MAX_IRQ: usize = 300;
|
|
const IPI_VECTOR: u64 = 1;
|
|
|
|
const GIC_PPI_START: u32 = 16;
|
|
const GIC_SPI_START: u32 = 32;
|
|
|
|
pub mod gicc;
|
|
pub mod gicd;
|
|
pub mod gicv2m;
|
|
|
|
/// ARM Generic Interrupt Controller v2
|
|
pub struct Gic {
|
|
gicc: OneTimeInit<Gicc>,
|
|
gicd: OneTimeInit<Gicd>,
|
|
gicd_base: PhysicalAddress,
|
|
gicc_base: PhysicalAddress,
|
|
table: IrqSafeRwLock<FixedInterruptTable<MAX_IRQ>>,
|
|
}
|
|
|
|
/// Per-CPU GIC information
|
|
pub struct GicPerCpu {}
|
|
|
|
impl GicInterface for Gic {}
|
|
|
|
impl Device for Gic {
|
|
fn display_name(&self) -> &str {
|
|
"ARM Generic Interrupt Controller v2"
|
|
}
|
|
|
|
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
|
|
log::debug!(
|
|
"Init GIC: gicd={:#x}, gicc={:#x}",
|
|
self.gicd_base,
|
|
self.gicc_base
|
|
);
|
|
let gicd_mmio = Arc::new(RawDeviceMemoryMapping::map(
|
|
self.gicd_base.into_u64(),
|
|
0x1000,
|
|
Default::default(),
|
|
)?);
|
|
let gicd_mmio_shared = DeviceMemoryIo::from_raw(gicd_mmio.clone())?;
|
|
let gicd_mmio_banked = DeviceMemoryIo::from_raw(gicd_mmio)?;
|
|
let gicc_mmio = DeviceMemoryIo::map(self.gicc_base, Default::default())?;
|
|
|
|
let gicd = Gicd::new(gicd_mmio_shared, gicd_mmio_banked);
|
|
let gicc = Gicc::new(gicc_mmio);
|
|
|
|
gicd.init();
|
|
gicc.init();
|
|
|
|
self.gicd.init(gicd);
|
|
self.gicc.init(gicc);
|
|
|
|
register_external_interrupt_controller(self.clone());
|
|
AArch64::set_gic(self.clone());
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ExternalInterruptController for Gic {
|
|
fn register_irq(
|
|
&self,
|
|
irq: Irq,
|
|
options: IrqOptions,
|
|
handler: Arc<dyn InterruptHandler>,
|
|
) -> Result<(), Error> {
|
|
let mut table = self.table.write();
|
|
let gicd = self.gicd.get();
|
|
|
|
let index = match irq {
|
|
Irq::External(i) => i + GIC_SPI_START,
|
|
Irq::Private(i) => i + GIC_PPI_START,
|
|
} as usize;
|
|
|
|
log::debug!(
|
|
"Bound irq{} to {:?} {:?} {:?}",
|
|
index,
|
|
handler.display_name(),
|
|
options.trigger,
|
|
options.level
|
|
);
|
|
if index >= GIC_SPI_START as usize {
|
|
gicd.configure_irq(index, options);
|
|
}
|
|
table.insert(index, handler)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn enable_irq(&self, irq: Irq) -> Result<(), Error> {
|
|
let gicd = self.gicd.get();
|
|
let index = match irq {
|
|
Irq::External(i) => i + GIC_SPI_START,
|
|
Irq::Private(i) => i + GIC_PPI_START,
|
|
} as usize;
|
|
log::debug!("Enable irq{index} ({irq:?})");
|
|
gicd.enable_irq(index);
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_pending_irqs(&self) {
|
|
let gicc = self.gicc.get();
|
|
|
|
let irq_number = gicc.pending_irq_number();
|
|
if irq_number >= MAX_IRQ {
|
|
return;
|
|
}
|
|
|
|
gicc.clear_irq(irq_number);
|
|
|
|
if irq_number == IPI_VECTOR as usize {
|
|
// TODO pop entry
|
|
crate::panic::panic_secondary();
|
|
}
|
|
|
|
if irq_number < GIC_PPI_START as usize {
|
|
return;
|
|
}
|
|
|
|
let irq = if irq_number < GIC_SPI_START as usize {
|
|
Irq::Private(irq_number as u32 - GIC_PPI_START)
|
|
} else {
|
|
Irq::External(irq_number as u32 - GIC_SPI_START)
|
|
};
|
|
|
|
{
|
|
let table = self.table.read();
|
|
let entry = match table.handler(irq_number) {
|
|
Some(handler) => handler.clone(),
|
|
None => {
|
|
log::warn!("No handler for irq{}", irq_number);
|
|
return;
|
|
}
|
|
};
|
|
|
|
entry.handle_irq(IrqVector::Irq(irq));
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LocalInterruptController for Gic {
|
|
fn send_ipi(&self, target: IpiDeliveryTarget, msg: IpiMessage) -> Result<(), Error> {
|
|
let local = cpu_index();
|
|
let mask = match target {
|
|
IpiDeliveryTarget::OtherCpus => usize::MAX & !(1 << local),
|
|
IpiDeliveryTarget::Specific(mask) => mask,
|
|
IpiDeliveryTarget::ThisCpu => 1 << local,
|
|
};
|
|
|
|
for i in 0..CPU_COUNT.load(Ordering::Acquire) {
|
|
if mask & (1 << i) != 0 {
|
|
Cpu::push_ipi_queue(i as _, msg);
|
|
}
|
|
}
|
|
|
|
// Issue a memory barrier
|
|
barrier::dsb(barrier::ISH);
|
|
barrier::isb(barrier::SY);
|
|
|
|
unsafe {
|
|
self.gicd.get().set_sgir(target, IPI_VECTOR);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn init_ap(&self) -> Result<(), Error> {
|
|
self.gicc.get().init();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DeviceTreeInterruptController for Gic {
|
|
fn map_interrupt(&self, property: &TProp, offset: usize) -> Option<FullIrq> {
|
|
// IRQ_TYPE_NONE - 0
|
|
// IRQ_TYPE_EDGE_RISING - 1
|
|
// IRQ_TYPE_EDGE_FALLING - 2
|
|
// IRQ_TYPE_EDGE_BOTH - 3
|
|
// IRQ_TYPE_LEVEL_HIGH - 4
|
|
// IRQ_TYPE_LEVEL_LOW - 8
|
|
|
|
let sizes = (1, 1, 1);
|
|
let (kind, number, options) = property.read_cells_at(offset, sizes)?;
|
|
let number = number as u32;
|
|
|
|
let (trigger, level) = match options & 0xF {
|
|
0x01 => (IrqTrigger::Edge, IrqLevel::ActiveHigh),
|
|
0x02 => (IrqTrigger::Edge, IrqLevel::ActiveLow),
|
|
0x03 => return None, // TODO
|
|
0x04 => (IrqTrigger::Level, IrqLevel::ActiveHigh),
|
|
0x08 => (IrqTrigger::Level, IrqLevel::ActiveLow),
|
|
_ => return None,
|
|
};
|
|
let irq = match kind {
|
|
0x00 => Irq::External(number),
|
|
0x01 => Irq::Private(number),
|
|
_ => return None,
|
|
};
|
|
|
|
Some(FullIrq {
|
|
irq,
|
|
options: IrqOptions { trigger, level },
|
|
})
|
|
}
|
|
|
|
fn as_interrupt_controller(self: Arc<Self>) -> Arc<dyn ExternalInterruptController> {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Gic {
|
|
/// Constructs an instance of GICv2.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller must ensure the addresses actually point to the GIC components.
|
|
pub unsafe fn new(gicd_base: PhysicalAddress, gicc_base: PhysicalAddress) -> Self {
|
|
Self {
|
|
gicc: OneTimeInit::new(),
|
|
gicd: OneTimeInit::new(),
|
|
gicd_base,
|
|
gicc_base,
|
|
table: IrqSafeRwLock::new(FixedInterruptTable::new()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GicPerCpu {
|
|
/// Constructs per-CPU GIC data structure
|
|
pub fn new() -> Self {
|
|
Self {}
|
|
}
|
|
}
|
|
|
|
device_tree_driver! {
|
|
compatible: ["arm,cortex-a15-gic", "arm,gic-400"],
|
|
driver: {
|
|
fn probe(&self, node: &Arc<Node>, context: &ProbeContext) -> Option<Arc<dyn Device>> {
|
|
let gicd_range = node.map_range(context, 0)?;
|
|
let gicc_range = node.map_range(context, 1)?;
|
|
|
|
let gicd_base = PhysicalAddress::from_u64(gicd_range.start);
|
|
let gicc_base = PhysicalAddress::from_u64(gicc_range.start);
|
|
|
|
let gic = Arc::new(unsafe { Gic::new(gicd_base, gicc_base) });
|
|
|
|
// Register device-tree interrupt controller
|
|
node.make_interrupt_controller(gic.clone());
|
|
|
|
Some(gic)
|
|
}
|
|
}
|
|
}
|