use alloc::vec::Vec;
use rdif_intc::{AcpiGsiRoute, AcpiIrqPolarity, AcpiIrqTrigger};
use rdrive::{
DriverGeneric, PlatformDevice, module_driver,
probe::{
OnProbeError,
acpi::{AcpiId, AcpiInfo, AcpiIoApic},
},
};
use x2apic::ioapic::{IoApic, IrqFlags, IrqMode};
use crate::{common::PlatOp, irq::_handle_irq};
pub struct Plat;
const APIC_TIMER_VECTOR: usize = 0x20;
const LAPIC_REG_EOI: u32 = 0x0b0;
const LAPIC_REG_ICR_LOW: u32 = 0x300;
const LAPIC_REG_ICR_HIGH: u32 = 0x310;
const ICR_DELIVERY_PENDING: u32 = 1 << 12;
const ICR_FIXED_BASE: u32 = 0x0000_4000;
const ICR_DEST_SELF: u32 = 0x0004_0000;
const ICR_DEST_ALL_EXCLUDING_SELF: u32 = 0x000c_0000;
module_driver!(
name: "ACPI IOAPIC",
level: ProbeLevel::PreKernel,
priority: ProbePriority::INTC,
probe_kinds: &[ProbeKind::Acpi {
ids: &[AcpiId {
hid: "ACPIIOAP",
cids: &[],
}],
on_probe: probe_ioapic
}],
);
struct X86IoApicIntc {
ioapics: Vec<X86IoApic>,
routes: Vec<AcpiGsiRoute>,
}
impl X86IoApicIntc {
fn new(ioapics: &[AcpiIoApic]) -> Self {
Self {
ioapics: ioapics.iter().copied().map(X86IoApic::new).collect(),
routes: Vec::new(),
}
}
fn remember_route(&mut self, route: AcpiGsiRoute) {
if let Some(existing) = self.routes.iter_mut().find(|r| r.vector == route.vector) {
*existing = route;
} else {
self.routes.push(route);
}
}
fn route_for_vector(&self, vector: usize) -> Option<AcpiGsiRoute> {
self.routes
.iter()
.copied()
.find(|r| r.vector == vector)
.or_else(|| {
rdrive::probe::acpi::with_acpi(|system| system.routing().resolve_vector(vector))
.flatten()
})
}
fn set_vector_enable(&mut self, vector: usize, enable: bool) -> bool {
let Some(route) = self.route_for_vector(vector) else {
return false;
};
self.set_route_enable(&route, enable);
true
}
fn set_route_enable(&mut self, route: &AcpiGsiRoute, enable: bool) {
for ioapic in &mut self.ioapics {
if ioapic.contains_route(route) {
ioapic.set_route_enable(route, enable);
return;
}
}
}
}
struct X86IoApic {
info: AcpiIoApic,
ioapic: IoApic,
}
impl X86IoApic {
fn new(info: AcpiIoApic) -> Self {
let ioapic_base = someboot::mem::phys_to_virt(info.address as usize) as u64;
let mut ioapic = unsafe { IoApic::new(ioapic_base) };
let max_entry = unsafe { ioapic.max_table_entry() };
let redirection_entries = max_entry.saturating_add(1);
unsafe {
ioapic.init(irq_vector_base(info.gsi_base) as u8);
for input in 0..=max_entry {
let mut entry = ioapic.table_entry(input);
entry.set_flags(entry.flags() | IrqFlags::MASKED);
ioapic.set_table_entry(input, entry);
}
}
info!(
"ACPI IOAPIC initialized: id={} base={:#x} gsi_base={} entries={}",
info.id, info.address, info.gsi_base, redirection_entries
);
Self {
info: AcpiIoApic {
redirection_entries,
..info
},
ioapic,
}
}
fn contains(&self, gsi: u32) -> bool {
let start = self.info.gsi_base;
let end = start.saturating_add(u32::from(self.info.redirection_entries));
(start..end).contains(&gsi)
}
fn contains_route(&self, route: &AcpiGsiRoute) -> bool {
self.info.id == route.controller_id
&& self.info.address == route.controller_address
&& self.contains(route.gsi)
}
fn set_route_enable(&mut self, route: &AcpiGsiRoute, enable: bool) {
if !self.contains_route(route) {
return;
}
unsafe {
let input = route.controller_input;
let mut entry = self.ioapic.table_entry(input);
entry.set_vector(route.vector as u8);
entry.set_mode(IrqMode::Fixed);
entry.set_flags(intx_flags(route.trigger, route.polarity));
entry.set_dest(0);
self.ioapic.set_table_entry(input, entry);
if enable {
self.ioapic.enable_irq(input);
} else {
self.ioapic.disable_irq(input);
}
}
}
}
impl DriverGeneric for X86IoApicIntc {
fn name(&self) -> &str {
"x86 ACPI IOAPIC"
}
}
impl rdif_intc::Interface for X86IoApicIntc {
fn setup_irq_by_acpi(&mut self, route: &AcpiGsiRoute) -> rdrive::IrqId {
self.remember_route(*route);
self.set_route_enable(route, false);
route.vector.into()
}
}
fn probe_ioapic(info: AcpiInfo<'_>, dev: PlatformDevice) -> Result<(), OnProbeError> {
let ioapics = info.root.routing().io_apics();
if ioapics.is_empty() {
return Err(OnProbeError::NotMatch);
}
dev.register(rdif_intc::Intc::new(X86IoApicIntc::new(ioapics)));
Ok(())
}
impl PlatOp for Plat {
fn irq_set_enable(irq: rdrive::IrqId, enable: bool) {
let raw = irq.raw();
if raw == someboot::irq::systimer_irq().raw() {
someboot::irq::irq_set_enable(someboot::irq::IrqId::new(raw), enable);
return;
}
set_ioapic_vector_enable(raw, enable);
}
fn send_ipi(irq: rdrive::IrqId, target: crate::irq::IpiTarget) {
let vector = irq.raw() as u8;
unsafe {
match target {
crate::irq::IpiTarget::Current { .. } => {
send_lapic_ipi(0, ICR_FIXED_BASE | ICR_DEST_SELF | u32::from(vector))
}
crate::irq::IpiTarget::Other { cpu_id } => {
let Some(apic_id) = someboot::smp::cpu_idx_to_id(cpu_id) else {
warn!("failed to resolve CPU index {cpu_id} to APIC ID");
return;
};
send_lapic_ipi(raw_apic_id(apic_id), ICR_FIXED_BASE | u32::from(vector));
}
crate::irq::IpiTarget::AllExceptCurrent { .. } => {
send_lapic_ipi(
0,
ICR_FIXED_BASE | ICR_DEST_ALL_EXCLUDING_SELF | u32::from(vector),
);
}
}
}
}
fn irq_handler() -> someboot::irq::IrqId {
someboot::irq::systimer_irq()
}
fn irq_handler_with_raw(raw: usize) -> Option<someboot::irq::IrqId> {
if raw == APIC_TIMER_VECTOR {
_handle_irq(raw.into());
lapic_eoi();
return Some(someboot::irq::systimer_irq());
}
_handle_irq(raw.into());
lapic_eoi();
Some(someboot::irq::IrqId::new(raw))
}
fn systick_irq() -> rdrive::IrqId {
someboot::irq::systimer_irq().raw().into()
}
fn secondary_init() {}
fn secondary_init_intc(_cpu_idx: usize) {}
fn secondary_init_systick() {}
fn send_ipi_to_cpu(cpu_id: usize) {
Self::send_ipi(
APIC_TIMER_VECTOR.into(),
crate::irq::IpiTarget::Other { cpu_id },
);
}
}
fn set_ioapic_vector_enable(vector: usize, enable: bool) {
for intc in rdrive::get_list::<rdif_intc::Intc>() {
if intc.descriptor().name.starts_with("ACPI IOAPIC")
&& let Ok(ioapic) = intc.downcast::<X86IoApicIntc>()
&& let Ok(mut ioapic) = ioapic.try_lock()
&& ioapic.set_vector_enable(vector, enable)
{
return;
}
}
}
fn intx_flags(trigger: AcpiIrqTrigger, polarity: AcpiIrqPolarity) -> IrqFlags {
let mut flags = IrqFlags::empty();
if trigger == AcpiIrqTrigger::Level {
flags |= IrqFlags::LEVEL_TRIGGERED;
}
if polarity == AcpiIrqPolarity::ActiveLow {
flags |= IrqFlags::LOW_ACTIVE;
}
flags
}
fn irq_vector_base(gsi_base: u32) -> usize {
rdrive::probe::acpi::PCI_INTX_VECTOR_BASE + gsi_base as usize
}
fn lapic_eoi() {
unsafe {
lapic_write(LAPIC_REG_EOI, 0);
}
}
fn raw_apic_id(id: usize) -> u32 {
(id as u32) << 24
}
unsafe fn send_lapic_ipi(destination: u32, icr_low: u32) {
unsafe {
lapic_write(LAPIC_REG_ICR_HIGH, destination);
lapic_write(LAPIC_REG_ICR_LOW, icr_low);
while lapic_read(LAPIC_REG_ICR_LOW) & ICR_DELIVERY_PENDING != 0 {
core::hint::spin_loop();
}
}
}
unsafe fn lapic_read(offset: u32) -> u32 {
let ptr = lapic_ptr(offset) as *const u32;
unsafe { ptr.read_volatile() }
}
unsafe fn lapic_write(offset: u32, value: u32) {
let ptr = lapic_ptr(offset);
unsafe {
ptr.write_volatile(value);
}
}
fn lapic_ptr(offset: u32) -> *mut u32 {
const IA32_APIC_BASE: u32 = 0x1b;
const LAPIC_BASE_MASK: u64 = 0xffff_f000;
let base = unsafe { x86::msr::rdmsr(IA32_APIC_BASE) & LAPIC_BASE_MASK } as usize;
unsafe { someboot::mem::phys_to_virt(base).add(offset as usize) }.cast()
}
#[cfg(all(test, any(unix, windows)))]
mod tests {
use super::*;
#[test]
fn acpi_intx_flags_preserve_trigger_and_polarity() {
let level_low = intx_flags(AcpiIrqTrigger::Level, AcpiIrqPolarity::ActiveLow);
assert!(level_low.contains(IrqFlags::LEVEL_TRIGGERED));
assert!(level_low.contains(IrqFlags::LOW_ACTIVE));
let edge_high = intx_flags(AcpiIrqTrigger::Edge, AcpiIrqPolarity::ActiveHigh);
assert!(!edge_high.contains(IrqFlags::LEVEL_TRIGGERED));
assert!(!edge_high.contains(IrqFlags::LOW_ACTIVE));
}
}