Skip to main content

strat9_kernel/arch/x86_64/
apic.rs

1//! Local APIC (Advanced Programmable Interrupt Controller) driver
2//!
3//! Provides MMIO-based access to the Local APIC registers.
4//! The Local APIC base address is discovered via ACPI MADT and
5//! accessed through the HHDM (Higher Half Direct Map).
6
7use crate::memory;
8use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
9
10/// Whether the Local APIC has been initialized
11static APIC_INITIALIZED: AtomicBool = AtomicBool::new(false);
12/// Whether x2APIC mode is active.
13static APIC_X2_MODE: AtomicBool = AtomicBool::new(false);
14
15/// Virtual base address of the Local APIC MMIO registers
16static APIC_BASE_VIRT: AtomicU64 = AtomicU64::new(0);
17
18/// Physical base address of the Local APIC MMIO registers (used by address-space init)
19static APIC_BASE_PHYS: AtomicU64 = AtomicU64::new(0);
20/// Per-CPU shadow of xAPIC-compatible ICR high writes (destination field).
21static ICR_HIGH_SHADOW: [AtomicU32; crate::arch::x86_64::percpu::MAX_CPUS] =
22    [const { AtomicU32::new(0) }; crate::arch::x86_64::percpu::MAX_CPUS];
23
24/// Return the physical base address of the LAPIC MMIO region, or 0 if not yet initialized.
25pub fn lapic_phys() -> u64 {
26    APIC_BASE_PHYS.load(Ordering::Relaxed)
27}
28
29// ===== Local APIC Register Offsets =====
30
31/// Local APIC ID Register
32const REG_ID: u32 = 0x020;
33/// Local APIC Version Register
34#[allow(dead_code)]
35const REG_VERSION: u32 = 0x030;
36/// Task Priority Register
37const REG_TPR: u32 = 0x080;
38/// End of Interrupt Register
39const REG_EOI: u32 = 0x0B0;
40/// Spurious Interrupt Vector Register
41const REG_SPURIOUS: u32 = 0x0F0;
42/// Error Status Register
43pub const REG_ESR: u32 = 0x280;
44/// Interrupt Command Register (low)
45pub const REG_ICR_LOW: u32 = 0x300;
46/// Interrupt Command Register (high)
47pub const REG_ICR_HIGH: u32 = 0x310;
48/// LVT Timer Register
49pub const REG_LVT_TIMER: u32 = 0x320;
50/// Timer Initial Count Register
51pub const REG_TIMER_INIT: u32 = 0x380;
52/// Timer Current Count Register
53pub const REG_TIMER_CURRENT: u32 = 0x390;
54/// Timer Divide Configuration Register
55pub const REG_TIMER_DIVIDE: u32 = 0x3E0;
56
57// ===== LVT Timer Modes =====
58
59/// LVT Timer: periodic mode (bit 17)
60pub const LVT_TIMER_PERIODIC: u32 = 1 << 17;
61/// LVT Timer: masked (bit 16)
62#[allow(dead_code)]
63pub const LVT_TIMER_MASKED: u32 = 1 << 16;
64/// Dedicated Local APIC timer interrupt vector (aligned with Theseus-style setup).
65pub const LVT_TIMER_VECTOR: u8 = 0xD2;
66
67/// MSR address for APIC base
68const IA32_APIC_BASE_MSR: u32 = 0x1B;
69/// IA32_APIC_BASE physical base address mask (up to MAXPHYADDR 52 bits).
70const APIC_BASE_ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
71/// APIC global enable bit in IA32_APIC_BASE MSR
72const APIC_BASE_ENABLE: u64 = 1 << 11;
73/// x2APIC enable bit in IA32_APIC_BASE MSR
74const APIC_BASE_EXTD: u64 = 1 << 10;
75
76/// Spurious interrupt vector number
77const SPURIOUS_VECTOR: u8 = 0xFF;
78
79/// Vector used for cross-CPU reschedule IPIs.
80pub const IPI_RESCHED_VECTOR: u8 = 0xE0;
81
82/// Vector used for TLB shootdown IPIs.
83pub const IPI_TLB_SHOOTDOWN_VECTOR: u8 = 0xF0;
84
85/// Vector used for N3 MMU migration synchronization IPIs.
86pub const IPI_N3_MIGRATE_VECTOR: u8 = 0xF1;
87
88/// Check if APIC is present via CPUID
89pub fn is_present() -> bool {
90    let (_eax, _ebx, _ecx, edx) = super::cpuid(1, 0);
91    // CPUID.01H:EDX bit 9 = APIC
92    edx & (1 << 9) != 0
93}
94
95/// Check if x2APIC is supported via CPUID
96pub fn is_x2apic_supported() -> bool {
97    let (_eax, _ebx, ecx, _edx) = super::cpuid(1, 0);
98    // CPUID.01H:ECX bit 21 = x2APIC
99    ecx & (1 << 21) != 0
100}
101
102/// Get the current Local APIC ID.
103///
104/// When APIC is initialized, read the LAPIC ID register (authoritative at runtime).
105/// Before APIC init, fall back to CPUID initial APIC ID.
106pub fn lapic_id() -> u32 {
107    if APIC_INITIALIZED.load(Ordering::Relaxed) {
108        // SAFETY: guarded by APIC_INITIALIZED.
109        let raw = unsafe { read_reg(REG_ID) };
110        if APIC_X2_MODE.load(Ordering::Relaxed) {
111            return raw;
112        }
113        return raw >> 24;
114    }
115
116    let (_eax, ebx, _ecx, _edx) = super::cpuid(1, 0);
117    (ebx >> 24) & 0xFF
118}
119
120/// Check if the Local APIC has been initialized
121pub fn is_initialized() -> bool {
122    APIC_INITIALIZED.load(Ordering::Relaxed)
123}
124
125/// Returns true when Local APIC is running in x2APIC mode.
126pub fn is_x2apic_enabled() -> bool {
127    APIC_X2_MODE.load(Ordering::Relaxed)
128}
129
130#[inline]
131fn x2apic_msr_for_reg(offset: u32) -> Option<u32> {
132    match offset {
133        REG_ID => Some(0x802),
134        REG_VERSION => Some(0x803),
135        REG_TPR => Some(0x808),
136        REG_EOI => Some(0x80B),
137        REG_SPURIOUS => Some(0x80F),
138        REG_ESR => Some(0x828),
139        REG_LVT_TIMER => Some(0x832),
140        REG_TIMER_INIT => Some(0x838),
141        REG_TIMER_CURRENT => Some(0x839),
142        REG_TIMER_DIVIDE => Some(0x83E),
143        _ => None,
144    }
145}
146
147#[inline]
148fn current_cpu_slot() -> usize {
149    let idx = crate::arch::x86_64::percpu::current_cpu_index();
150    if idx < crate::arch::x86_64::percpu::MAX_CPUS {
151        idx
152    } else {
153        0
154    }
155}
156
157/// Read a Local APIC register
158///
159/// # Safety
160/// APIC must be initialized (base address valid and mapped).
161pub unsafe fn read_reg(offset: u32) -> u32 {
162    if APIC_X2_MODE.load(Ordering::Relaxed) {
163        return match offset {
164            REG_ICR_LOW => super::rdmsr(0x830) as u32,
165            REG_ICR_HIGH => ((super::rdmsr(0x830) >> 32) as u32) << 24,
166            _ => {
167                let Some(msr) = x2apic_msr_for_reg(offset) else {
168                    return 0;
169                };
170                super::rdmsr(msr) as u32
171            }
172        };
173    }
174
175    let addr = APIC_BASE_VIRT.load(Ordering::Relaxed) + offset as u64;
176    // SAFETY: APIC MMIO is mapped via HHDM, volatile read required for MMIO
177    unsafe { core::ptr::read_volatile(addr as *const u32) }
178}
179
180/// Write a Local APIC register
181///
182/// # Safety
183/// APIC must be initialized (base address valid and mapped).
184pub unsafe fn write_reg(offset: u32, value: u32) {
185    if APIC_X2_MODE.load(Ordering::Relaxed) {
186        match offset {
187            REG_ICR_HIGH => {
188                let cpu = current_cpu_slot();
189                // xAPIC callers write destination as apic_id << 24 in ICR high.
190                // Keep this compatibility and decode to a raw x2APIC destination id.
191                let dest = if value & 0x00FF_FFFF == 0 {
192                    value >> 24
193                } else {
194                    value
195                };
196                ICR_HIGH_SHADOW[cpu].store(dest, Ordering::Relaxed);
197            }
198            REG_ICR_LOW => {
199                let cpu = current_cpu_slot();
200                let dest = ICR_HIGH_SHADOW[cpu].load(Ordering::Relaxed) as u64;
201                super::wrmsr(0x830, (dest << 32) | value as u64);
202            }
203            _ => {
204                if let Some(msr) = x2apic_msr_for_reg(offset) {
205                    super::wrmsr(msr, value as u64);
206                }
207            }
208        }
209        return;
210    }
211
212    let addr = APIC_BASE_VIRT.load(Ordering::Relaxed) + offset as u64;
213    // SAFETY: APIC MMIO is mapped via HHDM, volatile write required for MMIO
214    unsafe { core::ptr::write_volatile(addr as *mut u32, value) }
215}
216
217/// Initialize the Local APIC.
218///
219/// `madt_lapic_addr` is the LAPIC base physical address from the MADT.
220/// We use the MSR value as authoritative but log if it differs from MADT.
221pub fn init(madt_lapic_addr: u64) {
222    // Read the APIC base MSR to get the actual physical address
223    let apic_base_msr = super::rdmsr(IA32_APIC_BASE_MSR);
224    let apic_phys = apic_base_msr & APIC_BASE_ADDR_MASK;
225
226    if apic_phys != madt_lapic_addr as u64 {
227        log::warn!(
228            "LAPIC: MSR base 0x{:X} differs from MADT 0x{:X}, using MSR",
229            apic_phys,
230            madt_lapic_addr
231        );
232    }
233
234    let use_x2apic = if is_x2apic_supported() {
235        if apic_base_msr & APIC_BASE_ENABLE == 0 {
236            super::wrmsr(IA32_APIC_BASE_MSR, apic_base_msr | APIC_BASE_ENABLE);
237        }
238        super::wrmsr(
239            IA32_APIC_BASE_MSR,
240            apic_base_msr | APIC_BASE_ENABLE | APIC_BASE_EXTD,
241        );
242        let verify = super::rdmsr(IA32_APIC_BASE_MSR);
243        verify & (APIC_BASE_ENABLE | APIC_BASE_EXTD) == (APIC_BASE_ENABLE | APIC_BASE_EXTD)
244    } else {
245        if apic_base_msr & APIC_BASE_ENABLE == 0 {
246            super::wrmsr(IA32_APIC_BASE_MSR, apic_base_msr | APIC_BASE_ENABLE);
247        }
248        false
249    };
250    APIC_X2_MODE.store(use_x2apic, Ordering::Release);
251
252    // Convert physical base to virtual via HHDM
253    let apic_virt = memory::phys_to_virt(apic_phys);
254    APIC_BASE_VIRT.store(apic_virt, Ordering::Relaxed);
255    APIC_BASE_PHYS.store(apic_phys, Ordering::Relaxed);
256
257    // Diagnostic: log HHDM and computed addresses so we can detect HHDM=0 issues.
258    crate::serial_println!(
259        "[apic] init: hhdm={:#x} lapic_phys={:#x} lapic_virt={:#x}",
260        memory::hhdm_offset(),
261        apic_phys,
262        apic_virt
263    );
264    if apic_virt == apic_phys {
265        crate::serial_println!(
266            "[apic] WARN: lapic_virt == lapic_phys (HHDM offset is 0!) \
267             The LAPIC MMIO is identity-mapped at a low address. \
268             Kernel MMIO entries will be propagated to user page tables."
269        );
270    }
271
272    // SAFETY: APIC base is now set and mapped via HHDM
273    unsafe {
274        // Clear the Error Status Register (write twice per Intel SDM)
275        write_reg(REG_ESR, 0);
276        write_reg(REG_ESR, 0);
277
278        // Set Task Priority Register to 0 (accept all interrupts)
279        write_reg(REG_TPR, 0);
280
281        // Enable the APIC: set bit 8 (APIC Software Enable) + spurious vector
282        write_reg(REG_SPURIOUS, 0x100 | SPURIOUS_VECTOR as u32);
283    }
284
285    APIC_INITIALIZED.store(true, Ordering::Relaxed);
286
287    let id = lapic_id();
288    log::info!(
289        "LAPIC: initialized at phys=0x{:X} virt=0x{:X} (ID={}, mode={})",
290        apic_phys,
291        apic_virt,
292        id,
293        if use_x2apic { "x2APIC" } else { "xAPIC" }
294    );
295}
296
297/// Initialize per-core Local APIC state on Application Processors.
298///
299/// This assumes the APIC base is already mapped and `APIC_BASE_VIRT`
300/// has been set by the BSP during `init()`.
301pub fn init_ap() {
302    if !APIC_INITIALIZED.load(Ordering::Relaxed) {
303        log::warn!("LAPIC: init_ap called before init");
304        return;
305    }
306
307    if APIC_X2_MODE.load(Ordering::Acquire) {
308        let base = super::rdmsr(IA32_APIC_BASE_MSR);
309        if base & (APIC_BASE_ENABLE | APIC_BASE_EXTD) != (APIC_BASE_ENABLE | APIC_BASE_EXTD) {
310            if base & APIC_BASE_ENABLE == 0 {
311                super::wrmsr(IA32_APIC_BASE_MSR, base | APIC_BASE_ENABLE);
312            }
313            super::wrmsr(IA32_APIC_BASE_MSR, base | APIC_BASE_ENABLE | APIC_BASE_EXTD);
314        }
315    }
316
317    // SAFETY: APIC base is mapped via HHDM and APIC is enabled.
318    unsafe {
319        write_reg(REG_ESR, 0);
320        write_reg(REG_ESR, 0);
321        write_reg(REG_TPR, 0);
322        write_reg(REG_SPURIOUS, 0x100 | SPURIOUS_VECTOR as u32);
323    }
324}
325
326/// Send End-of-Interrupt to the Local APIC
327#[inline]
328pub fn eoi() {
329    // SAFETY: APIC is initialized when this is called from interrupt handlers
330    unsafe {
331        write_reg(REG_EOI, 0);
332    }
333}
334
335/// Send an IPI with a pre-built ICR low value to a specific APIC destination.
336///
337/// `icr_low` must contain delivery mode/vector/flags in xAPIC layout.
338pub fn send_ipi_raw(target_apic_id: u32, icr_low: u32) {
339    unsafe {
340        if APIC_X2_MODE.load(Ordering::Relaxed) {
341            // x2APIC: single MSR write, synchronous (CPU blocks until dispatched).
342            super::wrmsr(0x830, ((target_apic_id as u64) << 32) | icr_low as u64);
343        } else {
344            // xAPIC: two-step MMIO write (destination high, then command low).
345            write_reg(REG_ICR_HIGH, target_apic_id << 24);
346            write_reg(REG_ICR_LOW, icr_low);
347        }
348    }
349}
350
351/// Send a "reschedule now" IPI to the CPU identified by `target_apic_id`.
352///
353/// Uses Fixed delivery mode (vector `IPI_RESCHED_VECTOR`), physical destination.
354/// This is fire-and-forget : no delivery status wait is needed for reschedule
355/// IPIs because a missed IPI only delays preemption by at most one timer tick.
356///
357/// # Safety
358/// APIC must be initialized (`APIC_INITIALIZED == true`).
359pub fn send_resched_ipi(target_apic_id: u32) {
360    // Fixed delivery, edge-triggered, physical destination, no shorthand.
361    // Level bit = 0 (reserved for Fixed mode per Intel SDM Vol. 3A Table 10-1).
362    send_ipi_raw(target_apic_id, IPI_RESCHED_VECTOR as u32);
363}
364
365/// Send the reschedule IPI to the CURRENT CPU.
366///
367/// Used by timer IRQ handlers that must not switch context from inside an
368/// `extern "x86-interrupt"` frame (Ring-3-origin timer ticks): the IPI
369/// handler owns its own full context-save frame, so the preemption is done
370/// there instead, and the timer frame simply unwinds with iretq.
371pub fn self_ipi_resched() {
372    if !is_initialized() {
373        return;
374    }
375    send_resched_ipi(lapic_id());
376}