From 88b661fb183363dd5f223343168b32fb664cc929 Mon Sep 17 00:00:00 2001 From: vasilito Date: Thu, 2 Jul 2026 23:09:07 +0300 Subject: [PATCH] lapic: fix spurious interrupt vector from 0x00 to 0xFF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LAPIC Spurious Interrupt Vector Register (SVR) was set to 0x100, meaning bit 8 (APIC enable) was set but the vector field was 0x00. Vector 0 is the divide-error exception handler, so any LAPIC spurious interrupt was misrouted to divide_by_zero — a latent bug. Fix: set SVR to 0x1FF (vector 0xFF + enable bit). Vector 255 is the conventional spurious vector per Intel SDM Vol 3 §10.9. Add a dedicated lapic_spurious IDT handler at vector 0xFF that increments a counter and returns WITHOUT sending EOI (spurious interrupts must not be EOId per the spec). Override the generic interrupt stub at position 255 in the IDT and reserve bit 255. Also fix spurious_irq_resource to report LAPIC spurious count for both PIC and APIC modes instead of returning a placeholder string. --- src/arch/x86_shared/device/local_apic.rs | 18 +++++++++++++-- src/arch/x86_shared/idt.rs | 5 +++++ src/arch/x86_shared/interrupt/irq.rs | 28 ++++++++++++++---------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs index b6afe02afe..93a1372d9a 100644 --- a/src/arch/x86_shared/device/local_apic.rs +++ b/src/arch/x86_shared/device/local_apic.rs @@ -69,13 +69,27 @@ impl LocalApic { } } + /// LAPIC Spurious Interrupt Vector. + /// + /// Must not overlap with exception vectors (0–31) or generic IRQ vectors + /// (32–254). Vector 255 is the conventional choice per Intel SDM Vol 3 + /// § 10.9 ("Spurious Interrupt"). + /// + /// The previous value was 0x00 (divide-error exception), meaning any LAPIC + /// spurious interrupt was misrouted to the divide-by-zero handler — a + /// latent bug that causes undefined behaviour on real hardware. + const SPURIOUS_VECTOR: u32 = 0xFF; + + /// Spurious Interrupt Vector Register value: vector | bit 8 (APIC enable). + const SVR_VALUE: u32 = Self::SPURIOUS_VECTOR | 0x100; + unsafe fn init_ap(&mut self) { unsafe { if self.x2 { wrmsr(IA32_APIC_BASE, rdmsr(IA32_APIC_BASE) | (1 << 10)); - wrmsr(IA32_X2APIC_SIVR, 0x100); + wrmsr(IA32_X2APIC_SIVR, u64::from(Self::SVR_VALUE)); } else { - self.write(0xF0, 0x100); + self.write(0xF0, Self::SVR_VALUE); } self.setup_error_int(); //self.setup_timer(); diff --git a/src/arch/x86_shared/idt.rs b/src/arch/x86_shared/idt.rs index 500645855d..c159da6261 100644 --- a/src/arch/x86_shared/idt.rs +++ b/src/arch/x86_shared/idt.rs @@ -208,6 +208,10 @@ fn init_generic(cpu_id: LogicalCpuId, idt: &mut Idt, backup_stack_end: usize) { }); } + // Override vector 0xFF (255) with the LAPIC spurious interrupt handler. + // The generic stub loop above fills 32..=255, so this must come after. + current_idt[0xFF].set_func(irq::lapic_spurious); + // reserve bits 31:0, i.e. the first 32 interrupts, which are reserved for exceptions *current_reservations[0].get_mut() |= 0x0000_0000_FFFF_FFFF; @@ -251,6 +255,7 @@ fn init_generic(cpu_id: LogicalCpuId, idt: &mut Idt, backup_stack_end: usize) { idt.set_reserved_mut(IpiKind::Switch as u8, true); idt.set_reserved_mut(IpiKind::Tlb as u8, true); idt.set_reserved_mut(IpiKind::Pit as u8, true); + idt.set_reserved_mut(0xFF, true); #[cfg(target_arch = "x86")] { diff --git a/src/arch/x86_shared/interrupt/irq.rs b/src/arch/x86_shared/interrupt/irq.rs index a0b376bc0d..441bf49bf9 100644 --- a/src/arch/x86_shared/interrupt/irq.rs +++ b/src/arch/x86_shared/interrupt/irq.rs @@ -26,6 +26,7 @@ pub enum IrqMethod { static SPURIOUS_COUNT_IRQ7: AtomicUsize = AtomicUsize::new(0); static SPURIOUS_COUNT_IRQ15: AtomicUsize = AtomicUsize::new(0); +static SPURIOUS_COUNT_LAPIC: AtomicUsize = AtomicUsize::new(0); pub fn spurious_count_irq7() -> usize { SPURIOUS_COUNT_IRQ7.load(Ordering::Relaxed) @@ -33,20 +34,21 @@ pub fn spurious_count_irq7() -> usize { pub fn spurious_count_irq15() -> usize { SPURIOUS_COUNT_IRQ15.load(Ordering::Relaxed) } +pub fn spurious_count_lapic() -> usize { + SPURIOUS_COUNT_LAPIC.load(Ordering::Relaxed) +} pub fn spurious_count() -> usize { - spurious_count_irq7() + spurious_count_irq15() + spurious_count_irq7() + spurious_count_irq15() + spurious_count_lapic() } pub fn spurious_irq_resource(_token: &mut CleanLockToken) -> syscall::Result> { - match irq_method() { - IrqMethod::Apic => Ok(Vec::from(&b"(not implemented for APIC yet)"[..])), - IrqMethod::Pic => Ok(format!( - "{}\tIRQ7\n{}\tIRQ15\n{}\ttotal\n", - spurious_count_irq7(), - spurious_count_irq15(), - spurious_count() - ) - .into_bytes()), - } + Ok(format!( + "{}\tIRQ7\n{}\tIRQ15\n{}\tLAPIC\n{}\ttotal\n", + spurious_count_irq7(), + spurious_count_irq15(), + spurious_count_lapic(), + spurious_count() + ) + .into_bytes()) } static IRQ_METHOD: AtomicUsize = AtomicUsize::new(IrqMethod::Pic as usize); @@ -320,6 +322,10 @@ interrupt!(lapic_error, || { unsafe { lapic_eoi() }; }); +interrupt!(lapic_spurious, || { + SPURIOUS_COUNT_LAPIC.fetch_add(1, Ordering::Relaxed); +}); + interrupt_error!(generic_irq, |_stack, code| { let mut token = unsafe { CleanLockToken::new() };