Skip to main content

strat9_kernel/arch/x86_64/
idt.rs

1//! Interrupt Descriptor Table (IDT) for Strat9-OS
2//!
3//! Handles CPU exceptions and hardware IRQs.
4//! Inspired by MaestroOS `idt.rs` and Redox-OS kernel.
5
6use super::{pic, tss};
7use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
8use x86_64::{
9    structures::{
10        gdt::SegmentSelector,
11        idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
12    },
13    VirtAddr,
14};
15
16const KERNEL_CODE_SELECTOR: SegmentSelector = SegmentSelector(0x08);
17
18#[repr(C, packed)]
19struct Idtr {
20    limit: u16,
21    base: u64,
22}
23
24#[derive(Clone, Copy, Debug)]
25pub struct LiveIdtGateInfo {
26    pub vector: u8,
27    pub selector: u16,
28    pub options: u16,
29    pub offset: u64,
30}
31
32/// IRQ interrupt vector numbers (PIC1_OFFSET + IRQ number)
33#[allow(dead_code)]
34pub mod irq {
35    pub const TIMER: u8 = super::pic::PIC1_OFFSET; // IRQ0 = 0x20
36    pub const KEYBOARD: u8 = super::pic::PIC1_OFFSET + 1; // IRQ1 = 0x21
37    pub const CASCADE: u8 = super::pic::PIC1_OFFSET + 2; // IRQ2 = 0x22
38    pub const MOUSE: u8 = super::pic::PIC1_OFFSET + 12; // IRQ12 = 0x2C
39    pub const COM2: u8 = super::pic::PIC1_OFFSET + 3; // IRQ3 = 0x23
40    pub const COM1: u8 = super::pic::PIC1_OFFSET + 4; // IRQ4 = 0x24
41    pub const FLOPPY: u8 = super::pic::PIC1_OFFSET + 6; // IRQ6 = 0x26
42    pub const ATA_PRIMARY: u8 = super::pic::PIC1_OFFSET + 14; // IRQ14 = 0x2E
43    pub const ATA_SECONDARY: u8 = super::pic::PIC1_OFFSET + 15; // IRQ15 = 0x2F
44}
45
46/// RAII guard: swap GS to kernel on entry if we came from Ring 3, and restore
47/// user GS automatically on drop (covers every exit path including early returns).
48///
49/// # Why this is needed
50/// After `swapgs ; iretq` in the Ring-3 trampoline:
51///   - `IA32_GS_BASE`        = 0  (user GS base, inactive)
52///   - `IA32_KERNEL_GS_BASE` = kernel per-CPU pointer
53/// When an interrupt fires from Ring 3, the CPU does NOT automatically call
54/// swapgs.  The first `gs:[0]` access (e.g. in `current_cpu_index`) would
55/// dereference virtual address 0 → page fault → double fault → triple fault.
56///
57/// # Safety
58/// Must be constructed **before** any `gs:[…]` access in the handler.
59/// `InterruptStackFrame::code_segment` is a plain memory read from the
60/// interrupt stack : it does not access GS.
61struct SwapGsGuard {
62    from_ring3: bool,
63}
64
65impl SwapGsGuard {
66    /// Construct.  If `from_ring3` is true, executes `swapgs` immediately to
67    /// restore the kernel per-CPU GS base.
68    #[inline(always)]
69    fn new(from_ring3: bool) -> Self {
70        if from_ring3 {
71            // SAFETY: We are in Ring 0 with interrupts disabled (standard for
72            // interrupt handlers).  GS_BASE currently points at user space (0);
73            // swapgs gives us the kernel per-CPU block via KERNEL_GS_BASE.
74            unsafe { core::arch::asm!("swapgs", options(nostack, preserves_flags)) };
75        }
76        Self { from_ring3 }
77    }
78}
79
80impl Drop for SwapGsGuard {
81    #[inline(always)]
82    fn drop(&mut self) {
83        if self.from_ring3 {
84            // SAFETY: Symmetric to the constructor.  Restores user GS_BASE so
85            // that iretq returns to Ring 3 with the correct GS state.
86            unsafe { core::arch::asm!("swapgs", options(nostack, preserves_flags)) };
87        }
88    }
89}
90
91/// Determine whether `swapgs` is needed at interrupt/exception entry.
92///
93/// In the normal case, `code_segment & 3 == 3` (Ring 3) means we need
94/// swapgs.  However, between `swapgs` and `iretq` in
95/// `elf_ring3_trampoline`, CS is still Ring 0 but `IA32_GS_BASE` is
96/// already the user value (0).  If `iretq` itself faults, the exception
97/// handler sees CS=Ring 0 but GS=user : the simple ring check misses
98/// this.  Reading `IA32_GS_BASE` via `rdmsr` catches both cases.
99///
100/// Cost: ~20-30 cycles for the `rdmsr` : acceptable in exception paths
101/// (not used for high-frequency IRQ handlers where IF=0 prevents
102/// firing in the swapgs→iretq window).
103#[inline(always)]
104fn needs_swapgs(cs: u16) -> bool {
105    // Fast path: Ring 3 → always need swapgs.
106    if (cs & 3) == 3 {
107        return true;
108    }
109    // Slow path: check if GS_BASE unexpectedly points to user space.
110    // SAFETY: rdmsr is privileged but we are in Ring 0 (exception handler).
111    let gs_base: u64 = unsafe {
112        let lo: u32;
113        let hi: u32;
114        core::arch::asm!(
115            "rdmsr",
116            in("ecx") 0xC000_0101u32,  // IA32_GS_BASE
117            out("eax") lo,
118            out("edx") hi,
119            options(nostack, preserves_flags),
120        );
121        (lo as u64) | ((hi as u64) << 32)
122    };
123    gs_base < 0xFFFF_8000_0000_0000
124}
125
126/// Static IDT storage (must be 'static for load())
127static mut IDT_STORAGE: InterruptDescriptorTable = InterruptDescriptorTable::new();
128static IDT_STORAGE_LOCK: AtomicBool = AtomicBool::new(false);
129static USER_PF_TRACE_BUDGET: AtomicU32 = AtomicU32::new(64);
130static RESCHED_IPI_TRACE_BUDGET: AtomicU32 = AtomicU32::new(32);
131
132pub fn live_gate_info(vector: u8) -> Option<LiveIdtGateInfo> {
133    let mut idtr = Idtr { limit: 0, base: 0 };
134    // SAFETY: `sidt` is a privileged register read with no side effect.
135    unsafe {
136        core::arch::asm!(
137            "sidt [{}]",
138            in(reg) &mut idtr,
139            options(nostack, preserves_flags),
140        );
141    }
142
143    let entry_offset = vector as usize * 16;
144    if entry_offset + 16 > idtr.limit as usize + 1 {
145        return None;
146    }
147
148    // SAFETY: The IDTR base/limit were read from the CPU and bounds-checked above.
149    let (low, high) = unsafe {
150        let entry_ptr = (idtr.base + entry_offset as u64) as *const u64;
151        (
152            core::ptr::read_unaligned(entry_ptr),
153            core::ptr::read_unaligned(entry_ptr.add(1)),
154        )
155    };
156
157    let offset = (low & 0xFFFF) | (((low >> 48) & 0xFFFF) << 16) | ((high & 0xFFFF_FFFF) << 32);
158    let selector = ((low >> 16) & 0xFFFF) as u16;
159    let options = ((low >> 32) & 0xFFFF) as u16;
160
161    Some(LiveIdtGateInfo {
162        vector,
163        selector,
164        options,
165        offset,
166    })
167}
168
169/// Decision returned by the raw interrupt trampolines.
170///
171/// Phase 1 of the preemptive scheduler refactor only wires the raw timer/IPI
172/// stubs and returns `next_rsp = 0`, which means "restore the current
173/// interrupt frame and return with iretq". The future interrupt-aware scheduler
174/// path will return a non-zero `next_rsp` and matching FPU buffers.
175#[repr(C)]
176#[derive(Clone, Copy, Debug, Default)]
177pub struct InterruptReturnDecision {
178    pub next_rsp: u64,
179    pub old_fpu: *mut u8,
180    pub new_fpu: *const u8,
181}
182
183/// Raw Local APIC timer interrupt entry.
184///
185/// Saves registers in exactly the same order as `SyscallFrame`, calls the Rust
186/// inner handler, then restores the interrupted context and returns with
187/// `iretq`. This avoids the `extern "x86-interrupt"` ABI mismatch with the
188/// legacy `ret`-based scheduler switch path.
189#[unsafe(naked)]
190unsafe extern "C" fn lapic_timer_entry() -> ! {
191    core::arch::naked_asm!(
192        "cld",
193        // Hardware IRQ stack frame at entry:
194        //   [rsp+0]  = RIP
195        //   [rsp+8]  = CS
196        //   [rsp+16] = RFLAGS
197        //   [rsp+24] = RSP
198        //   [rsp+32] = SS
199        // If interrupted from Ring 3, restore kernel GS before any percpu use.
200        "test qword ptr [rsp + 8], 0x3",
201        "jz 2f",
202        "swapgs",
203        "2:",
204        // Save GPRs in reverse order so that final RSP points at a SyscallFrame.
205        "push rax",
206        "push rcx",
207        "push rdx",
208        "push rdi",
209        "push rsi",
210        "push r8",
211        "push r9",
212        "push r10",
213        "push r11",
214        "push rbx",
215        "push rbp",
216        "push r12",
217        "push r13",
218        "push r14",
219        "push r15",
220        // SysV large-struct return uses an implicit out-pointer in RDI.
221        // Reserve 32 bytes to keep 16-byte alignment before `call`.
222        "sub rsp, 32",
223        "mov rdi, rsp",
224        "lea rsi, [rsp + 32]",
225        "call {inner}",
226        // Load returned InterruptReturnDecision fields.
227        "mov rax, [rsp + 0]",
228        "mov rdx, [rsp + 8]",
229        "mov rcx, [rsp + 16]",
230        "add rsp, 32",
231        "test rax, rax",
232        "jz 3f",
233        // Context switch path: save old task's FPU, switch stack, restore new task's FPU.
234        "fxsave [rdx]",
235        "mov rsp, rax",
236        "fxrstor [rcx]",
237        "call {switch_finish}",
238        "3:",
239        // No context switch (rax == 0): skip FPU save/restore entirely.
240        // The interrupted task's FPU state remains unchanged.
241        // Restore current SyscallFrame.
242        "pop r15",
243        "pop r14",
244        "pop r13",
245        "pop r12",
246        "pop rbp",
247        "pop rbx",
248        "pop r11",
249        "pop r10",
250        "pop r9",
251        "pop r8",
252        "pop rsi",
253        "pop rdi",
254        "pop rdx",
255        "pop rcx",
256        "pop rax",
257        // Restore user GS iff we are returning to Ring 3.
258        "test qword ptr [rsp + 8], 0x3",
259        "jz 4f",
260        "swapgs",
261        "4:",
262        "iretq",
263        inner = sym lapic_timer_inner,
264        switch_finish = sym crate::process::scheduler::finish_interrupt_switch,
265    );
266}
267
268/// Raw reschedule IPI entry.
269///
270/// Uses the same `SyscallFrame` layout as the timer entry. Phase 1 only marks
271/// a reschedule hint and returns to the interrupted context.
272#[unsafe(naked)]
273unsafe extern "C" fn resched_ipi_entry() -> ! {
274    core::arch::naked_asm!(
275        "cld",
276        "test qword ptr [rsp + 8], 0x3",
277        "jz 2f",
278        "swapgs",
279        "2:",
280        "push rax",
281        "mov al, 0x65",
282        "out 0xe9, al",
283        "push rcx",
284        "push rdx",
285        "push rdi",
286        "push rsi",
287        "push r8",
288        "push r9",
289        "push r10",
290        "push r11",
291        "push rbx",
292        "push rbp",
293        "push r12",
294        "push r13",
295        "push r14",
296        "push r15",
297        "sub rsp, 32",
298        "mov al, 0x45",
299        "out 0xe9, al",
300        "mov rdi, rsp",
301        "lea rsi, [rsp + 32]",
302        "call {inner}",
303        "mov rax, [rsp + 0]",
304        "mov rdx, [rsp + 8]",
305        "mov rcx, [rsp + 16]",
306        "add rsp, 32",
307        "test rax, rax",
308        "jz 3f",
309        "ud2",
310        "3:",
311        "pop r15",
312        "pop r14",
313        "pop r13",
314        "pop r12",
315        "pop rbp",
316        "pop rbx",
317        "pop r11",
318        "pop r10",
319        "pop r9",
320        "pop r8",
321        "pop rsi",
322        "pop rdi",
323        "pop rdx",
324        "pop rcx",
325        "pop rax",
326        "test qword ptr [rsp + 8], 0x3",
327        "jz 4f",
328        "swapgs",
329        "4:",
330        "iretq",
331        inner = sym resched_ipi_inner,
332    );
333}
334
335extern "C" fn lapic_timer_inner(
336    frame: &mut crate::syscall::SyscallFrame,
337) -> InterruptReturnDecision {
338    let cpu = crate::arch::x86_64::percpu::current_cpu_index();
339    let ticks = crate::process::scheduler::ticks();
340    // Heartbeat: single byte only. e9_println!/format_args in IRQ can cause issues.
341    let from_ring3 = (frame.iret_cs & 3) == 3;
342    if from_ring3 && (ticks < 5 || ticks % 100 == 0) {
343        unsafe { core::arch::asm!("mov al, 0x48; out 0xe9, al", out("al") _) } // 'H'
344    }
345    crate::process::scheduler::timer_tick();
346    crate::arch::x86_64::speaker::speaker_tick();
347    super::apic::eoi();
348
349    // Deliver pending POSIX signals before returning to Ring 3 via iretq.
350    // On this IRQ-return path we only perform deliveries that are safe from
351    // timer interrupt context; fatal/default actions remain deferred to the
352    // normal syscall-side delivery path, which may kill/switch the current
353    // task and is not yet validated on the raw timer-iret path.
354    if from_ring3 {
355        crate::process::signal::deliver_pending_signal_on_interrupt_return(frame);
356    }
357
358    // Temporarily keep timer IRQs side-effect free with respect to stack
359    // switching. The raw `iretq`-based resume path is not yet correct for all
360    // contexts:
361    // - Ring 3 resumes can return with a shifted IRET frame under SMP load.
362    // - Ring 0 resumes are fundamentally different because same-CPL `iretq`
363    //   does not restore RSP/SS, so synthetic `SyscallFrame` resumes of kernel
364    //   tasks can continue with a bogus stack pointer and RIP=0.
365    // Keep only the reschedule hint here and let tasks switch on safer paths
366    // (blocking syscalls, explicit yields, future validated return path).
367    crate::process::scheduler::request_force_resched_hint(cpu);
368    InterruptReturnDecision::default()
369}
370
371extern "C" fn resched_ipi_inner(
372    frame: &mut crate::syscall::SyscallFrame,
373) -> InterruptReturnDecision {
374    let cpu = crate::arch::x86_64::percpu::current_cpu_index();
375    let should_trace = RESCHED_IPI_TRACE_BUDGET
376        .fetch_update(Ordering::AcqRel, Ordering::Relaxed, |budget| {
377            budget.checked_sub(1)
378        })
379        .is_ok();
380    if should_trace {
381        let rsp0 = crate::arch::x86_64::tss::kernel_stack_for(cpu)
382            .map(|addr| addr.as_u64())
383            .unwrap_or(0);
384        let (slot_rip, slot_cs, slot_rsp, slot_ss) = if rsp0 >= 40 {
385            // SAFETY: rsp0 points at the top of the current CPU's kernel stack.
386            // During a Ring3->Ring0 interrupt, the CPU-saved IRET frame lives at
387            // [rsp0-40 .. rsp0-8]. We only read those 5 u64 words for diagnosis.
388            unsafe {
389                let frame_base = (rsp0 - 40) as *const u64;
390                (
391                    *frame_base.add(0),
392                    *frame_base.add(1),
393                    *frame_base.add(3),
394                    *frame_base.add(4),
395                )
396            }
397        } else {
398            (0, 0, 0, 0)
399        };
400        crate::e9_println!(
401            "[ipi-rsp0] cpu={} rsp0={:#x} slot_rip={:#x} slot_cs={:#x} slot_rsp={:#x} slot_ss={:#x} frame_rip={:#x} frame_cs={:#x} frame_rsp={:#x} frame_ss={:#x}",
402            cpu,
403            rsp0,
404            slot_rip,
405            slot_cs,
406            slot_rsp,
407            slot_ss,
408            frame.iret_rip,
409            frame.iret_cs,
410            frame.iret_rsp,
411            frame.iret_ss,
412        );
413    }
414    super::apic::eoi();
415    crate::process::scheduler::request_force_resched_hint(cpu);
416    InterruptReturnDecision::default()
417}
418
419#[inline]
420fn lock_idt_storage() {
421    while IDT_STORAGE_LOCK
422        .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
423        .is_err()
424    {
425        core::hint::spin_loop();
426    }
427}
428
429#[inline]
430fn unlock_idt_storage() {
431    IDT_STORAGE_LOCK.store(false, Ordering::Release);
432}
433
434pub fn init() {
435    lock_idt_storage();
436    unsafe {
437        let idt = &raw mut IDT_STORAGE;
438
439        // CPU exceptions
440        (*idt)
441            .breakpoint
442            .set_handler_fn(breakpoint_handler)
443            .set_code_selector(KERNEL_CODE_SELECTOR);
444        (*idt)
445            .page_fault
446            .set_handler_fn(page_fault_handler)
447            .set_code_selector(KERNEL_CODE_SELECTOR);
448        (*idt)
449            .general_protection_fault
450            .set_handler_fn(general_protection_fault_handler)
451            .set_code_selector(KERNEL_CODE_SELECTOR);
452        (*idt)
453            .stack_segment_fault
454            .set_handler_fn(stack_segment_fault_handler)
455            .set_code_selector(KERNEL_CODE_SELECTOR);
456        (*idt)
457            .non_maskable_interrupt
458            .set_handler_fn(non_maskable_interrupt_handler)
459            .set_code_selector(KERNEL_CODE_SELECTOR);
460        (*idt)
461            .invalid_opcode
462            .set_handler_fn(invalid_opcode_handler)
463            .set_code_selector(KERNEL_CODE_SELECTOR);
464        (*idt)
465            .double_fault
466            .set_handler_fn(double_fault_handler)
467            .set_code_selector(KERNEL_CODE_SELECTOR)
468            .set_stack_index(tss::DOUBLE_FAULT_IST_INDEX);
469
470        // Hardware IRQs (PIC remapped to 0x20+)
471        let idt_ref = &mut *idt;
472        idt_ref[irq::TIMER as u8]
473            .set_handler_fn(legacy_timer_handler)
474            .set_code_selector(KERNEL_CODE_SELECTOR);
475        idt_ref[irq::KEYBOARD as u8]
476            .set_handler_fn(keyboard_handler)
477            .set_code_selector(KERNEL_CODE_SELECTOR);
478        idt_ref[irq::MOUSE as u8]
479            .set_handler_fn(mouse_handler)
480            .set_code_selector(KERNEL_CODE_SELECTOR);
481
482        // Spurious interrupt handler at vector 0xFF (APIC spurious vector)
483        idt_ref[0xFF_u8]
484            .set_handler_fn(spurious_handler)
485            .set_code_selector(KERNEL_CODE_SELECTOR);
486
487        // Cross-CPU reschedule IPI (vector 0xE0)
488
489        idt_ref[super::apic::IPI_RESCHED_VECTOR as u8]
490            .set_handler_addr(VirtAddr::from_ptr(resched_ipi_entry as *const ()))
491            .set_code_selector(KERNEL_CODE_SELECTOR);
492
493        // Cross-CPU TLB shootdown IPI (vector 0xF0)
494        idt_ref[super::apic::IPI_TLB_SHOOTDOWN_VECTOR as u8]
495            .set_handler_fn(tlb_shootdown_handler)
496            .set_code_selector(KERNEL_CODE_SELECTOR);
497
498        (*idt).load_unsafe();
499    }
500    unlock_idt_storage();
501
502    log::debug!("IDT initialized with {} entries", 256);
503}
504
505pub fn load() {
506    lock_idt_storage();
507    unsafe {
508        let idt = &raw const IDT_STORAGE;
509        (*idt).load_unsafe();
510    }
511    unlock_idt_storage();
512}
513
514/// Register the Local APIC timer IRQ vector to use the timer handler.
515pub fn register_lapic_timer_vector(vector: u8) {
516    lock_idt_storage();
517    unsafe {
518        let idt = &raw mut IDT_STORAGE;
519        (&mut *idt)[vector]
520            .set_handler_addr(VirtAddr::from_ptr(lapic_timer_entry as *const ()))
521            .set_code_selector(KERNEL_CODE_SELECTOR);
522        (*idt).load_unsafe();
523    }
524    unlock_idt_storage();
525}
526
527/// Register the AHCI storage controller IRQ handler.
528///
529/// Called after AHCI initialisation once the PCI interrupt line is known.
530pub fn register_ahci_irq(irq: u8) {
531    let vector = if irq < 16 {
532        super::pic::PIC1_OFFSET + irq
533    } else {
534        irq
535    };
536
537    lock_idt_storage();
538    unsafe {
539        let idt = &raw mut IDT_STORAGE;
540        (&mut *idt)[vector]
541            .set_handler_fn(ahci_handler)
542            .set_code_selector(KERNEL_CODE_SELECTOR);
543        (*idt).load_unsafe();
544    }
545    unlock_idt_storage();
546    log::info!("AHCI IRQ {} registered on vector {:#x}", irq, vector);
547}
548
549/// Register the NVMe storage controller IRQ handler.
550///
551/// Called after NVMe initialisation once the PCI interrupt line is known.
552pub fn register_nvme_irq(irq: u8) {
553    let vector = if irq < 16 {
554        super::pic::PIC1_OFFSET + irq
555    } else {
556        irq
557    };
558
559    lock_idt_storage();
560    unsafe {
561        let idt = &raw mut IDT_STORAGE;
562        (&mut *idt)[vector]
563            .set_handler_fn(nvme_handler)
564            .set_code_selector(KERNEL_CODE_SELECTOR);
565        (*idt).load_unsafe();
566    }
567    unlock_idt_storage();
568    log::info!("NVMe IRQ {} registered on vector {:#x}", irq, vector);
569}
570
571/// Register the VirtIO block device IRQ handler
572///
573/// Called after VirtIO block device initialization to route the device's
574/// IRQ to the correct handler.
575pub fn register_virtio_block_irq(irq: u8) {
576    // PCI INTx gives an IRQ line number (typically 0..15), while IDT expects
577    // a vector number. Map legacy IRQ lines to the remapped interrupt vectors.
578    let vector = if irq < 16 {
579        super::pic::PIC1_OFFSET + irq
580    } else {
581        irq
582    };
583
584    lock_idt_storage();
585    unsafe {
586        let idt = &raw mut IDT_STORAGE;
587        (&mut *idt)[vector]
588            .set_handler_fn(virtio_block_handler)
589            .set_code_selector(KERNEL_CODE_SELECTOR);
590        (*idt).load_unsafe();
591    }
592    unlock_idt_storage();
593    log::info!("VirtIO-blk IRQ {} registered on vector {:#x}", irq, vector);
594}
595
596/// Register the xHCI USB controller IRQ handler.
597///
598/// Called after xHCI initialization once the PCI interrupt line is known.
599pub fn register_xhci_irq(irq: u8) {
600    let vector = if irq < 16 {
601        super::pic::PIC1_OFFSET + irq
602    } else {
603        irq
604    };
605
606    lock_idt_storage();
607    unsafe {
608        let idt = &raw mut IDT_STORAGE;
609        (&mut *idt)[vector]
610            .set_handler_fn(xhci_handler)
611            .set_code_selector(KERNEL_CODE_SELECTOR);
612        (*idt).load_unsafe();
613    }
614    unlock_idt_storage();
615    log::info!("xHCI IRQ {} registered on vector {:#x}", irq, vector);
616}
617
618/// Register the NIC IRQ handler.
619///
620/// Called after a NIC driver successfully initialises and has read its
621/// PCI interrupt line.  The handler reads ICR and sends EOI; actual
622/// receive processing is triggered via the driver's `receive()` method
623/// which the network stack calls when `handle_interrupt()` signals
624/// that packets are available.
625pub fn register_nic_irq(irq: u8) {
626    let vector = if irq < 16 {
627        super::pic::PIC1_OFFSET + irq
628    } else {
629        irq
630    };
631
632    lock_idt_storage();
633    unsafe {
634        let idt = &raw mut IDT_STORAGE;
635        (&mut *idt)[vector]
636            .set_handler_fn(nic_handler)
637            .set_code_selector(KERNEL_CODE_SELECTOR);
638        (*idt).load_unsafe();
639    }
640    unlock_idt_storage();
641    log::info!("NIC IRQ {} registered on vector {:#x}", irq, vector);
642}
643
644// =============================================
645// CPU Exception Handlers
646// =============================================
647
648/// Performs the breakpoint handler operation.
649extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
650    let _gs = SwapGsGuard::new(needs_swapgs(stack_frame.code_segment.0));
651    log::warn!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
652}
653
654/// Performs the invalid opcode handler operation.
655extern "x86-interrupt" fn invalid_opcode_handler(stack_frame: InterruptStackFrame) {
656    let cs = stack_frame.code_segment.0;
657    let is_user = (cs & 3) == 3;
658    let _gs = SwapGsGuard::new(needs_swapgs(cs));
659    if is_user {
660        if let Some(tid) = crate::process::current_task_id() {
661            crate::silo::handle_user_fault(
662                tid,
663                crate::silo::SiloFaultReason::InvalidOpcode,
664                stack_frame.instruction_pointer.as_u64(),
665                0,
666                stack_frame.instruction_pointer.as_u64(),
667            );
668            return;
669        }
670    }
671    log::error!("EXCEPTION: INVALID OPCODE\n{:#?}", stack_frame);
672    panic!("Invalid opcode");
673}
674
675extern "x86-interrupt" fn non_maskable_interrupt_handler(stack_frame: InterruptStackFrame) {
676    // NMI can fire at any point : including the swapgs→iretq window.
677    // Use rdmsr to safely restore kernel GS if needed.
678    let _gs = SwapGsGuard::new(needs_swapgs(stack_frame.code_segment.0));
679    if crate::boot::panic::panic_in_progress() {
680        crate::arch::x86_64::cli();
681        loop {
682            crate::arch::x86_64::hlt();
683        }
684    }
685    crate::serial_force_println!(
686        "[NMI] rip={:#x} cs={:#x}",
687        stack_frame.instruction_pointer.as_u64(),
688        stack_frame.code_segment.0
689    );
690    crate::arch::x86_64::cli();
691    loop {
692        crate::arch::x86_64::hlt();
693    }
694}
695
696/// Performs the page fault handler operation.
697extern "x86-interrupt" fn page_fault_handler(
698    stack_frame: InterruptStackFrame,
699    error_code: PageFaultErrorCode,
700) {
701    use x86_64::registers::control::Cr2;
702    let cs = stack_frame.code_segment.0;
703    let is_user = (cs & 3) == 3;
704    // SAFETY: must be before any gs:[...] access – GS may point to user memory
705    // if the fault fired from Ring 3 (after swapgs in elf_ring3_trampoline),
706    // OR during the swapgs→iretq window (CS=Ring0 but GS=user).
707    // needs_swapgs() uses rdmsr to catch both cases.
708    let swapgs_needed = needs_swapgs(cs);
709    let _gs = SwapGsGuard::new(swapgs_needed);
710
711    // Detect the swapgs→iretq window: CS=Ring0 but GS was user (0).
712    if swapgs_needed && !is_user {
713        let fault_addr = x86_64::registers::control::Cr2::read()
714            .as_ref()
715            .map(|v| v.as_u64())
716            .unwrap_or(0);
717        crate::serial_force_println!(
718            "\x1b[31;1m[pagefault]\x1b[0m SWAPGS-WINDOW: CS={:#x} (Ring0) but GS was user! rip={:#x} addr={:#x} err={:#x}",
719            cs,
720            stack_frame.instruction_pointer.as_u64(),
721            fault_addr,
722            error_code.bits()
723        );
724
725        // Kill the faulting process instead of panicking the kernel.
726        // The page fault is unrecoverable in this window: we cannot return
727        // to Ring 3 because the IRETQ frame or a user page is invalid.
728        // Keep SwapGsGuard alive until AFTER current_task_clone to avoid
729        // a second #PF from current_cpu_index() reading gs:[0] (kernel GS
730        // is still needed for percpu access during the kill path).
731        if let Some(task) = crate::process::current_task_clone() {
732            crate::process::kill_task(task.id);
733        }
734        drop(_gs);
735        crate::process::scheduler::exit_current_task(-11); // SIGSEGV
736    }
737
738    // Get the faulting address
739    let fault_addr = Cr2::read();
740    let fault_vaddr = fault_addr.as_ref().map(|v| v.as_u64()).unwrap_or(0);
741    let rip = stack_frame.instruction_pointer.as_u64();
742    let user_rsp = stack_frame.stack_pointer.as_u64();
743
744    let mut trace_ctx = crate::trace::TraceTaskCtx::empty();
745    if is_user {
746        if let Some(task) = crate::process::current_task_clone() {
747            let as_ref = task.process.address_space_arc();
748            trace_ctx = crate::trace::TraceTaskCtx {
749                task_id: task.id.as_u64(),
750                pid: task.pid,
751                tid: task.tid,
752                cr3: as_ref.cr3().as_u64(),
753            };
754        }
755    }
756
757    let do_pf_trace = if is_user {
758        USER_PF_TRACE_BUDGET
759            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
760                if v > 0 {
761                    Some(v - 1)
762                } else {
763                    None
764                }
765            })
766            .is_ok()
767    } else {
768        true
769    };
770    if do_pf_trace {
771        crate::trace_mem!(
772            crate::trace::category::MEM_PF,
773            crate::trace::TraceKind::MemPageFault,
774            error_code.bits() as u64,
775            trace_ctx,
776            rip,
777            fault_vaddr,
778            user_rsp,
779            0
780        );
781    }
782
783    // Try COW only for write-protection faults on already-present pages.
784    // For not-present faults, demand paging should run first.
785    if error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION)
786        && error_code.contains(PageFaultErrorCode::CAUSED_BY_WRITE)
787        && is_user
788    {
789        if let Some(task) = crate::process::current_task_clone() {
790            let address_space = task.process.address_space_arc();
791            if let Ok(vaddr) = fault_addr {
792                match crate::syscall::fork::handle_cow_fault(vaddr.as_u64(), &address_space) {
793                    Ok(()) => {
794                        crate::trace_mem!(
795                            crate::trace::category::MEM_COW,
796                            crate::trace::TraceKind::MemCow,
797                            1,
798                            trace_ctx,
799                            rip,
800                            vaddr.as_u64(),
801                            0,
802                            0
803                        );
804                        return;
805                    }
806                    Err(reason) => {
807                        crate::trace_mem!(
808                            crate::trace::category::MEM_COW,
809                            crate::trace::TraceKind::MemCow,
810                            0,
811                            trace_ctx,
812                            rip,
813                            vaddr.as_u64(),
814                            0,
815                            0
816                        );
817                        crate::serial_println!(
818                            "\x1b[31m[pagefault] COW resolve failed\x1b[0m: task={} \x1b[36mpid={}\x1b[0m tid={} \x1b[35maddr={:#x}\x1b[0m \x1b[35mrip={:#x}\x1b[0m err={}",
819                            task.id.as_u64(),
820                            task.pid,
821                            task.tid,
822                            vaddr.as_u64(),
823                            stack_frame.instruction_pointer.as_u64(),
824                            reason
825                        );
826                    }
827                }
828            }
829        }
830    }
831
832    if is_user {
833        if let Some(task) = crate::process::current_task_clone() {
834            let address_space = task.process.address_space_arc();
835            if let Ok(vaddr) = fault_addr {
836                if do_pf_trace {
837                    // FORCE OUTPUT for the first user faults only; lazy demand paging can
838                    // legitimately fault thousands of times during boot and flood serial.
839                    crate::serial_force_println!(
840                        "\x1b[33m[pagefault] USER fault\x1b[0m: tid={} rip={:#x} addr={:#x} err={:#x}",
841                        task.tid,
842                        rip,
843                        vaddr.as_u64(),
844                        error_code.bits()
845                    );
846                    // Mirror to e9 so the first handled faults stay visible in e9_debug.log.
847                    crate::e9_println!(
848                        "[PF] tid={} rip={:#x} addr={:#x} err={:#x}",
849                        task.tid,
850                        rip,
851                        vaddr.as_u64(),
852                        error_code.bits()
853                    );
854                }
855
856                match address_space.handle_fault(vaddr.as_u64()) {
857                    Ok(()) => {
858                        if do_pf_trace {
859                            crate::serial_force_println!(
860                                "\x1b[32m[pagefault] USER fault resolved\x1b[0m: tid={} addr={:#x}",
861                                task.tid,
862                                vaddr.as_u64()
863                            );
864                        }
865                        return;
866                    }
867                    Err(e) => {
868                        crate::serial_force_println!(
869                            "\x1b[31m[pagefault] USER fault resolution FAILED\x1b[0m: tid={} addr={:#x} err={:?}",
870                            task.tid,
871                            vaddr.as_u64(),
872                            e
873                        );
874                        crate::e9_println!(
875                            "[PF-FAIL] tid={} rip={:#x} addr={:#x}",
876                            task.tid,
877                            rip,
878                            vaddr.as_u64()
879                        );
880                        dump_user_pf_context(&address_space, rip, user_rsp);
881                    }
882                }
883            }
884        }
885    }
886
887    if is_user {
888        if let Some(tid) = crate::process::current_task_id() {
889            crate::silo::handle_user_fault(
890                tid,
891                crate::silo::SiloFaultReason::PageFault,
892                fault_addr.as_ref().map(|v| v.as_u64()).unwrap_or(0),
893                error_code.bits() as u64,
894                stack_frame.instruction_pointer.as_u64(),
895            );
896            return;
897        }
898    } else {
899        // FORCE OUTPUT for kernel fault
900        crate::serial_force_println!(
901            "\x1b[31;1m[pagefault] KERNEL fault\x1b[0m: rip={:#x} addr={:#x} err={:#x}",
902            rip,
903            fault_addr.as_ref().map(|v| v.as_u64()).unwrap_or(0),
904            error_code.bits()
905        );
906    }
907
908    // Capture current task (non-blocking, safe from IRQ context) for the diagnostic dump.
909    let task_snap = crate::process::scheduler::current_task_clone_try();
910    dump_page_fault_full(&stack_frame, error_code, fault_addr, &task_snap);
911}
912
913// =============================================================================
914// CRITICAL: Full page fault diagnostic dump
915//
916// Invoked for every non-recoverable page fault (kernel or unhandled user).
917// Designed to be deadlock-safe:
918//   - Uses serial_println! (direct UART) instead of the log framework, which
919//     may itself allocate or acquire locks.
920//   - All memory reads go through translate_via_raw_pt so no unmapped address
921//     is ever dereferenced.
922//   - The buddy allocator lock is acquired with try_lock (non-blocking) for
923//     memory statistics.
924//   - Uses current_task_clone_try (non-blocking) instead of current_task_clone.
925// =============================================================================
926
927/// Decodes `PageFaultErrorCode` bits into a human-readable string.
928fn decode_error_code(ec: PageFaultErrorCode) -> &'static str {
929    let p = ec.contains(PageFaultErrorCode::PROTECTION_VIOLATION);
930    let w = ec.contains(PageFaultErrorCode::CAUSED_BY_WRITE);
931    let u = ec.contains(PageFaultErrorCode::USER_MODE);
932    match (p, w, u) {
933        (false, false, false) => "kernel read of non-present page",
934        (false, true, false) => "kernel write to non-present page",
935        (false, false, true) => "user read of non-present page",
936        (false, true, true) => "user write to non-present page",
937        (true, false, false) => "kernel read protection violation",
938        (true, true, false) => "kernel write protection violation (COW / RO page)",
939        (true, false, true) => "user read protection violation (NX / supervisor-only)",
940        (true, true, true) => "user write protection violation (COW / RO page)",
941    }
942}
943
944/// Formats page table entry flags into a short human-readable byte string.
945fn format_pte_flags(entry: u64) -> [u8; 32] {
946    let mut buf = [b' '; 32];
947    let mut pos = 0usize;
948    let flags: &[(&str, u64)] = &[
949        ("P", 1 << 0),
950        ("RW", 1 << 1),
951        ("US", 1 << 2),
952        ("PWT", 1 << 3),
953        ("PCD", 1 << 4),
954        ("A", 1 << 5),
955        ("D", 1 << 6),
956        ("PS", 1 << 7),
957        ("G", 1 << 8),
958        ("NX", 1 << 63),
959    ];
960    for &(name, bit) in flags {
961        if entry & bit != 0 {
962            for &b in name.as_bytes() {
963                if pos < buf.len() {
964                    buf[pos] = b;
965                    pos += 1;
966                }
967            }
968            if pos < buf.len() {
969                buf[pos] = b'|';
970                pos += 1;
971            }
972        }
973    }
974    if pos > 0 && buf[pos - 1] == b'|' {
975        buf[pos - 1] = b' ';
976    }
977    buf
978}
979
980/// Translates a virtual address to a physical address via a manual 4-level
981/// page table walk.  Returns `Some(phys)` or `None` if any level is absent.
982///
983/// # SAFETY
984/// Read-only access to page tables through the HHDM mapping.
985/// All intermediate addresses are derived from table entries : no pointer
986/// originating from user-controlled data is ever dereferenced.
987fn translate_via_raw_pt(vaddr: u64, cr3_phys: u64, hhdm: u64) -> Option<u64> {
988    unsafe {
989        let l4_ptr = (cr3_phys + hhdm) as *const u64;
990        let l4e = *l4_ptr.add(((vaddr >> 39) & 0x1FF) as usize);
991        if l4e & 1 == 0 {
992            return None;
993        }
994
995        let l3_ptr = ((l4e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
996        let l3e = *l3_ptr.add(((vaddr >> 30) & 0x1FF) as usize);
997        if l3e & 1 == 0 {
998            return None;
999        }
1000        if l3e & 0x80 != 0 {
1001            return Some((l3e & 0x000F_FFFF_C000_0000) + (vaddr & 0x3FFF_FFFF));
1002        }
1003
1004        let l2_ptr = ((l3e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
1005        let l2e = *l2_ptr.add(((vaddr >> 21) & 0x1FF) as usize);
1006        if l2e & 1 == 0 {
1007            return None;
1008        }
1009        if l2e & 0x80 != 0 {
1010            return Some((l2e & 0x000F_FFFF_FFE0_0000) + (vaddr & 0x1F_FFFF));
1011        }
1012
1013        let l1_ptr = ((l2e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
1014        let l1e = *l1_ptr.add(((vaddr >> 12) & 0x1FF) as usize);
1015        if l1e & 1 == 0 {
1016            return None;
1017        }
1018        Some((l1e & 0x000F_FFFF_FFFF_F000) + (vaddr & 0xFFF))
1019    }
1020}
1021
1022/// Hex + ASCII dump of `count` bytes at virtual address `vaddr`.
1023/// Each page boundary is translated through the raw page tables.
1024fn dump_memory_bytes(vaddr: u64, cr3_phys: u64, count: usize, prefix: &str) {
1025    let hhdm = crate::memory::hhdm_offset();
1026    let mut offset = 0usize;
1027    while offset < count {
1028        let cur_va = vaddr.wrapping_add(offset as u64);
1029        let page_off = (cur_va & 0xFFF) as usize;
1030        let chunk = core::cmp::min(count - offset, 0x1000 - page_off);
1031        let Some(phys) = translate_via_raw_pt(cur_va, cr3_phys, hhdm) else {
1032            crate::serial_println!("{}(page {:#x} not mapped)", prefix, cur_va);
1033            offset += chunk;
1034            continue;
1035        };
1036        // SAFETY: read-only access to a valid physical page through the HHDM mapping.
1037        let src = (phys - (cur_va & 0xFFF) + hhdm) as *const u8;
1038        let mut line_off = 0usize;
1039        while line_off < chunk {
1040            let ll = core::cmp::min(16, chunk - line_off);
1041            let line_va = cur_va.wrapping_add(line_off as u64);
1042            let mut hex = [0u8; 48];
1043            let mut asc = [b'.'; 16];
1044            for i in 0..ll {
1045                let byte = unsafe { *src.add(page_off + line_off + i) };
1046                let hi = byte >> 4;
1047                let lo = byte & 0xF;
1048                hex[i * 3] = if hi < 10 { b'0' + hi } else { b'a' + hi - 10 };
1049                hex[i * 3 + 1] = if lo < 10 { b'0' + lo } else { b'a' + lo - 10 };
1050                hex[i * 3 + 2] = b' ';
1051                if byte >= 0x20 && byte < 0x7F {
1052                    asc[i] = byte;
1053                }
1054            }
1055            for i in ll..16 {
1056                hex[i * 3] = b' ';
1057                hex[i * 3 + 1] = b' ';
1058                hex[i * 3 + 2] = b' ';
1059            }
1060            crate::serial_println!(
1061                "{}{:#018x}: {} |{}|",
1062                prefix,
1063                line_va,
1064                core::str::from_utf8(&hex[..48]).unwrap_or("???"),
1065                core::str::from_utf8(&asc[..ll]).unwrap_or("???")
1066            );
1067            line_off += ll;
1068        }
1069        offset += chunk;
1070    }
1071}
1072
1073/// Detailed page table walk with flag decoding at every level.
1074fn dump_page_table_walk(vaddr: u64, cr3_phys: u64) {
1075    let hhdm = crate::memory::hhdm_offset();
1076    let l4_idx = ((vaddr >> 39) & 0x1FF) as usize;
1077    let l3_idx = ((vaddr >> 30) & 0x1FF) as usize;
1078    let l2_idx = ((vaddr >> 21) & 0x1FF) as usize;
1079    let l1_idx = ((vaddr >> 12) & 0x1FF) as usize;
1080
1081    // SAFETY: read-only access through the HHDM mapping for diagnostic purposes.
1082    unsafe {
1083        let l4_ptr = (cr3_phys + hhdm) as *const u64;
1084        let l4e = *l4_ptr.add(l4_idx);
1085        let f = format_pte_flags(l4e);
1086        crate::serial_println!(
1087            "  PML4[{:>3}] = {:#018x}  phys={:#014x}  [{}]",
1088            l4_idx,
1089            l4e,
1090            l4e & 0x000F_FFFF_FFFF_F000,
1091            core::str::from_utf8(&f).unwrap_or("?").trim()
1092        );
1093        if l4e & 1 == 0 {
1094            crate::serial_println!("  \x1b[1;31m  ==> STOP: PML4 not present !\x1b[0m");
1095            return;
1096        }
1097
1098        let l3_ptr = ((l4e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
1099        let l3e = *l3_ptr.add(l3_idx);
1100        let f = format_pte_flags(l3e);
1101        crate::serial_println!(
1102            "  PDPT[{:>3}] = {:#018x}  phys={:#014x}  [{}]",
1103            l3_idx,
1104            l3e,
1105            l3e & 0x000F_FFFF_FFFF_F000,
1106            core::str::from_utf8(&f).unwrap_or("?").trim()
1107        );
1108        if l3e & 1 == 0 {
1109            crate::serial_println!("  \x1b[1;31m ==> STOP: PDPT not present !\x1b[0m");
1110            return;
1111        }
1112        if l3e & 0x80 != 0 {
1113            crate::serial_println!(
1114                "  ==> 1 GiB huge page => phys {:#x}",
1115                l3e & 0x000F_FFFF_C000_0000
1116            );
1117            return;
1118        } // 1 GiB
1119
1120        let l2_ptr = ((l3e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
1121        let l2e = *l2_ptr.add(l2_idx);
1122        let f = format_pte_flags(l2e);
1123        crate::serial_println!(
1124            "  PD  [{:>3}] = {:#018x}  phys={:#014x}  [{}]",
1125            l2_idx,
1126            l2e,
1127            l2e & 0x000F_FFFF_FFFF_F000,
1128            core::str::from_utf8(&f).unwrap_or("?").trim()
1129        );
1130        if l2e & 1 == 0 {
1131            crate::serial_println!("  \x1b[1;31m ==> STOP: PD not present !\x1b[0m");
1132            return;
1133        }
1134        if l2e & 0x80 != 0 {
1135            crate::serial_println!(
1136                "  ==> 2 MiB huge page => phys {:#x}",
1137                l2e & 0x000F_FFFF_FFE0_0000
1138            );
1139            return;
1140        } // 2 MiB
1141
1142        let l1_ptr = ((l2e & 0x000F_FFFF_FFFF_F000) + hhdm) as *const u64;
1143        let l1e = *l1_ptr.add(l1_idx);
1144        let f = format_pte_flags(l1e);
1145        crate::serial_println!(
1146            "  PT  [{:>3}] = {:#018x}  phys={:#014x}  [{}]",
1147            l1_idx,
1148            l1e,
1149            l1e & 0x000F_FFFF_FFFF_F000,
1150            core::str::from_utf8(&f).unwrap_or("?").trim()
1151        );
1152        if l1e & 1 == 0 {
1153            crate::serial_println!("  \x1b[1;31m ==> STOP: PT not present !\x1b[0m");
1154        } else {
1155            crate::serial_println!(
1156                "  \x1b[1;32m ==> PAGE PRESENT\x1b[0m => phys {:#x} (check RW/US/NX flags)",
1157                l1e & 0x000F_FFFF_FFFF_F000
1158            );
1159        }
1160        // Neighbouring PT entries for context
1161        crate::serial_println!("  --- Neighbouring PT entries ---");
1162        let start = if l1_idx >= 2 { l1_idx - 2 } else { 0 };
1163        for i in start..core::cmp::min(l1_idx + 3, 512) {
1164            let e = *l1_ptr.add(i);
1165            if e != 0 {
1166                let f = format_pte_flags(e);
1167                crate::serial_println!(
1168                    "    PT[{:>3}] = {:#018x}  [{}]{}",
1169                    i,
1170                    e,
1171                    core::str::from_utf8(&f).unwrap_or("?").trim(),
1172                    if i == l1_idx { " <<<" } else { "" }
1173                );
1174            }
1175        }
1176    }
1177}
1178
1179/// Dumps VMA regions near the faulting address.
1180fn dump_nearby_vma_regions(as_ref: &crate::memory::AddressSpace, fault_vaddr: u64) {
1181    let page_start = fault_vaddr & !0xFFF;
1182    let probes = [
1183        page_start,
1184        fault_vaddr & !0x1F_FFFF,
1185        fault_vaddr & !0x3FFF_FFFF,
1186        0x0000_0001_0000_0000,
1187        0x0000_0000_0040_0000,
1188        0x0000_7FFF_F000_0000,
1189    ];
1190    let mut found_any = false;
1191    for &p in &probes {
1192        if let Some(vma) = as_ref.region_by_start(p) {
1193            let end = vma.start + (vma.page_count as u64) * vma.page_size.bytes();
1194            let hit = fault_vaddr >= vma.start && fault_vaddr < end;
1195            crate::serial_println!(
1196                "  VMA {:#014x}..{:#014x}  pages={:<5}  type={:?}  flags={:?}  pgsz={:?}{}",
1197                vma.start,
1198                end,
1199                vma.page_count,
1200                vma.vma_type,
1201                vma.flags,
1202                vma.page_size,
1203                if hit {
1204                    "  \x1b[1;32m<<< FAULT\x1b[0m"
1205                } else {
1206                    ""
1207                }
1208            );
1209            found_any = true;
1210        }
1211    }
1212    if as_ref.has_mapping_in_range(page_start, 0x1000) {
1213        crate::serial_println!(
1214            "  Note: fault page {:#x} IS within a tracked mapping range",
1215            page_start
1216        );
1217    } else {
1218        crate::serial_println!(
1219            "  Note: fault page {:#x} is NOT within any tracked mapping range",
1220            page_start
1221        );
1222    }
1223    if !found_any {
1224        crate::serial_println!("  (no VMA regions found at probed addresses)");
1225    }
1226}
1227
1228/// Full diagnostic dump for a non-recoverable page fault.
1229///
1230/// Uses `serial_println!` directly (lock-free UART) to avoid any deadlock
1231/// with the log framework or the heap allocator.
1232fn dump_page_fault_full(
1233    stack_frame: &InterruptStackFrame,
1234    error_code: PageFaultErrorCode,
1235    fault_addr: Result<x86_64::VirtAddr, x86_64::addr::VirtAddrNotValid>,
1236    task: &Option<alloc::sync::Arc<crate::process::task::Task>>,
1237) -> ! {
1238    use x86_64::registers::control::{Cr0, Cr3, Cr4};
1239
1240    let rip = stack_frame.instruction_pointer.as_u64();
1241    let rsp = stack_frame.stack_pointer.as_u64();
1242    let cs = stack_frame.code_segment.0;
1243    let ss = stack_frame.stack_segment.0;
1244    let rflags = stack_frame.cpu_flags.bits();
1245    let fault_vaddr = fault_addr.as_ref().map(|v| v.as_u64()).unwrap_or(0);
1246    let is_user = (cs & 3) == 3;
1247
1248    crate::serial_println!("\x1b[1;31m");
1249    crate::serial_println!(
1250        "****************************************************************************"
1251    );
1252    crate::serial_println!("*                  KERNEL PAGE FAULT EXCEPTiON                     *");
1253    crate::serial_println!(
1254        "********************************************************************\x1b[0m"
1255    );
1256
1257    // --- Error code ---
1258    crate::serial_println!("\x1b[1;33m--- Error code ---\x1b[0m");
1259    crate::serial_println!("  Raw         : {:#06x}", error_code.bits());
1260    crate::serial_println!(
1261        "  Diagnostic  : \x1b[1;31m{}\x1b[0m",
1262        decode_error_code(error_code)
1263    );
1264    crate::serial_println!(
1265        "  PRESENT     : {} | WRITE : {} | USER : {} | RSVD : {} | FETCH : {}",
1266        error_code.contains(PageFaultErrorCode::PROTECTION_VIOLATION) as u8,
1267        error_code.contains(PageFaultErrorCode::CAUSED_BY_WRITE) as u8,
1268        error_code.contains(PageFaultErrorCode::USER_MODE) as u8,
1269        (error_code.bits() >> 3) & 1,
1270        (error_code.bits() >> 4) & 1
1271    );
1272
1273    // --- Faulting context ---
1274    crate::serial_println!("\x1b[1;33m--- Faulting context ---\x1b[0m");
1275    crate::serial_println!("  CR2 (addr)  : \x1b[1;35m{:#018x}\x1b[0m", fault_vaddr);
1276    crate::serial_println!("  RIP         : \x1b[1;36m{:#018x}\x1b[0m", rip);
1277    crate::serial_println!("  RSP         : {:#018x}", rsp);
1278    crate::serial_println!(
1279        "  CS          : {:#06x}  (ring={}{}) | SS : {:#06x}",
1280        cs,
1281        cs & 3,
1282        if is_user { " USER" } else { " KERNEL" },
1283        ss
1284    );
1285
1286    // RFLAGS décodé
1287    let mut rf_str = [0u8; 64];
1288    let mut rfp = 0usize;
1289    for &(name, bit) in &[
1290        ("CF", 1u64),
1291        ("PF", 4),
1292        ("AF", 16),
1293        ("ZF", 64),
1294        ("SF", 128),
1295        ("TF", 256),
1296        ("IF", 512),
1297        ("DF", 1024),
1298        ("OF", 2048),
1299    ] {
1300        if rflags & bit != 0 {
1301            for &b in name.as_bytes() {
1302                if rfp < rf_str.len() {
1303                    rf_str[rfp] = b;
1304                    rfp += 1;
1305                }
1306            }
1307            if rfp < rf_str.len() {
1308                rf_str[rfp] = b' ';
1309                rfp += 1;
1310            }
1311        }
1312    }
1313    crate::serial_println!(
1314        "  RFLAGS      : {:#018x}  [{}]",
1315        rflags,
1316        core::str::from_utf8(&rf_str[..rfp]).unwrap_or("?")
1317    );
1318
1319    // --- Control registers ---
1320    crate::serial_println!("\x1b[1;33m--- Control registers ---\x1b[0m");
1321    let cr0 = Cr0::read_raw();
1322    let (cr3_frame, cr3_flags) = Cr3::read();
1323    let cr3_phys = cr3_frame.start_address().as_u64();
1324    let cr4 = Cr4::read_raw();
1325    let efer: u64 = x86_64::registers::model_specific::Efer::read_raw();
1326    crate::serial_println!("  CR0         : {:#018x}", cr0);
1327    crate::serial_println!(
1328        "  CR3         : {:#018x}  (flags={:#x})",
1329        cr3_phys,
1330        cr3_flags.bits()
1331    );
1332    crate::serial_println!("  CR4         : {:#018x}", cr4);
1333    crate::serial_println!(
1334        "  EFER        : {:#018x}  [{}{}{}]",
1335        efer,
1336        if efer & 1 != 0 { "SCE " } else { "" },
1337        if efer & (1 << 8) != 0 { "LME " } else { "" },
1338        if efer & (1 << 11) != 0 { "NXE" } else { "" }
1339    );
1340
1341    // --- CPU context ---
1342    crate::serial_println!("\x1b[1;33m--- CPU context ---\x1b[0m");
1343    crate::serial_println!("  LAPIC ID    : {}", super::apic::lapic_id());
1344    crate::serial_println!("  Ticks sched : {}", crate::process::scheduler::ticks());
1345    crate::serial_println!("  HHDM offset : {:#x}", crate::memory::hhdm_offset());
1346
1347    // --- Task context ---
1348    crate::serial_println!("\x1b[1;33m--- Task context ---\x1b[0m");
1349    if let Some(ref t) = *task {
1350        crate::serial_println!(
1351            "  ID={} PID={} TID={} TGID={} name=\"{}\" prio={:?} ticks={}",
1352            t.id.as_u64(),
1353            t.pid,
1354            t.tid,
1355            t.tgid,
1356            t.name,
1357            t.priority,
1358            t.ticks.load(core::sync::atomic::Ordering::Relaxed)
1359        );
1360        // SAFETY: Read task CR3 safely using the hardware page-table walker
1361        // (translate_via_raw_pt) to prevent recursive page faults if the
1362        // process's Arc<AddressSpace> is partially initialized or corrupted.
1363        //
1364        // Chain: &t.process → Arc<Process> data ptr (Arc::as_ptr)
1365        //      → (*process).address_space.get() → *mut Arc<AddressSpace>
1366        //      → Arc::as_ptr(arc_as) → *const AddressSpace
1367        //      → (*addr_space).cr3_phys
1368        //
1369        // Each step uses translate_via_raw_pt to verify the pointer is mapped
1370        // before dereferencing, using the hardware CR3 (cr3_phys) which always
1371        // maps the kernel's HHDM region.
1372        let task_cr3: u64 = {
1373            let hhdm = crate::memory::hhdm_offset();
1374            // Step 1: Arc<Process> data (Arc::as_ptr is always valid for a live Arc)
1375            let _proc_ptr: u64 = alloc::sync::Arc::as_ptr(&t.process) as u64;
1376            // Step 2: address_space field in Process = SyncUnsafeCell whose .get()
1377            // returns a raw ptr into the Process data : always valid for a live Process.
1378            // However, reading the Arc<AddressSpace> *value* from that pointer may
1379            // fault if the memory is unmapped, so we use translate_via_raw_pt.
1380            let as_cell_addr: u64 =
1381                unsafe { (*alloc::sync::Arc::as_ptr(&t.process)).address_space.get() as u64 };
1382            // Step 3: read the 8-byte Arc<AddressSpace> inner pointer from as_cell_addr
1383            // via raw page table walk with current hardware CR3.
1384            let as_inner_u64: u64 = match translate_via_raw_pt(as_cell_addr, cr3_phys, hhdm) {
1385                Some(phys) => unsafe { *((phys + hhdm) as *const u64) },
1386                None => 0,
1387            };
1388            if as_inner_u64 == 0 {
1389                0u64
1390            } else {
1391                // as_inner_u64 is the NonNull ptr inside Arc<AddressSpace>
1392                // = pointer to ArcInner<AddressSpace>.
1393                // ArcInner = strong(8) + weak(8) + data(AddressSpace).
1394                // So AddressSpace data is at as_inner_u64 + 16.
1395                let as_data_ptr: u64 = as_inner_u64 + 2 * core::mem::size_of::<usize>() as u64;
1396                // cr3_phys is the first field of AddressSpace (PhysAddr = u64, 8 bytes).
1397                match translate_via_raw_pt(as_data_ptr, cr3_phys, hhdm) {
1398                    Some(phys) => unsafe { *((phys + hhdm) as *const u64) },
1399                    None => 0,
1400                }
1401            }
1402        };
1403        if task_cr3 == 0 {
1404            crate::serial_println!(
1405                "  Task CR3    : <unreadable : null/unmapped Arc<AddressSpace>>"
1406            );
1407        } else {
1408            crate::serial_println!(
1409                "  Task CR3    : {:#018x}{}",
1410                task_cr3,
1411                if task_cr3 != cr3_phys {
1412                    " *** DIFFERS from hardware CR3! ***"
1413                } else {
1414                    " (matches hardware CR3)"
1415                }
1416            );
1417        }
1418    } else {
1419        crate::serial_println!("  (no current task : scheduler idle or unavailable)");
1420    }
1421
1422    // --- Memory statistics ---
1423    crate::serial_println!("\x1b[1;33m--- Memory stats ---\x1b[0m");
1424    if let Some(guard) = crate::memory::get_allocator().try_lock() {
1425        if let Some(ref alloc) = *guard {
1426            let (total, allocated) = alloc.page_totals();
1427            let free = total.saturating_sub(allocated);
1428            crate::serial_println!(
1429                "  Total={} pages ({} MiB)  Alloc={} ({} MiB)  Free={} ({} MiB)",
1430                total,
1431                total * 4 / 1024,
1432                allocated,
1433                allocated * 4 / 1024,
1434                free,
1435                free * 4 / 1024
1436            );
1437            let mut zones =
1438                [crate::memory::buddy::ZoneStats::empty(); crate::memory::zone::ZoneType::COUNT];
1439            let n = alloc.zone_snapshot(&mut zones);
1440            for i in 0..n {
1441                let zone = zones[i];
1442                let zone_ref = alloc.get_zone(i);
1443                crate::serial_println!(
1444                    "    Zone {} ({}): base={:#x} managed={} present={} spanned={} reserved={} alloc={} free={} state={:?} seg={}/{} largest={:?}",
1445                    i,
1446                    match zone.zone_type {
1447                        crate::memory::zone::ZoneType::DMA => "DMA",
1448                        crate::memory::zone::ZoneType::Normal => "Normal",
1449                        crate::memory::zone::ZoneType::HighMem => "High",
1450                    },
1451                    zone.base,
1452                    zone.managed_pages,
1453                    zone.present_pages,
1454                    zone.spanned_pages,
1455                    zone.reserved_pages,
1456                    zone.allocated_pages,
1457                    zone.free_pages,
1458                    zone.pressure(),
1459                    zone.segment_count,
1460                    zone.segment_capacity,
1461                    zone.largest_free_order
1462                );
1463                crate::serial_println!(
1464                    "      reserve={} avail={} holes={} cached[u/m]={}/{} free[u/m]={}/{} pageblocks[u/m]={}/{} total={} order={}",
1465                    zone.reserve_floor_pages(),
1466                    zone.available_after_reserve_pages(),
1467                    zone.hole_pages(),
1468                    zone.cached_unmovable_pages,
1469                    zone.cached_movable_pages,
1470                    zone.unmovable_free_pages,
1471                    zone.movable_free_pages,
1472                    zone.unmovable_pageblocks,
1473                    zone.movable_pageblocks,
1474                    zone.pageblock_count,
1475                    crate::memory::zone::PAGEBLOCK_ORDER
1476                );
1477                crate::serial_println!(
1478                    "      frag/order: o1={}%% o4={}%% o{}={}%%",
1479                    zone_ref.fragmentation_score(1, zone.cached_pages),
1480                    zone_ref.fragmentation_score(4, zone.cached_pages),
1481                    crate::memory::zone::PAGEBLOCK_ORDER,
1482                    zone_ref.fragmentation_score(
1483                        crate::memory::zone::PAGEBLOCK_ORDER as u8,
1484                        zone.cached_pages,
1485                    )
1486                );
1487            }
1488        } else {
1489            crate::serial_println!("  (allocator not initialized)");
1490        }
1491    } else {
1492        crate::serial_println!("  (allocator lock contended : skipping)");
1493    }
1494
1495    let quarantine = crate::memory::buddy::poison_quarantine_pages_snapshot();
1496    let fail_counts = crate::memory::buddy::buddy_alloc_fail_counts_snapshot();
1497    let compaction = crate::memory::buddy::compaction_stats_snapshot();
1498    crate::serial_println!("  Poison quarantine : {} pages", quarantine);
1499
1500    let mut printed_fail = false;
1501    for (order, count) in fail_counts.iter().enumerate() {
1502        if *count == 0 {
1503            continue;
1504        }
1505        printed_fail = true;
1506        crate::serial_println!("  Buddy alloc fail  : order={} count={}", order, count);
1507    }
1508    if !printed_fail {
1509        crate::serial_println!("  Buddy alloc fail  : none");
1510    }
1511
1512    if compaction.attempts == 0 {
1513        crate::serial_println!("  Compaction assist : none");
1514    } else {
1515        crate::serial_println!(
1516            "  Compaction assist : attempts={} success={} last_order={:?} migratetype={:?} zone={:?} pressure={:?}",
1517            compaction.attempts,
1518            compaction.successes,
1519            compaction.last_order,
1520            compaction.last_migratetype,
1521            compaction.last_zone,
1522            compaction.last_pressure
1523        );
1524        crate::serial_println!(
1525            "                      frag={}%% req={} avail={} usable={} cached={} drained={} pageblocks={}/{}",
1526            compaction.last_fragmentation_score,
1527            compaction.last_requested_pages,
1528            compaction.last_available_pages,
1529            compaction.last_usable_pages,
1530            compaction.last_cached_pages,
1531            compaction.last_drained_pages,
1532            compaction.last_matching_pageblocks,
1533            compaction.last_pageblock_count
1534        );
1535    }
1536
1537    // --- Code bytes at RIP ---
1538    crate::serial_println!("\x1b[1;33m--- Code at RIP ({:#x}) ---\x1b[0m", rip);
1539    dump_memory_bytes(rip, cr3_phys, 32, "  ");
1540
1541    // --- Stack dump ---
1542    crate::serial_println!("\x1b[1;33m--- Stack dump (RSP={:#x}) ---\x1b[0m", rsp);
1543    dump_memory_bytes(rsp, cr3_phys, 128, "  ");
1544
1545    // --- Page table walk ---
1546    crate::serial_println!(
1547        "\x1b[1;33m--- Page table walk (CR2={:#x}, CR3={:#x}) ---\x1b[0m",
1548        fault_vaddr,
1549        cr3_phys
1550    );
1551    if fault_addr.is_ok() {
1552        dump_page_table_walk(fault_vaddr, cr3_phys);
1553    } else {
1554        crate::serial_println!("  (CR2 is a non-canonical address: {:#x})", fault_vaddr);
1555    }
1556
1557    // --- VMA regions near fault ---
1558    if let Some(ref t) = *task {
1559        crate::serial_println!("\x1b[1;33m--- VMA regions near fault ---\x1b[0m");
1560        // SAFETY: Use the same safe ptr-chain read strategy as the Task CR3 section above:
1561        // Arc::as_ptr gives a valid *const AddressSpace if the Arc is alive, but the
1562        // Arc<AddressSpace> stored inside the SyncUnsafeCell might be corrupted.
1563        // We validate via translate_via_raw_pt before reading the inner ptr.
1564        let hhdm_vma = crate::memory::hhdm_offset();
1565        let safe_as: Option<*const crate::memory::AddressSpace> = unsafe {
1566            let as_cell_addr: u64 =
1567                (*alloc::sync::Arc::as_ptr(&t.process)).address_space.get() as u64;
1568            match translate_via_raw_pt(as_cell_addr, cr3_phys, hhdm_vma) {
1569                Some(phys) => {
1570                    // Read the Arc<AddressSpace> inner pointer (a NonNull ptr stored at this phys)
1571                    let as_inner_u64 = *((phys + hhdm_vma) as *const u64);
1572                    if as_inner_u64 == 0 {
1573                        None
1574                    } else {
1575                        // ArcInner<AddressSpace>.data at +16
1576                        let as_data_ptr = (as_inner_u64 + 2 * core::mem::size_of::<usize>() as u64)
1577                            as *const crate::memory::AddressSpace;
1578                        // Validate the AddressSpace pointer is mapped before returning it
1579                        if translate_via_raw_pt(as_data_ptr as u64, cr3_phys, hhdm_vma).is_some() {
1580                            Some(as_data_ptr)
1581                        } else {
1582                            None
1583                        }
1584                    }
1585                }
1586                None => None,
1587            }
1588        };
1589        if let Some(as_ptr) = safe_as {
1590            // SAFETY: We verified above that as_ptr is mapped and readable.
1591            let as_ref = unsafe { &*as_ptr };
1592            dump_nearby_vma_regions(as_ref, fault_vaddr);
1593        } else {
1594            crate::serial_println!("  (AddressSpace unreadable : skipping VMA dump)");
1595        }
1596    }
1597
1598    crate::serial_println!("\x1b[1;31m***********************************************************");
1599    crate::serial_println!("*                     END OF PAGE FAULT DuMP                      *");
1600    crate::serial_println!(
1601        "*******************************************************************\x1b[0m"
1602    );
1603
1604    panic!(
1605        "PAGE FAULT: {} at {:#x}, RIP={:#x}, CR3={:#x}, err={:#x}",
1606        decode_error_code(error_code),
1607        fault_vaddr,
1608        rip,
1609        cr3_phys,
1610        error_code.bits()
1611    );
1612}
1613
1614/// Performs the dump user pf context operation.
1615fn dump_user_pf_context(as_ref: &crate::memory::AddressSpace, rip: u64, rsp: u64) {
1616    use x86_64::VirtAddr;
1617
1618    let hhdm = crate::memory::hhdm_offset();
1619
1620    if let Some(phys) = as_ref.translate(VirtAddr::new(rip)) {
1621        let off = (rip & 0xfff) as usize;
1622        let mut bytes = [0u8; 8];
1623        // SAFETY: We read at most 8 bytes from a mapped user instruction page via HHDM.
1624        unsafe {
1625            let src = (phys.as_u64() - (rip & 0xfff) + hhdm + off as u64) as *const u8;
1626            core::ptr::copy_nonoverlapping(src, bytes.as_mut_ptr(), bytes.len());
1627        }
1628        crate::serial_println!(
1629            "[pagefault] ctx: rsp={:#x} rip-bytes={:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
1630            rsp,
1631            bytes[0],
1632            bytes[1],
1633            bytes[2],
1634            bytes[3],
1635            bytes[4],
1636            bytes[5],
1637            bytes[6],
1638            bytes[7],
1639        );
1640    } else {
1641        crate::serial_println!("[pagefault] ctx: rsp={:#x} rip page unmapped", rsp);
1642    }
1643
1644    if let Some(phys) = as_ref.translate(VirtAddr::new(rsp)) {
1645        crate::serial_println!(
1646            "[pagefault] stack-top: rsp mapped (phys={:#x})",
1647            phys.as_u64()
1648        );
1649    } else {
1650        crate::serial_println!("[pagefault] stack-top: rsp unmapped");
1651    }
1652}
1653
1654/// Performs the general protection fault handler operation.
1655extern "x86-interrupt" fn general_protection_fault_handler(
1656    stack_frame: InterruptStackFrame,
1657    error_code: u64,
1658) {
1659    let cs = stack_frame.code_segment.0;
1660    let is_user = (cs & 3) == 3;
1661    // Use rdmsr-based check: catches the swapgs→iretq window where
1662    // CS=Ring0 but GS=user (0).
1663    // Without this, #GP from a bad iretq would escalate to double fault => triple fault.
1664    let swapgs_needed = needs_swapgs(cs);
1665    let _gs = SwapGsGuard::new(swapgs_needed);
1666    // Detect the swapgs→iretq window case: CS says Ring 0 but GS was user.
1667    if swapgs_needed && !is_user {
1668        crate::serial_force_println!(
1669            "\x1b[31;1m[GPF]\x1b[0m SWAPGS-WINDOW: CS={:#x} (Ring0) but GS was user! rip={:#x} err={:#x} rsp={:#x}",
1670            cs,
1671            stack_frame.instruction_pointer.as_u64(),
1672            error_code,
1673            stack_frame.stack_pointer.as_u64()
1674        );
1675        // Keep SwapGsGuard alive through kill path.
1676        if let Some(task) = crate::process::current_task_clone() {
1677            crate::process::kill_task(task.id);
1678        }
1679        drop(_gs);
1680        crate::process::scheduler::exit_current_task(-11); // SIGSEGV
1681    }
1682    if is_user {
1683        if let Some(tid) = crate::process::current_task_id() {
1684            crate::serial_force_println!(
1685                "\x1b[31;1m[GPF]\x1b[0m USER tid={} rip={:#x} err={:#x}",
1686                tid,
1687                stack_frame.instruction_pointer.as_u64(),
1688                error_code
1689            );
1690            crate::silo::handle_user_fault(
1691                tid,
1692                crate::silo::SiloFaultReason::GeneralProtection,
1693                stack_frame.instruction_pointer.as_u64(),
1694                error_code,
1695                stack_frame.instruction_pointer.as_u64(),
1696            );
1697            return;
1698        }
1699    }
1700    crate::serial_force_println!(
1701        "\x1b[31;1m[GPF]\x1b[0m KERNEL rip={:#x} err={:#x} cs={:#x} rsp={:#x}",
1702        stack_frame.instruction_pointer.as_u64(),
1703        error_code,
1704        stack_frame.code_segment.0,
1705        stack_frame.stack_pointer.as_u64()
1706    );
1707    panic!("General protection fault");
1708}
1709
1710/// Performs the stack segment fault handler operation.
1711extern "x86-interrupt" fn stack_segment_fault_handler(
1712    stack_frame: InterruptStackFrame,
1713    error_code: u64,
1714) {
1715    // Use rdmsr-based check: iretq can trigger #SS if the user SS is bad,
1716    // and at that point GS is already swapped to user.
1717    let _gs = SwapGsGuard::new(needs_swapgs(stack_frame.code_segment.0));
1718    crate::serial_force_println!(
1719        "\x1b[31;1m[STACK_FAULT]\x1b[0m rip={:#x} err={:#x} cs={:#x} rsp={:#x}",
1720        stack_frame.instruction_pointer.as_u64(),
1721        error_code,
1722        stack_frame.code_segment.0,
1723        stack_frame.stack_pointer.as_u64()
1724    );
1725    panic!("Stack segment fault");
1726}
1727
1728/// Performs the double fault handler operation.
1729///
1730/// Uses IST stack so the handler always runs on a known-good stack, even
1731/// when RSP0 is corrupt.  We must still do `swapgs` if the fault originated
1732/// from Ring 3 (or from Ring 0 code that already did `swapgs`, e.g. the
1733/// `iretq` path in `elf_ring3_trampoline`).
1734///
1735/// # Note on divergent handler
1736/// This handler is `-> !`, so `SwapGsGuard::drop` will never run.  That is
1737/// fine because we never return to the interrupted context.
1738extern "x86-interrupt" fn double_fault_handler(
1739    stack_frame: InterruptStackFrame,
1740    error_code: u64,
1741) -> ! {
1742    // Best-effort swapgs: if GS currently points at user space (address 0)
1743    // we need to swap to kernel GS so that any code below that touches
1744    // `gs:[0]` (e.g. via `current_cpu_index`) does not page-fault again.
1745    // We use a raw read of IA32_GS_BASE via rdmsr to decide.
1746    //
1747    // During `elf_ring3_trampoline`, `swapgs` is executed *before* `iretq`.
1748    // If `iretq` itself faults, `code_segment` is still Ring 0 (0x08) but
1749    // GS_BASE is already the user value (0).  The normal `cs & 3 == 3` test
1750    // would miss this case.  Reading the MSR catches it.
1751    unsafe {
1752        let lo: u32;
1753        let hi: u32;
1754        core::arch::asm!(
1755            "rdmsr",
1756            in("ecx") 0xC000_0101u32,  // IA32_GS_BASE
1757            out("eax") lo,
1758            out("edx") hi,
1759            options(nostack, preserves_flags),
1760        );
1761        let gs_base = (lo as u64) | ((hi as u64) << 32);
1762        // If GS_BASE is in the low half (user space) or zero, swap to kernel.
1763        if gs_base < 0xFFFF_8000_0000_0000 {
1764            core::arch::asm!("swapgs", options(nostack, preserves_flags));
1765        }
1766    }
1767    crate::serial_force_println!(
1768        "\x1b[31;1m[DOUBLE_FAULT]\x1b[0m rip={:#x} err={:#x} cs={:#x} rsp={:#x}",
1769        stack_frame.instruction_pointer.as_u64(),
1770        error_code,
1771        stack_frame.code_segment.0,
1772        stack_frame.stack_pointer.as_u64()
1773    );
1774    panic!(
1775        "EXCEPTION: DOUBLE FAULT (error code: {:#x})\n{:#?}",
1776        error_code, stack_frame
1777    );
1778}
1779
1780// =============================================
1781// Hardware IRQ handlers
1782// =============================================
1783
1784/// Legacy external timer IRQ handler (PIC/IOAPIC IRQ0 path, vector 0x20).
1785///
1786/// When the LAPIC timer is active, we ignore this source to avoid double-ticking.
1787extern "x86-interrupt" fn legacy_timer_handler(stack_frame: InterruptStackFrame) {
1788    // Restore kernel GS if the timer fired while Ring 3 was running.
1789    let _gs = SwapGsGuard::new((stack_frame.code_segment.0 & 3) == 3);
1790    if crate::arch::x86_64::timer::is_apic_timer_active() {
1791        // Ignore legacy timer source once LAPIC timer is running.
1792        if super::apic::is_initialized() {
1793            super::apic::eoi();
1794        } else {
1795            pic::end_of_interrupt(0);
1796        }
1797        return;
1798    }
1799
1800    // FORCE OUTPUT for heartbeat (every 100 ticks to avoid flooding,
1801    // plus first 10 ticks to confirm timer fires after Ring-3 entry)
1802    let ticks = crate::process::scheduler::ticks();
1803    if ticks < 10 || ticks % 100 == 0 {
1804        crate::serial_force_println!("[heartbeat] PIC timer tick={}", ticks);
1805    }
1806
1807    // Increment tick counter
1808    crate::process::scheduler::timer_tick();
1809    // NOTE: avoid complex rendering/allocation work in IRQ context.
1810    // Status bar refresh is currently done from non-IRQ paths.
1811
1812    // Send EOI first so the timer can fire again on the new task
1813    if super::apic::is_initialized() {
1814        super::apic::eoi();
1815    } else {
1816        pic::end_of_interrupt(0);
1817    }
1818
1819    // Mirror the LAPIC timer policy: do not run maybe_preempt() directly
1820    // from a Ring-3-origin timer IRQ. The extern "x86-interrupt" frame
1821    // must unwind via iretq; switching away from it can corrupt the
1822    // interrupt return state. Post a resched hint instead.
1823    let cpl = stack_frame.code_segment.0 & 3;
1824    if cpl == 3 {
1825        let cpu = crate::arch::x86_64::percpu::current_cpu_index();
1826        crate::process::scheduler::request_force_resched_hint(cpu);
1827    } else {
1828        crate::process::scheduler::maybe_preempt();
1829    }
1830    if ticks < 10 {
1831        crate::serial_force_println!("[heartbeat] PIC timer tick={} preempt_done", ticks);
1832    }
1833}
1834
1835/// Local APIC timer handler (dedicated vector, e.g. 0xD2).
1836extern "x86-interrupt" fn lapic_timer_handler(stack_frame: InterruptStackFrame) {
1837    // Restore kernel GS if the timer fired while Ring 3 was running.
1838    let cs = stack_frame.code_segment.0;
1839    let _gs = SwapGsGuard::new((cs & 3) == 3);
1840    let cpu = crate::arch::x86_64::percpu::current_cpu_index();
1841    let ticks = crate::process::scheduler::ticks();
1842    // Trace first 10 ticks per CPU unconditionally to confirm timer fires
1843    // after Ring-3 entry, then one-per-100 heartbeat to avoid flooding.
1844    if ticks < 10 || ticks % 100 == 0 {
1845        crate::serial_force_println!(
1846            "[heartbeat] APIC timer tick={} cpu={} cs={:#x} rip={:#x}",
1847            ticks,
1848            cpu,
1849            cs,
1850            stack_frame.instruction_pointer.as_u64()
1851        );
1852    }
1853
1854    // serial_force_println holds FORCE_LOCK (IRQ-disabled spinlock) while writing
1855    // to the UART. At 115200 baud each byte takes ~87 µs; a 60-char message is
1856    // ~5 ms of IRQs-off time : long enough to miss ticks and corrupt scheduling.
1857    // Keep serial output out of the hot IRQ path; use e9 port (µs-range) instead.
1858    unsafe { core::arch::asm!("mov al, '0'; out 0xe9, al", out("al") _) };
1859    crate::process::scheduler::timer_tick();
1860    unsafe { core::arch::asm!("mov al, '1'; out 0xe9, al", out("al") _) };
1861    super::apic::eoi();
1862    // IMPORTANT:
1863    // Do not run `maybe_preempt()` directly from a Ring-3-origin timer IRQ.
1864    //
1865    // Current scheduler switch path (`do_switch_context` + `ret`) is built for
1866    // task context frames, while this function is an `extern "x86-interrupt"`
1867    // frame that the compiler expects to unwind with iretq.
1868    //
1869    // On first user-mode preemption (CPU1), switching away from this frame can
1870    // corrupt the interrupt return state and trigger #DF/#TF. Instead, mark a
1871    // lock-free resched hint and return through the normal interrupt epilogue.
1872    // The scheduler will consume the hint on a safe path.
1873    if (cs & 3) == 3 {
1874        crate::process::scheduler::request_force_resched_hint(cpu);
1875        unsafe { core::arch::asm!("mov al, 'P'; out 0xe9, al", out("al") _) };
1876    } else {
1877        crate::process::scheduler::maybe_preempt();
1878    }
1879}
1880
1881/// PS/2 Mouse IRQ12 handler.
1882extern "x86-interrupt" fn mouse_handler(_stack_frame: InterruptStackFrame) {
1883    crate::arch::x86_64::mouse::handle_irq();
1884    // PS/2 mouse IRQ12 is intentionally kept on the remapped legacy PIC path.
1885    // Even when LAPIC/IOAPIC are active for timer/IPI traffic, this source must
1886    // still be acknowledged via the 8259 PIC.
1887    pic::end_of_interrupt(12);
1888}
1889
1890/// Performs the keyboard handler operation.
1891extern "x86-interrupt" fn keyboard_handler(_stack_frame: InterruptStackFrame) {
1892    let raw = unsafe { super::io::inb(0x60) };
1893
1894    // Feed scancode + TSC low bits into the entropy pool.
1895    crate::entropy::add_entropy(1, (raw as u64) ^ super::rdtsc());
1896
1897    if let Some(ch) = super::keyboard_layout::handle_scancode_raw(raw) {
1898        crate::arch::x86_64::keyboard::add_to_buffer(ch);
1899    }
1900
1901    // PS/2 keyboard IRQ1 is intentionally kept on the remapped legacy PIC path.
1902    // A LAPIC EOI here leaves the PIC request in service and stalls keyboard
1903    // delivery after the first edge.
1904    pic::end_of_interrupt(1);
1905}
1906
1907/// Spurious interrupt handler (APIC vector 0xFF).
1908/// Per Intel SDM: do NOT send EOI for spurious interrupts.
1909extern "x86-interrupt" fn spurious_handler(_stack_frame: InterruptStackFrame) {
1910    // Intentionally empty : no EOI per Intel SDM
1911}
1912
1913/// AHCI storage controller IRQ handler.
1914///
1915/// Reads `HBA_IS`, processes per-port completions, wakes waiting tasks, then
1916/// sends EOI.  Must not call any function that may block or allocate.
1917extern "x86-interrupt" fn ahci_handler(_stack_frame: InterruptStackFrame) {
1918    // Feed IRQ timing into entropy pool.
1919    crate::entropy::add_entropy(3, super::rdtsc());
1920
1921    crate::hardware::storage::ahci::handle_interrupt();
1922
1923    if super::apic::is_initialized() {
1924        super::apic::eoi();
1925    } else {
1926        let irq = crate::hardware::storage::ahci::AHCI_IRQ_LINE
1927            .load(core::sync::atomic::Ordering::Relaxed);
1928        pic::end_of_interrupt(irq);
1929    }
1930}
1931
1932/// NVMe storage controller IRQ handler.
1933///
1934/// Processes I/O completion queue entries and wakes waiting tasks.
1935extern "x86-interrupt" fn nvme_handler(_stack_frame: InterruptStackFrame) {
1936    crate::entropy::add_entropy(3, super::rdtsc());
1937
1938    crate::hardware::storage::nvme::handle_interrupt();
1939
1940    if super::apic::is_initialized() {
1941        super::apic::eoi();
1942    } else {
1943        let irq = crate::hardware::storage::nvme::NVME_IRQ_LINE
1944            .load(core::sync::atomic::Ordering::Relaxed);
1945        pic::end_of_interrupt(irq);
1946    }
1947}
1948
1949/// VirtIO Block device IRQ handler
1950///
1951/// Handles interrupts from the VirtIO block device.
1952/// The IRQ line is determined at runtime from PCI config.
1953extern "x86-interrupt" fn virtio_block_handler(_stack_frame: InterruptStackFrame) {
1954    // Handle the VirtIO block interrupt
1955    crate::hardware::storage::virtio_block::handle_interrupt();
1956
1957    // Send EOI
1958    if super::apic::is_initialized() {
1959        super::apic::eoi();
1960    } else {
1961        // Get the IRQ number from the device
1962        let irq = crate::hardware::storage::virtio_block::get_irq();
1963        pic::end_of_interrupt(irq);
1964    }
1965}
1966
1967/// xHCI USB controller IRQ handler
1968///
1969/// Handles interrupts from the xHCI host controller.
1970/// Processes event ring completions for control transfers and HID reports.
1971extern "x86-interrupt" fn xhci_handler(_stack_frame: InterruptStackFrame) {
1972    crate::hardware::usb::xhci::handle_interrupt();
1973
1974    if super::apic::is_initialized() {
1975        super::apic::eoi();
1976    } else {
1977        let irq =
1978            crate::hardware::usb::xhci::XHCI_IRQ_LINE.load(core::sync::atomic::Ordering::Relaxed);
1979        pic::end_of_interrupt(irq);
1980    }
1981}
1982
1983/// NIC IRQ handler
1984///
1985/// Dispatches to the registered NIC device's `handle_interrupt()` which
1986/// reads ICR, tracks link state, and reclaims completed TX buffers.
1987/// The network stack is expected to call `receive()` in response (or a
1988/// future NAPI-style poll can be driven from here).
1989extern "x86-interrupt" fn nic_handler(stack_frame: InterruptStackFrame) {
1990    // SAFETY: SwapGsGuard restores kernel GS if we interrupted Ring 3.
1991    // Without this guard, SpinLock::lock → current_cpu_index() reads gs:[0]
1992    // and #PF if GS is still set to the user value (swapgs→iretq window).
1993    let _gs = SwapGsGuard::new((stack_frame.code_segment.0 & 3) == 3);
1994
1995    crate::hardware::nic::handle_interrupt();
1996
1997    if super::apic::is_initialized() {
1998        super::apic::eoi();
1999    } else {
2000        let irq = crate::hardware::nic::NIC_IRQ_LINE.load(core::sync::atomic::Ordering::Relaxed);
2001        pic::end_of_interrupt(irq);
2002    }
2003}
2004
2005/// Cross-CPU reschedule IPI handler (vector 0xF0).
2006///
2007/// Sent by another CPU (via `apic::send_resched_ipi`) to request that this
2008/// CPU preempts its current task immediately rather than waiting for the next
2009/// timer tick. This is used when a task running on this CPU is killed or
2010/// suspended by a different CPU.
2011///
2012/// EOI is sent ***before*** ` maybe_preempt()` so the APIC can accept further
2013/// IPIs before the potentially long context-switch path runs.
2014extern "x86-interrupt" fn resched_ipi_handler(stack_frame: InterruptStackFrame) {
2015    // Restore kernel GS if the IPI arrived while Ring 3 was running.
2016    let _gs = SwapGsGuard::new((stack_frame.code_segment.0 & 3) == 3);
2017    super::apic::eoi();
2018    crate::process::scheduler::maybe_preempt();
2019}
2020
2021/// Cross-CPU TLB shootdown IPI handler (vector 0xF0).
2022extern "x86-interrupt" fn tlb_shootdown_handler(stack_frame: InterruptStackFrame) {
2023    // Restore kernel GS if the IPI arrived while Ring 3 was running.
2024    let _gs = SwapGsGuard::new((stack_frame.code_segment.0 & 3) == 3);
2025    // Note: EOI is sent by the architecture-independent handler.
2026    super::tlb::tlb_shootdown_ipi_handler();
2027}