/* * isr.S - Interrupt/exception stubs for amd64 * * Stack on entry to ISR stub (after CPU pushes): * [rsp+0] = error code (if any, or pushed 0) * [rsp+8] = vector (pushed by stub) * [rsp+16] = RIP * [rsp+24] = CS * [rsp+32] = RFLAGS * [rsp+40] = RSP (if privilege change) * [rsp+48] = SS (if privilege change) * * For IRQs (vector >= 32), we return via IRETQ. * For exceptions (vector < 32), we halt. */ .intel_syntax noprefix .section .text /* Mark symbols referenced from C as hidden so the C compiler uses direct * RIP-relative addressing (LEA) rather than GOT indirection. Without this, * -fPIC code collapses the GOT reference to a direct MOV that reads the * symbol's *bytes* instead of its *address*, corrupting the IDT setup. */ .hidden isr_stub_table .hidden isr_stub0 .hidden isr_stub32 .hidden isr_stub33 .hidden isr_stub_default .hidden isr_common_entry .macro ISR_NOERR num .global isr_stub\num isr_stub\num: push 0 /* fake error code */ push \num /* vector number */ jmp isr_common_entry .endm .macro ISR_ERR num .global isr_stub\num isr_stub\num: /* error code already on stack from CPU */ push \num /* vector number */ jmp isr_common_entry .endm .global isr_common_entry isr_common_entry: /* Save all general-purpose registers */ push rax push rbx push rcx push rdx push rsi push rdi push rbp push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 /* Stack layout now: * [rsp+0] = r15 * [rsp+8] = r14 * ... * [rsp+112] = rax * [rsp+120] = vector * [rsp+128] = error * [rsp+136] = rip * [rsp+144] = cs * [rsp+152] = rflags */ /* FABRIC-3.md SXXVIII (Stage 0, preemptive-context-switch trap-frame * parity, 2026-09-13): FXSAVE/FXRSTOR added here to close a real, * pre-existing gap, not just a future-preemption nicety -- confirmed * live double-precision FP code reachable from ordinary interpreter * dispatch (vm_runtime.c's Loop #5/#6 physics inference, called from * vm_tick() during word execution, not just at boot). The GPR pushes * above only cover integer state; on x86-64 SysV all XMM registers are * caller-saved, meaning the compiler is free to leave a live value in * one between any two instructions of straight-line code -- an * asynchronous interrupt is not a call site the interrupted code's own * instructions know about, so nothing here previously guaranteed FP * state actually survived a timer tick landing mid-computation. * * rbp is repurposed below as a fixed anchor pointing at "rsp right after * the 15 GPR pushes" (rbp's own true pushed value already lives safely * on the stack above and is restored by the ordinary `pop rbp` later) -- * FXSAVE requires a 16-byte-aligned address and rsp's alignment here is * unpredictable (the interrupt could have landed anywhere), so the * FXSAVE area is carved out and aligned separately, with rbp used to * keep addressing everything else at its original, fixed offsets * regardless of that adjustment. */ mov rbp, rsp sub rsp, 528 /* 512-byte FXSAVE area + 16 slack for alignment */ and rsp, -16 fxsave [rsp] /* Set up arguments for isr_common_handler(vector, error, rip, cs, rflags, cr2) */ mov rdi, [rbp + 120] /* vector */ mov rsi, [rbp + 128] /* error */ mov rdx, [rbp + 136] /* rip */ mov rcx, [rbp + 144] /* cs */ mov r8, [rbp + 152] /* rflags */ mov r9, cr2 /* cr2 */ /* Pass stack frame pointer (after pushes) as 7th argument */ push rbp call isr_common_handler add rsp, 8 fxrstor [rsp] mov rsp, rbp /* collapse back to right after the GPR pushes */ /* Restore general-purpose registers */ pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rbp pop rdi pop rsi pop rdx pop rcx pop rbx pop rax /* Remove vector and error code from stack */ add rsp, 16 /* Return from interrupt */ iretq /* Exception/interrupt stubs (0-31 explicit) */ ISR_NOERR 0 ISR_NOERR 1 ISR_NOERR 2 ISR_NOERR 3 ISR_NOERR 4 ISR_NOERR 5 ISR_NOERR 6 ISR_NOERR 7 ISR_ERR 8 ISR_NOERR 9 ISR_ERR 10 ISR_ERR 11 ISR_ERR 12 ISR_ERR 13 ISR_ERR 14 ISR_NOERR 15 ISR_NOERR 16 ISR_ERR 17 ISR_NOERR 18 ISR_NOERR 19 ISR_NOERR 20 ISR_ERR 21 ISR_NOERR 22 ISR_NOERR 23 ISR_NOERR 24 ISR_NOERR 25 ISR_NOERR 26 ISR_NOERR 27 ISR_NOERR 28 ISR_NOERR 29 ISR_ERR 30 ISR_NOERR 31 /* Stub for APIC Timer (vector 0x20 = 32) */ /* Diagnostic: write raw 'I' to COM1 (0x3F8) when stub is entered. * If 'I' never appears in the serial log, the stub is never reached * (bad IDT entry or fault during CPU interrupt delivery). */ .global isr_stub32 isr_stub32: push 0 push 0x20 jmp isr_common_entry /* Stub for i8042 keyboard IRQ1, routed via I/O APIC to vector 0x21 = 33 * (item 4.3.5, FABRIC-0.md ยง27.5). Before this stub existed, vector 33 fell * through to isr_stub_default below, which unconditionally reports "255" * regardless of which IDT slot actually fired -- the CPU legitimately took * IDT[33] (confirmed via the LAPIC's own ISR register), but isr_common_handler * only ever saw vector 255 and treated a real keyboard IRQ as spurious, * silently skipping EOI and leaving vector 33 permanently stuck in-service. */ .global isr_stub33 isr_stub33: push 0 push 33 jmp isr_common_entry /* Default stub for other vectors (34-255) */ .global isr_stub_default isr_stub_default: push 0 /* fake error */ push 255 /* unknown vector, will be logged */ jmp isr_common_entry /* Use .data instead of .rodata so UEFI applies relocations */ .section .data .align 8 .global isr_stub_table isr_stub_table: .quad isr_stub0, isr_stub1, isr_stub2, isr_stub3 .quad isr_stub4, isr_stub5, isr_stub6, isr_stub7 .quad isr_stub8, isr_stub9, isr_stub10, isr_stub11 .quad isr_stub12, isr_stub13, isr_stub14, isr_stub15 .quad isr_stub16, isr_stub17, isr_stub18, isr_stub19 .quad isr_stub20, isr_stub21, isr_stub22, isr_stub23 .quad isr_stub24, isr_stub25, isr_stub26, isr_stub27 .quad isr_stub28, isr_stub29, isr_stub30, isr_stub31 /* Vector 32 = APIC Timer */ .quad isr_stub32 /* Vector 33 = i8042 keyboard IRQ1 (item 4.3.5) */ .quad isr_stub33 /* Vectors 34-255 default to unknown stub */ .rept 222 .quad isr_stub_default .endr