lapic: fix spurious interrupt vector from 0x00 to 0xFF

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.
This commit is contained in:
2026-07-02 23:09:07 +03:00
parent c46d3a90eb
commit 88b661fb18
3 changed files with 38 additions and 13 deletions
+16 -2
View File
@@ -69,13 +69,27 @@ impl LocalApic {
}
}
/// LAPIC Spurious Interrupt Vector.
///
/// Must not overlap with exception vectors (031) or generic IRQ vectors
/// (32254). 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();
+5
View File
@@ -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")]
{
+13 -7
View File
@@ -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<Vec<u8>> {
match irq_method() {
IrqMethod::Apic => Ok(Vec::from(&b"(not implemented for APIC yet)"[..])),
IrqMethod::Pic => Ok(format!(
"{}\tIRQ7\n{}\tIRQ15\n{}\ttotal\n",
Ok(format!(
"{}\tIRQ7\n{}\tIRQ15\n{}\tLAPIC\n{}\ttotal\n",
spurious_count_irq7(),
spurious_count_irq15(),
spurious_count_lapic(),
spurious_count()
)
.into_bytes()),
}
.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() };