#![no_std] #![no_main] #![allow(unsafe_code)] use core::panic::PanicInfo; use rsnix::uefi::*; extern crate alloc; // --------------------------------------------------------------------------- // Panic handler // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Panic handler // --------------------------------------------------------------------------- #[panic_handler] fn panic(info: &PanicInfo) -> ! { use core::fmt::Write; // A background task (id >= 2) panicked: die gracefully — log it, // mark the task done, switch away. The shell keeps running and // `jobs` shows the corpse. Panics in the shell, idle, ISR context, // or before the scheduler exists still halt the whole system. if rsnix::sched::is_ready() && !rsnix::sched::in_isr() && rsnix::sched::current() >= 2 { let mut buf = [0u8; 192]; let n = { let mut w = rsnix::strbuf::FmtBuf::new(&mut buf); let _ = write!(&mut w, "{}", info.message()); if let Some(loc) = info.location() { let _ = write!(&mut w, " at {}:{}", loc.file(), loc.line()); } w.len() }; let msg = core::str::from_utf8(&buf[..n]).unwrap_or("task panic"); rsnix::sched::abort_current(msg); } rsnix::serial::write_str("\r\n*** PANIC ***\r\n"); let _ = write!(&mut rsnix::serial::SerialWriter, "{}\r\n", info.message()); if let Some(loc) = info.location() { let _ = write!( &mut rsnix::serial::SerialWriter, " at {}:{}\r\n", loc.file(), loc.line() ); } // Real hardware has no serial console — put the panic on the screen // too (white-on-red band), so a pre-shell panic is not a silent // blank screen. The last boot stage is appended so the user can // see how far boot got before the crash. let mut loc_buf = [0u8; 96]; let mut msg_buf = [0u8; 128]; let mut stage_buf = [0u8; 96]; let loc_n = { let mut w = rsnix::strbuf::FmtBuf::new(&mut loc_buf); if let Some(loc) = info.location() { let _ = write!(&mut w, "at {}:{}", loc.file(), loc.line()); } else { let _ = write!(&mut w, "(no location)"); } w.len() }; let msg_n = { let mut w = rsnix::strbuf::FmtBuf::new(&mut msg_buf); let _ = write!(&mut w, "{}", info.message()); w.len() }; let stage_n = { let s = rsnix::gop::last_boot_stage(); let n = s.len().min(stage_buf.len()); stage_buf[..n].copy_from_slice(&s.as_bytes()[..n]); n }; let lines = [ "*** PANIC ***", core::str::from_utf8(&msg_buf[..msg_n]).unwrap_or("?"), core::str::from_utf8(&loc_buf[..loc_n]).unwrap_or("?"), core::str::from_utf8(&stage_buf[..stage_n]).unwrap_or(""), ]; rsnix::gop::dump_lines(&lines); loop { unsafe { core::arch::asm!("cli; hlt"); } } } // --------------------------------------------------------------------------- // EFI entry point // --------------------------------------------------------------------------- /// EFI entry point. /// /// # Safety /// /// The firmware hands us raw pointers (image handle, system table) that /// must remain valid for the lifetime of the call — a contract guaranteed /// by the UEFI loader. #[unsafe(no_mangle)] pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemTable) -> ! { let st = unsafe { &mut *system_table }; let bs = unsafe { &mut *st.boot_services }; let con_out = unsafe { &mut *st.con_out }; // Keep the runtime-services table for later (ResetSystem → poweroff). rsnix::power::init(st.runtime_services); // ---- Phase 1: UEFI boot services alive ---- // Print banner via UEFI console unsafe { let s = concat!("rsnix v", env!("CARGO_PKG_VERSION"), " booting..\n"); let bytes = s.as_bytes(); let mut buf: [u16; 48] = [0; 48]; for (i, &b) in bytes.iter().enumerate().take(47) { buf[i] = b as u16; } (con_out.output_string)(con_out, buf.as_ptr()); } rsnix::serial::init(); rsnix::serial_println!("=== rsnix boot ==="); // Locate GOP (Graphics Output Protocol) *before* the memory map is // captured: the heap/page allocators need to know the framebuffer // region so they never hand out its pages (some real firmware lists // it as conventional memory). let mut gop_interface: *mut core::ffi::c_void = core::ptr::null_mut(); let gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; let gop_result = unsafe { (bs.locate_protocol)( &gop_guid as *const Guid, core::ptr::null_mut(), &mut gop_interface, ) }; if gop_result == EFI_SUCCESS { let gop = unsafe { &mut *(gop_interface as *mut GraphicsOutputProtocol) }; rsnix::gop::peek_fb_region(gop); } // Stash the memory map let map_key = match rsnix::memory::capture_memory_map(bs) { Ok(k) => k, Err(e) => { rsnix::serial_println!("mem: capture_memory_map failed 0x{:x}", e); 0 } }; if gop_result == EFI_SUCCESS { let gop = unsafe { &mut *(gop_interface as *mut GraphicsOutputProtocol) }; rsnix::serial_println!("gop: framebuffer found"); rsnix::gop::init(gop); } else { rsnix::serial_println!("gop: not found (0x{:x})", gop_result); } rsnix::gop::boot_marker("rsnix v0.5.5 booting.."); rsnix::vt::init(); let (rows, cols) = rsnix::vt::size(); rsnix::serial_println!("vt: terminal ready ({}x{})", rows, cols); let (fw, fh) = rsnix::gop::pixel_dims(); rsnix::gop::boot_marker(&alloc::format!("rsnix: gop {}x{} vt {}x{} ok", fw, fh, cols, rows)); // ExitBootServices — we now own the machine if unsafe { (bs.exit_boot_services)(image_handle, map_key) } != EFI_SUCCESS { rsnix::serial_println!("WARN: ExitBootServices failed, retrying..."); match unsafe { rsnix::memory::refresh_and_exit(bs, image_handle) } { Ok(_) => {} Err(e) => { rsnix::serial_println!("FATAL: cannot ExitBootServices (0x{:x})", e); loop { unsafe { core::arch::asm!("cli; hlt"); } } } } } // ---- Phase 2: Native mode ---- rsnix::serial::init(); rsnix::serial_println!("serial: COM1 115200 8N1"); rsnix::ps2::init(); rsnix::serial_println!("ps2: keyboard initialized"); if rsnix::ps2::mouse_init() { rsnix::serial_println!("ps2: mouse ready (IRQ12, stream mode)"); } else { rsnix::serial_println!("ps2: mouse not present"); } if rsnix::gdt::init() { rsnix::serial_println!("gdt: own GDT + TSS (IST: page-fault/double-fault)"); } else { rsnix::serial_println!("gdt: kept UEFI GDT (IST fault stacks off)"); } rsnix::interrupts::init(); rsnix::serial_println!("intr: IDT loaded, PIC remapped, timer ~100 Hz"); rsnix::gop::boot_marker("rsnix: serial+ps2+gdt+intr ok"); dump_memory_summary(); if rsnix::memory::self_test() { rsnix::serial_println!("heap: self-test OK (alloc/free/coalesce)"); } else { rsnix::serial_println!("heap: self-test FAILED"); } if rsnix::memory::init_page_allocator() { rsnix::serial_println!("pages: bitmap allocator ready"); } else { rsnix::serial_println!("pages: init FAILED"); } rsnix::gop::init_shadow(); rsnix::gop::boot_marker("rsnix: shadow ok"); if rsnix::memory::page_self_test() { rsnix::serial_println!("pages: self-test OK (alloc/free/runs)"); } else { rsnix::serial_println!("pages: self-test FAILED"); } if rsnix::paging::adopt_tables() { rsnix::serial_println!( "pg: adopted own page tables (cr3={:#x})", rsnix::paging::cr3() ); } else { rsnix::serial_println!("pg: adopt_tables FAILED"); } if rsnix::paging::self_test() { rsnix::serial_println!("pg: self-test OK (map/unmap/split)"); } else { rsnix::serial_println!("pg: self-test FAILED"); } // Scheduler init needs the page allocator (idle task's stack + guard // page) and the adopted page tables, so it runs after the memory // subsystem is fully up. The tick (interrupts::init) is already // live, which is what `sleep`/wake scheduling depends on. rsnix::sched::init(); let t = rsnix::rtc::now(); rsnix::serial_println!( "rtc: {}-{:02}-{:02} {:02}:{:02}:{:02}", t.year, t.month, t.day, t.hour, t.minute, t.second ); let devices = rsnix::pci::probe(); rsnix::serial_println!("pci: {} device(s) on all buses", devices.len()); // Disk: AHCI SATA driver, then mount every FAT32 volume under /disk//. if !rsnix::ahci::init() { rsnix::serial_println!("ahci: no disk attached"); } rsnix::fat::init(); // prints one line per mounted volume let (dirs, files) = rsnix::fs::init(); rsnix::serial_println!("fs: vfs ready — {} dirs, {} files", dirs, files); if rsnix::fs::self_test() { rsnix::serial_println!("fs: self-test OK (create/read/write/remove)"); } else { rsnix::serial_println!("fs: self-test FAILED"); } rsnix::serial_println!("boot: entering shell"); rsnix::gop::boot_marker("rsnix: boot done — shell"); if rsnix::ps2::mouse_present() { rsnix::cursor::spawn(); } else { rsnix::serial_println!("cursor: no mouse — overlay disabled"); } shell::run(); } // --------------------------------------------------------------------------- // Print a memory summary from the stashed memory map // --------------------------------------------------------------------------- fn dump_memory_summary() { let (map, key) = rsnix::memory::get_map(); let mut conventional_4k = 0u64; let mut addr_space_4k = 0u64; for desc in map.iter() { let pages = desc.number_of_pages; addr_space_4k += pages; if desc.kind == 7 { conventional_4k += pages; } } rsnix::serial_println!( "mem: {} entries, key=0x{:x}, usable: {} KB ({} MB)", map.len(), key, conventional_4k * 4, (conventional_4k * 4) / 1024, ); rsnix::serial_println!( "mem: address space: {} MB (incl. PCIe MMIO)", (addr_space_4k * 4) / 1024, ); }