Skip to main content

strat9_kernel/arch/x86_64/
ioapic.rs

1//! I/O APIC driver
2//!
3//! The I/O APIC handles routing of external hardware interrupts to
4//! Local APICs. It uses indirect MMIO: write register index to IOREGSEL,
5//! then read/write IOWIN.
6
7use crate::{acpi::madt::InterruptSourceOverride, memory};
8use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
9
10/// Whether the I/O APIC has been initialized
11static IOAPIC_INITIALIZED: AtomicBool = AtomicBool::new(false);
12
13/// Virtual base address of the I/O APIC MMIO registers
14static IOAPIC_BASE_VIRT: AtomicU64 = AtomicU64::new(0);
15
16/// GSI base for this I/O APIC
17static IOAPIC_GSI_BASE: AtomicU32 = AtomicU32::new(0);
18
19/// MADT interrupt-source overrides, stored so PCI NIC drivers can route
20/// their IRQ correctly after the APIC init phase.
21static MADT_OVERRIDES: spin::Mutex<[Option<InterruptSourceOverride>; 16]> =
22    spin::Mutex::new([None; 16]);
23
24// I/O APIC register offsets (indirect access)
25const IOREGSEL: u64 = 0x00;
26const IOWIN: u64 = 0x10;
27
28// I/O APIC registers (selected via IOREGSEL)
29const IOAPICID: u32 = 0x00;
30const IOAPICVER: u32 = 0x01;
31// Redirection table entries start at register 0x10
32// Each entry is 64 bits (two 32-bit registers): low at 0x10+2*n, high at 0x10+2*n+1
33const IOREDTBL_BASE: u32 = 0x10;
34
35// Redirection entry bit fields
36const REDIR_MASK: u64 = 1 << 16;
37const REDIR_LEVEL_TRIGGER: u64 = 1 << 15;
38const REDIR_ACTIVE_LOW: u64 = 1 << 13;
39
40/// Read an I/O APIC register (indirect access)
41///
42/// # Safety
43/// I/O APIC must be initialized.
44unsafe fn ioapic_read(reg: u32) -> u32 {
45    let base = IOAPIC_BASE_VIRT.load(Ordering::Relaxed);
46    // SAFETY: I/O APIC MMIO is mapped via HHDM
47    unsafe {
48        core::ptr::write_volatile((base + IOREGSEL) as *mut u32, reg);
49        core::ptr::read_volatile((base + IOWIN) as *const u32)
50    }
51}
52
53/// Write an I/O APIC register (indirect access)
54///
55/// # Safety
56/// I/O APIC must be initialized.
57unsafe fn ioapic_write(reg: u32, value: u32) {
58    let base = IOAPIC_BASE_VIRT.load(Ordering::Relaxed);
59    // SAFETY: I/O APIC MMIO is mapped via HHDM
60    unsafe {
61        core::ptr::write_volatile((base + IOREGSEL) as *mut u32, reg);
62        core::ptr::write_volatile((base + IOWIN) as *mut u32, value);
63    }
64}
65
66/// Read a 64-bit redirection entry
67///
68/// # Safety
69/// I/O APIC must be initialized, index must be valid.
70unsafe fn read_redir(index: u32) -> u64 {
71    let reg_low = IOREDTBL_BASE + index * 2;
72    let reg_high = IOREDTBL_BASE + index * 2 + 1;
73    // SAFETY: caller ensures valid index
74    let low = unsafe { ioapic_read(reg_low) } as u64;
75    let high = unsafe { ioapic_read(reg_high) } as u64;
76    low | (high << 32)
77}
78
79/// Write a 64-bit redirection entry
80///
81/// # Safety
82/// I/O APIC must be initialized, index must be valid.
83unsafe fn write_redir(index: u32, value: u64) {
84    let reg_low = IOREDTBL_BASE + index * 2;
85    let reg_high = IOREDTBL_BASE + index * 2 + 1;
86    // SAFETY: caller ensures valid index
87    unsafe {
88        ioapic_write(reg_low, value as u32);
89        ioapic_write(reg_high, (value >> 32) as u32);
90    }
91}
92
93/// Store the MADT interrupt-source overrides for later use by PCI NIC drivers.
94///
95/// Must be called once after MADT parsing, before any NIC init runs.
96pub fn store_madt_overrides(overrides: &[Option<InterruptSourceOverride>]) {
97    let mut dest = MADT_OVERRIDES.lock();
98    for (i, ovr) in overrides.iter().enumerate().take(16) {
99        dest[i] = ovr.clone();
100    }
101}
102
103/// Initialize the I/O APIC.
104///
105/// `phys_addr` is the I/O APIC base physical address from MADT.
106/// `gsi_base` is the first GSI handled by this I/O APIC.
107pub fn init(phys_addr: u32, gsi_base: u32) {
108    let virt_addr = memory::phys_to_virt(phys_addr as u64);
109    IOAPIC_BASE_VIRT.store(virt_addr, Ordering::Relaxed);
110    IOAPIC_GSI_BASE.store(gsi_base, Ordering::Relaxed);
111
112    // SAFETY: I/O APIC MMIO is mapped via HHDM
113    let id = unsafe { ioapic_read(IOAPICID) >> 24 };
114    let ver_reg = unsafe { ioapic_read(IOAPICVER) };
115    let version = ver_reg & 0xFF;
116    let max_redir = ((ver_reg >> 16) & 0xFF) + 1;
117
118    // Mask all interrupts initially
119    for i in 0..max_redir {
120        // SAFETY: index within max_redir
121        unsafe {
122            let entry = read_redir(i);
123            write_redir(i, entry | REDIR_MASK);
124        }
125    }
126
127    IOAPIC_INITIALIZED.store(true, Ordering::Relaxed);
128
129    log::info!(
130        "I/O APIC: id={}, version={}, {} entries, GSI base={}, virt=0x{:X}",
131        id,
132        version,
133        max_redir,
134        gsi_base,
135        virt_addr
136    );
137}
138
139/// Route a GSI to a specific LAPIC and vector.
140///
141/// `gsi` is the Global System Interrupt number.
142/// `lapic_id` is the destination LAPIC ID.
143/// `vector` is the interrupt vector (0x20+).
144/// `trigger` is the trigger mode (0=edge, 1=level).
145/// `polarity` is the polarity (0=active high, 1=active low).
146pub fn route_irq(gsi: u32, lapic_id: u32, vector: u8, trigger: u8, polarity: u8) {
147    let gsi_base = IOAPIC_GSI_BASE.load(Ordering::Relaxed);
148    if gsi < gsi_base {
149        log::warn!("I/O APIC: GSI {} below base {}", gsi, gsi_base);
150        return;
151    }
152    let index = gsi - gsi_base;
153
154    // Build redirection entry:
155    // [7:0]   vector
156    // [10:8]  delivery mode (000 = fixed)
157    // [11]    destination mode (0 = physical)
158    // [13]    polarity (0 = active high, 1 = active low)
159    // [15]    trigger mode (0 = edge, 1 = level)
160    // [16]    mask (0 = enabled)
161    // [63:56] destination LAPIC ID
162    let mut entry: u64 = vector as u64;
163
164    if polarity == 0x03 || polarity == 1 {
165        entry |= REDIR_ACTIVE_LOW;
166    }
167    if trigger == 0x03 || trigger == 1 {
168        entry |= REDIR_LEVEL_TRIGGER;
169    }
170
171    // Destination in bits [63:56]
172    entry |= (lapic_id as u64) << 56;
173
174    // SAFETY: I/O APIC is initialized, index is valid
175    unsafe {
176        write_redir(index, entry);
177    }
178
179    log::debug!(
180        "I/O APIC: GSI{} -> vec 0x{:02X}, LAPIC {}, pol={}, trig={}",
181        gsi,
182        vector,
183        lapic_id,
184        polarity,
185        trigger
186    );
187}
188
189/// Route a legacy ISA IRQ, applying MADT interrupt source overrides.
190///
191/// This handles the common case of IRQ0→GSI2 remapping on QEMU q35.
192pub fn route_legacy_irq(
193    irq: u8,
194    lapic_id: u32,
195    vector: u8,
196    overrides: &[Option<InterruptSourceOverride>],
197) {
198    // Check if there's a source override for this IRQ
199    let (gsi, polarity, trigger) = find_override(irq, overrides);
200
201    route_irq(gsi, lapic_id, vector, trigger, polarity);
202
203    if gsi != irq as u32 {
204        log::info!("I/O APIC: IRQ{} remapped to GSI{} (override)", irq, gsi);
205    }
206}
207
208/// Find the override for a legacy IRQ, returning (gsi, polarity, trigger).
209fn find_override(irq: u8, overrides: &[Option<InterruptSourceOverride>]) -> (u32, u8, u8) {
210    for ovr in overrides {
211        if let Some(ref o) = ovr {
212            if o.irq_source == irq {
213                return (o.gsi, o.polarity(), o.trigger_mode());
214            }
215        }
216    }
217    // No override: GSI == IRQ, ISA defaults (edge, active high)
218    (irq as u32, 0, 0)
219}
220
221/// Mask a legacy IRQ, resolving MADT overrides to the correct GSI.
222pub fn mask_legacy_irq(irq: u8, overrides: &[Option<InterruptSourceOverride>]) {
223    let (gsi, _, _) = find_override(irq, overrides);
224    mask_irq(gsi);
225    log::debug!("I/O APIC: masked legacy IRQ{} (GSI{})", irq, gsi);
226}
227
228/// Mask a GSI (disable the interrupt)
229pub fn mask_irq(gsi: u32) {
230    let gsi_base = IOAPIC_GSI_BASE.load(Ordering::Relaxed);
231    if gsi < gsi_base {
232        return;
233    }
234    let index = gsi - gsi_base;
235    // SAFETY: I/O APIC is initialized
236    unsafe {
237        let entry = read_redir(index);
238        write_redir(index, entry | REDIR_MASK);
239    }
240}
241
242/// Unmask a GSI (enable the interrupt)
243#[allow(dead_code)]
244pub fn unmask_irq(gsi: u32) {
245    let gsi_base = IOAPIC_GSI_BASE.load(Ordering::Relaxed);
246    if gsi < gsi_base {
247        return;
248    }
249    let index = gsi - gsi_base;
250    // SAFETY: I/O APIC is initialized
251    unsafe {
252        let entry = read_redir(index);
253        write_redir(index, entry & !REDIR_MASK);
254    }
255}
256
257/// Route a PCI NIC interrupt through the I/O APIC using stored MADT overrides.
258///
259/// `irq` is the PCI interrupt line (from `pci_dev.interrupt_line`).
260/// `vector` is the IDT vector to route to (typically `PIC1_OFFSET + irq` for
261/// legacy IRQs, or `irq` itself for MSI / GSIs ≥ 16).
262///
263/// This should be called from the NIC driver's `init()` after the device is
264/// registered, with interrupts on the device-side already masked via IMC so
265/// no stray IRQ arrives before the handler is live.
266pub fn route_nic_irq(irq: u8, vector: u8) {
267    let lapic_id = super::apic::lapic_id();
268    let overrides = MADT_OVERRIDES.lock();
269    route_legacy_irq(irq, lapic_id, vector, &overrides[..]);
270}
271// MADT_OVERRIDES guard dropped here : overrides no longer needed.