Skip to main content

strat9_kernel/boot/
panic.rs

1use alloc::string::String;
2use core::{
3    panic::PanicInfo,
4    sync::atomic::{AtomicBool, Ordering},
5};
6use spin::Mutex;
7use x86_64::VirtAddr;
8type PanicHook = fn(&PanicInfo);
9const MAX_PANIC_HOOKS: usize = 8;
10
11static PANIC_HOOKS: Mutex<[Option<PanicHook>; MAX_PANIC_HOOKS]> =
12    Mutex::new([None; MAX_PANIC_HOOKS]);
13static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
14
15/// Returns true if a kernel panic is currently in progress.
16pub fn panic_in_progress() -> bool {
17    PANIC_IN_PROGRESS.load(Ordering::SeqCst)
18}
19
20/// Register a function to be called during a panic (serial-only hooks).
21pub fn register_panic_hook(hook: PanicHook) -> bool {
22    let mut hooks = PANIC_HOOKS.lock();
23    for slot in hooks.iter_mut() {
24        if slot.is_none() {
25            *slot = Some(hook);
26            return true;
27        }
28    }
29    false
30}
31
32/// Run all registered panic hooks (serial output only : no framebuffer access).
33fn run_panic_hooks(info: &PanicInfo) {
34    if let Some(hooks) = PANIC_HOOKS.try_lock() {
35        for hook in hooks.iter().flatten() {
36            hook(info);
37        }
38    }
39}
40
41/// Dump CPU/scheduler context to serial.
42fn panic_hook_dump_context(_info: &PanicInfo) {
43    let cpu = crate::arch::x86_64::percpu::current_cpu_index();
44    let ticks = crate::process::scheduler::ticks();
45    let cr3 = crate::memory::paging::active_page_table().as_u64();
46    crate::serial_println!("panic-hook: cpu={} ticks={} cr3=0x{:x}", cpu, ticks, cr3);
47    if let Some(task) = crate::process::scheduler::current_task_clone_try() {
48        crate::serial_println!(
49            "panic-hook: current_task id={} name={}",
50            task.id.as_u64(),
51            task.name
52        );
53    } else {
54        crate::serial_println!("panic-hook: current_task none (scheduler locked or idle)");
55    }
56    let sched = crate::process::scheduler::state_snapshot();
57    if cpu < sched.cpu_count {
58        crate::serial_println!(
59            "panic-hook: sched cpu={} current_tid={} need_resched={} rq(rt/fair/idle)={}/{}/{} blocked={} init={} phase={}",
60            cpu,
61            sched.current_task[cpu],
62            sched.need_resched[cpu],
63            sched.rq_rt[cpu],
64            sched.rq_fair[cpu],
65            sched.rq_idle[cpu],
66            sched.blocked_tasks,
67            sched.initialized,
68            sched.boot_phase
69        );
70    }
71}
72
73// -----------------------------------------------------------------------
74// Register / stack helpers
75// -----------------------------------------------------------------------
76
77#[inline(always)]
78fn read_rbp() -> u64 {
79    let rbp: u64;
80    unsafe {
81        core::arch::asm!("mov {}, rbp", out(reg) rbp, options(nomem, nostack, preserves_flags));
82    }
83    rbp
84}
85
86#[inline(always)]
87fn read_rsp() -> u64 {
88    let rsp: u64;
89    unsafe {
90        core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nomem, nostack, preserves_flags));
91    }
92    rsp
93}
94
95fn addr_readable(addr: u64) -> bool {
96    crate::memory::paging::translate(VirtAddr::new(addr)).is_some()
97}
98
99/// Read CR0, CR2, CR3, CR4 into the provided mutable references.
100fn read_cr_regs() -> (u64, u64, u64, u64) {
101    let cr0: u64;
102    let cr2: u64;
103    let cr3: u64;
104    let cr4: u64;
105    unsafe {
106        core::arch::asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags));
107        core::arch::asm!("mov {}, cr2", out(reg) cr2, options(nomem, nostack, preserves_flags));
108        core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack, preserves_flags));
109        core::arch::asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack, preserves_flags));
110    }
111    (cr0, cr2, cr3, cr4)
112}
113
114/// Dump backtrace via frame-pointer unwinding, returning the lines.
115fn collect_backtrace() -> alloc::vec::Vec<alloc::string::String> {
116    use alloc::format;
117    let mut lines = alloc::vec::Vec::new();
118    let mut rbp = read_rbp();
119    let rsp = read_rsp();
120    lines.push(format!("RSP=0x{:016X} RBP=0x{:016X}", rsp, rbp));
121    lines.push("Backtrace (frame-pointer):".into());
122
123    for i in 0..16 {
124        if rbp == 0 || (rbp & 0x7) != 0 {
125            lines.push(format!("  #{:02}: stop (invalid rbp)", i));
126            break;
127        }
128        if !addr_readable(rbp) || !addr_readable(rbp.saturating_add(8)) {
129            lines.push(format!("  #{:02}: stop (unmapped)", i));
130            break;
131        }
132
133        let prev = unsafe { *(rbp as *const u64) };
134        let ret = unsafe { *((rbp + 8) as *const u64) };
135        lines.push(format!("  #{:02}: RIP=0x{:016X}", i, ret));
136
137        if prev <= rbp || prev.saturating_sub(rbp) > 1024 * 1024 {
138            break;
139        }
140        rbp = prev;
141    }
142    lines
143}
144
145/// Collect comprehensive debug info lines for the panic screen.
146fn collect_panic_lines(info: &PanicInfo) -> alloc::vec::Vec<alloc::string::String> {
147    use alloc::format;
148    let mut lines = alloc::vec::Vec::new();
149
150    // --- Title ---
151    lines.push("=== GURU MEDITATION :: KERNEL PANIC ===".into());
152    lines.push(String::new());
153
154    // --- Panic location ---
155    if let Some(loc) = info.location() {
156        lines.push(format!(
157            "File: {}:{}:{}",
158            loc.file(),
159            loc.line(),
160            loc.column()
161        ));
162    } else {
163        lines.push("File: (unknown)".into());
164    }
165    lines.push(format!("Message: {}", info.message()));
166    lines.push(String::new());
167
168    // --- CPU state ---
169    let cpu = crate::arch::x86_64::percpu::current_cpu_index();
170    let (cr0, cr2, cr3, cr4) = read_cr_regs();
171    let rsp = read_rsp();
172    let rbp = read_rbp();
173
174    lines.push(format!("CPU={}  CR0={:#X}  CR2={:#X}", cpu, cr0, cr2));
175    lines.push(format!("CR3={:#X}  CR4={:#X}", cr3, cr4));
176    lines.push(format!("RSP={:#018X}  RBP={:#018X}", rsp, rbp));
177    lines.push(String::new());
178
179    // --- Backtrace ---
180    let bt = collect_backtrace();
181    lines.extend(bt);
182    lines.push(String::new());
183
184    // --- Scheduler state (best-effort) ---
185    let ticks = crate::process::scheduler::ticks();
186    lines.push(format!("Ticks={}", ticks));
187    if let Some(task) = crate::process::scheduler::current_task_clone_try() {
188        lines.push(format!("Task: id={} name={}", task.id.as_u64(), task.name));
189    } else {
190        lines.push("Task: (scheduler locked / idle)".into());
191    }
192
193    let sched = crate::process::scheduler::state_snapshot();
194    if cpu < sched.cpu_count {
195        lines.push(format!(
196            "Sched: tid={} rt={} fair={} idle={} blocked={} init={}",
197            sched.current_task[cpu],
198            sched.rq_rt[cpu],
199            sched.rq_fair[cpu],
200            sched.rq_idle[cpu],
201            sched.blocked_tasks,
202            sched.initialized,
203        ));
204    }
205    lines.push(String::new());
206
207    // --- Timestamp if available ---
208    let ts = crate::arch::x86_64::boot_timestamp::elapsed_ms();
209    lines.push(format!("Uptime: {} ms", ts));
210
211    lines
212}
213
214/// Print all panic lines to the serial port (emergency mode already active).
215fn panic_serial_dump(lines: &[alloc::string::String]) {
216    crate::serial_println!("\n\x1b[31;1m!!! KERNEL PANIC !!!\x1b[0m");
217    for line in lines {
218        crate::serial_println!("{}", line);
219    }
220}
221
222// -----------------------------------------------------------------------
223// Public API
224// -----------------------------------------------------------------------
225
226/// Install the default panic hooks (serial context + backtrace dumps).
227pub fn install_default_panic_hooks() {
228    let _ = register_panic_hook(panic_hook_dump_context);
229}
230
231/// Main kernel panic handler.
232///
233/// Always emits full debug info to the serial port.  Tries to display it
234/// on the framebuffer via the normal VGA_WRITER path.  If VGA_WRITER is
235/// locked (e.g. the fault happened inside a write to the console), falls
236/// back to a direct framebuffer draw that bypasses all locks.
237///
238/// After all output is delivered, halts the current CPU forever.
239pub fn panic_handler(info: &PanicInfo) -> ! {
240    // 1. Emergency serial mode : serial_println! bypasses all locks.
241    crate::arch::x86_64::serial::enter_emergency_mode();
242
243    // 2. Guard against recursive panics.
244    if PANIC_IN_PROGRESS.swap(true, Ordering::SeqCst) {
245        loop {
246            crate::arch::x86_64::hlt();
247        }
248    }
249
250    // 3. Disable interrupts so nothing disturbs the dump.
251    crate::arch::x86_64::cli();
252
253    // 3b. Panic beep : audible signal before we halt.
254    crate::arch::x86_64::speaker::beep_panic();
255
256    // 4. Collect all debug information into a line list.
257    let lines = collect_panic_lines(info);
258
259    // 5. Dump everything to serial first (always works).
260    panic_serial_dump(&lines);
261
262    // 6. Run custom panic hooks (serial-only).
263    run_panic_hooks(info);
264
265    // 7. Stop all other CPUs.
266    crate::arch::x86_64::smp::broadcast_panic_halt();
267
268    // 7b. Flush the VGA circular buffer so any buffered log lines appear.
269    crate::arch::x86_64::vgabuf::vgabuf_flush_all();
270
271    // 8. Display panic info on framebuffer : two paths with fallback.
272    if crate::arch::x86_64::vga::is_available() {
273        // Path A: try the normal VGA_WRITER terminal (needs the Mutex).
274        let writer_locked = {
275            if let Some(mut writer) = crate::arch::x86_64::vga::VGA_WRITER.try_lock() {
276                use core::fmt::Write;
277                writer.set_rgb_color(
278                    crate::arch::x86_64::vga::RgbColor::new(0xFF, 0xE7, 0xA0),
279                    crate::arch::x86_64::vga::RgbColor::new(0x3A, 0x1F, 0x00),
280                );
281                writer.clear();
282                for line in &lines {
283                    let _ = writeln!(writer, "{}", line);
284                }
285                true
286            } else {
287                false
288            }
289        };
290
291        if !writer_locked {
292            // Path B: VGA_WRITER is locked : draw directly to the
293            // framebuffer using saved raw params.
294            let str_lines: alloc::vec::Vec<&str> = lines.iter().map(|s| s.as_str()).collect();
295            crate::arch::x86_64::vga::panic_draw_direct(&str_lines);
296        }
297    }
298
299    // 9. Halt forever.
300    loop {
301        crate::arch::x86_64::hlt();
302    }
303}