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// I/O APIC register offsets (indirect access)
20const IOREGSEL: u64 = 0x00;
21const IOWIN: u64 = 0x10;
22
23// I/O APIC registers (selected via IOREGSEL)
24const IOAPICID: u32 = 0x00;
25const IOAPICVER: u32 = 0x01;
26// Redirection table entries start at register 0x10
27// Each entry is 64 bits (two 32-bit registers): low at 0x10+2*n, high at 0x10+2*n+1
28const IOREDTBL_BASE: u32 = 0x10;
29
30// Redirection entry bit fields
31const REDIR_MASK: u64 = 1 << 16;
32const REDIR_LEVEL_TRIGGER: u64 = 1 << 15;
33const REDIR_ACTIVE_LOW: u64 = 1 << 13;
34
35/// Read an I/O APIC register (indirect access)
36///
37/// # Safety
38/// I/O APIC must be initialized.
39unsafe fn ioapic_read(reg: u32) -> u32 {
40    let base = IOAPIC_BASE_VIRT.load(Ordering::Relaxed);
41    // SAFETY: I/O APIC MMIO is mapped via HHDM
42    unsafe {
43        core::ptr::write_volatile((base + IOREGSEL) as *mut u32, reg);
44        core::ptr::read_volatile((base + IOWIN) as *const u32)
45    }
46}
47
48/// Write an I/O APIC register (indirect access)
49///
50/// # Safety
51/// I/O APIC must be initialized.
52unsafe fn ioapic_write(reg: u32, value: u32) {
53    let base = IOAPIC_BASE_VIRT.load(Ordering::Relaxed);
54    // SAFETY: I/O APIC MMIO is mapped via HHDM
55    unsafe {
56        core::ptr::write_volatile((base + IOREGSEL) as *mut u32, reg);
57        core::ptr::write_volatile((base + IOWIN) as *mut u32, value);
58    }
59}
60
61/// Read a 64-bit redirection entry
62///
63/// # Safety
64/// I/O APIC must be initialized, index must be valid.
65unsafe fn read_redir(index: u32) -> u64 {
66    let reg_low = IOREDTBL_BASE + index * 2;
67    let reg_high = IOREDTBL_BASE + index * 2 + 1;
68    // SAFETY: caller ensures valid index
69    let low = unsafe { ioapic_read(reg_low) } as u64;
70    let high = unsafe { ioapic_read(reg_high) } as u64;
71    low | (high << 32)
72}
73
74/// Write a 64-bit redirection entry
75///
76/// # Safety
77/// I/O APIC must be initialized, index must be valid.
78unsafe fn write_redir(index: u32, value: u64) {
79    let reg_low = IOREDTBL_BASE + index * 2;
80    let reg_high = IOREDTBL_BASE + index * 2 + 1;
81    // SAFETY: caller ensures valid index
82    unsafe {
83        ioapic_write(reg_low, value as u32);
84        ioapic_write(reg_high, (value >> 32) as u32);
85    }
86}
87
88/// Initialize the I/O APIC.
89///
90/// `phys_addr` is the I/O APIC base physical address from MADT.
91/// `gsi_base` is the first GSI handled by this I/O APIC.
92pub fn init(phys_addr: u32, gsi_base: u32) {
93    let virt_addr = memory::phys_to_virt(phys_addr as u64);
94    IOAPIC_BASE_VIRT.store(virt_addr, Ordering::Relaxed);
95    IOAPIC_GSI_BASE.store(gsi_base, Ordering::Relaxed);
96
97    // SAFETY: I/O APIC MMIO is mapped via HHDM
98    let id = unsafe { ioapic_read(IOAPICID) >> 24 };
99    let ver_reg = unsafe { ioapic_read(IOAPICVER) };
100    let version = ver_reg & 0xFF;
101    let max_redir = ((ver_reg >> 16) & 0xFF) + 1;
102
103    // Mask all interrupts initially
104    for i in 0..max_redir {
105        // SAFETY: index within max_redir
106        unsafe {
107            let entry = read_redir(i);
108            write_redir(i, entry | REDIR_MASK);
109        }
110    }
111
112    IOAPIC_INITIALIZED.store(true, Ordering::Relaxed);
113
114    log::info!(
115        "I/O APIC: id={}, version={}, {} entries, GSI base={}, virt=0x{:X}",
116        id,
117        version,
118        max_redir,
119        gsi_base,
120        virt_addr
121    );
122}
123
124/// Route a GSI to a specific LAPIC and vector.
125///
126/// `gsi` is the Global System Interrupt number.
127/// `lapic_id` is the destination LAPIC ID.
128/// `vector` is the interrupt vector (0x20+).
129/// `trigger` is the trigger mode (0=edge, 1=level).
130/// `polarity` is the polarity (0=active high, 1=active low).
131pub fn route_irq(gsi: u32, lapic_id: u32, vector: u8, trigger: u8, polarity: u8) {
132    let gsi_base = IOAPIC_GSI_BASE.load(Ordering::Relaxed);
133    if gsi < gsi_base {
134        log::warn!("I/O APIC: GSI {} below base {}", gsi, gsi_base);
135        return;
136    }
137    let index = gsi - gsi_base;
138
139    // Build redirection entry:
140    // [7:0]   vector
141    // [10:8]  delivery mode (000 = fixed)
142    // [11]    destination mode (0 = physical)
143    // [13]    polarity (0 = active high, 1 = active low)
144    // [15]    trigger mode (0 = edge, 1 = level)
145    // [16]    mask (0 = enabled)
146    // [63:56] destination LAPIC ID
147    let mut entry: u64 = vector as u64;
148
149    if polarity == 0x03 || polarity == 1 {
150        entry |= REDIR_ACTIVE_LOW;
151    }
152    if trigger == 0x03 || trigger == 1 {
153        entry |= REDIR_LEVEL_TRIGGER;
154    }
155
156    // Destination in bits [63:56]
157    entry |= (lapic_id as u64) << 56;
158
159    // SAFETY: I/O APIC is initialized, index is valid
160    unsafe {
161        write_redir(index, entry);
162    }
163
164    log::debug!(
165        "I/O APIC: GSI{} -> vec 0x{:02X}, LAPIC {}, pol={}, trig={}",
166        gsi,
167        vector,
168        lapic_id,
169        polarity,
170        trigger
171    );
172}
173
174/// Route a legacy ISA IRQ, applying MADT interrupt source overrides.
175///
176/// This handles the common case of IRQ0→GSI2 remapping on QEMU q35.
177pub fn route_legacy_irq(
178    irq: u8,
179    lapic_id: u32,
180    vector: u8,
181    overrides: &[Option<InterruptSourceOverride>],
182) {
183    // Check if there's a source override for this IRQ
184    let (gsi, polarity, trigger) = find_override(irq, overrides);
185
186    route_irq(gsi, lapic_id, vector, trigger, polarity);
187
188    if gsi != irq as u32 {
189        log::info!("I/O APIC: IRQ{} remapped to GSI{} (override)", irq, gsi);
190    }
191}
192
193/// Find the override for a legacy IRQ, returning (gsi, polarity, trigger).
194fn find_override(irq: u8, overrides: &[Option<InterruptSourceOverride>]) -> (u32, u8, u8) {
195    for ovr in overrides {
196        if let Some(ref o) = ovr {
197            if o.irq_source == irq {
198                return (o.gsi, o.polarity(), o.trigger_mode());
199            }
200        }
201    }
202    // No override: GSI == IRQ, ISA defaults (edge, active high)
203    (irq as u32, 0, 0)
204}
205
206/// Mask a legacy IRQ, resolving MADT overrides to the correct GSI.
207pub fn mask_legacy_irq(irq: u8, overrides: &[Option<InterruptSourceOverride>]) {
208    let (gsi, _, _) = find_override(irq, overrides);
209    mask_irq(gsi);
210    log::debug!("I/O APIC: masked legacy IRQ{} (GSI{})", irq, gsi);
211}
212
213/// Mask a GSI (disable the interrupt)
214pub fn mask_irq(gsi: u32) {
215    let gsi_base = IOAPIC_GSI_BASE.load(Ordering::Relaxed);
216    if gsi < gsi_base {
217        return;
218    }
219    let index = gsi - gsi_base;
220    // SAFETY: I/O APIC is initialized
221    unsafe {
222        let entry = read_redir(index);
223        write_redir(index, entry | REDIR_MASK);
224    }
225}
226
227/// Unmask a GSI (enable the interrupt)
228#[allow(dead_code)]
229pub fn unmask_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}