use alloc::{boxed::Box, vec::Vec};
use core::{
fmt,
ops::{Deref, DerefMut},
};
use ioapic::IoApic;
use spin::Once;
use crate::{
Error, Result, arch::kernel::acpi::get_acpi_tables, info, io::IoMemAllocatorBuilder,
irq::IrqLine, sync::SpinLock,
};
mod ioapic;
mod pic;
pub struct IrqChip {
io_apics: SpinLock<Box<[IoApic]>>,
overrides: Box<[IsaOverride]>,
}
struct IsaOverride {
source: u8,
target: u32,
}
impl IrqChip {
pub fn map_gsi_pin_to(
&'static self,
irq_line: IrqLine,
gsi_index: u32,
) -> Result<MappedIrqLine> {
let mut io_apics = self.io_apics.lock();
let io_apic = io_apics
.iter_mut()
.rev()
.find(|io_apic| io_apic.interrupt_base() <= gsi_index)
.unwrap();
let index_in_io_apic = (gsi_index - io_apic.interrupt_base())
.try_into()
.map_err(|_| Error::InvalidArgs)?;
io_apic.enable(index_in_io_apic, &irq_line)?;
Ok(MappedIrqLine {
irq_line,
gsi_index,
irq_chip: self,
})
}
fn disable_gsi(&self, gsi_index: u32) {
let mut io_apics = self.io_apics.lock();
let io_apic = io_apics
.iter_mut()
.rev()
.find(|io_apic| io_apic.interrupt_base() <= gsi_index)
.unwrap();
let index_in_io_apic = (gsi_index - io_apic.interrupt_base()) as u8;
io_apic.disable(index_in_io_apic).unwrap();
}
pub fn map_isa_pin_to(
&'static self,
irq_line: IrqLine,
isa_index: u8,
) -> Result<MappedIrqLine> {
let gsi_index = self
.overrides
.iter()
.find(|isa_override| isa_override.source == isa_index)
.map(|isa_override| isa_override.target)
.unwrap_or(isa_index as u32);
self.map_gsi_pin_to(irq_line, gsi_index)
}
pub fn count_io_apics(&self) -> usize {
self.io_apics.lock().len()
}
}
pub struct MappedIrqLine {
irq_line: IrqLine,
gsi_index: u32,
irq_chip: &'static IrqChip,
}
impl fmt::Debug for MappedIrqLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MappedIrqLine")
.field("irq_line", &self.irq_line)
.field("gsi_index", &self.gsi_index)
.finish_non_exhaustive()
}
}
impl Deref for MappedIrqLine {
type Target = IrqLine;
fn deref(&self) -> &Self::Target {
&self.irq_line
}
}
impl DerefMut for MappedIrqLine {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.irq_line
}
}
impl Drop for MappedIrqLine {
fn drop(&mut self) {
self.irq_chip.disable_gsi(self.gsi_index)
}
}
pub static IRQ_CHIP: Once<IrqChip> = Once::new();
pub(in crate::arch) fn init(io_mem_builder: &IoMemAllocatorBuilder) {
use acpi::madt::{Madt, MadtEntry};
let acpi_tables = get_acpi_tables().unwrap();
let madt_table = acpi_tables.find_table::<Madt>().unwrap();
const PCAT_COMPAT: u32 = 1;
if madt_table.get().flags & PCAT_COMPAT != 0 {
pic::init_and_disable();
}
let mut io_apics = Vec::with_capacity(2);
let mut isa_overrides = Vec::new();
const BUS_ISA: u8 = 0;
for madt_entry in madt_table.get().entries() {
match madt_entry {
MadtEntry::IoApic(madt_io_apic) => {
let io_apic = unsafe {
IoApic::new(
madt_io_apic.io_apic_address as usize,
madt_io_apic.global_system_interrupt_base,
io_mem_builder,
)
};
io_apics.push(io_apic);
}
MadtEntry::InterruptSourceOverride(madt_isa_override)
if madt_isa_override.bus == BUS_ISA =>
{
let isa_override = IsaOverride {
source: madt_isa_override.irq,
target: madt_isa_override.global_system_interrupt,
};
isa_overrides.push(isa_override);
}
_ => {}
}
}
if isa_overrides.is_empty() {
isa_overrides.push(IsaOverride {
source: 0, target: 2, });
}
for isa_override in isa_overrides.iter() {
info!(
"IOAPIC override: ISA interrupt {} for GSI {}",
isa_override.source, isa_override.target
);
}
io_apics.sort_by_key(|io_apic| io_apic.interrupt_base());
assert!(!io_apics.is_empty(), "No I/O APICs found");
assert_eq!(
io_apics[0].interrupt_base(),
0,
"No I/O APIC with zero interrupt base found"
);
let irq_chip = IrqChip {
io_apics: SpinLock::new(io_apics.into_boxed_slice()),
overrides: isa_overrides.into_boxed_slice(),
};
IRQ_CHIP.call_once(|| irq_chip);
}