From 045c9ddf71b62dff538cd3ae1963166ab5c145a0 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 2 Aug 2026 01:52:10 +0200 Subject: v0.5.5: visible boot progress + panic-on-screen for real hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real hardware has no serial console: a pre-shell panic was a silent blank screen (the panic handler only wrote to serial, then cli;hlt). v0.5.4's shadow change made this window reachable — if the fb-aware page allocation or the heap fallback failed on hardware, the kernel halted before the shell banner with zero screen output. - gop::boot_marker(): every boot stage now draws straight to the framebuffer (row 0, white-on-black). The last marker visible before a hang identifies where the kernel died. - Panic handler: pre-shell panics now draw a white-on-red '*** PANIC *** at file:line' band via gop::dump_lines. - init_shadow(): heap fallback is panic-proof (try_reserve_exact + resize instead of vec![0; words] which aborts on OOM); page-alloc result defensively re-checked against the fb region; any failure degrades to no-shadow mode with a visible warning band. - Verified: full chain (rsboot -> kernel) boots with markers, shell banner renders, all 5 test suites pass. --- rsnix/src/gop.rs | 72 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 16 deletions(-) (limited to 'rsnix') diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs index 437bf9d..7b97d14 100644 --- a/rsnix/src/gop.rs +++ b/rsnix/src/gop.rs @@ -265,28 +265,40 @@ pub fn init_shadow() { let mut origin = "none"; // Primary: fb-aware page allocator (fb pages already marked - // used by init_page_allocator). + // used by init_page_allocator). Defensively refuse the range if + // it ever overlaps the fb region anyway. if let Some(p) = crate::memory::alloc_pages(pages) { - SHADOW = p as *mut u32; - SHADOW_WORDS = words; - origin = "page-alloc"; - for i in 0..words { - *SHADOW.add(i) = fb.bg; - } - } else { - // Fallback: heap, rejected if it overlaps the fb region. - let v: alloc::vec::Vec = alloc::vec![0; words]; - let p = v.as_ptr() as usize; let overlaps = FB_REGION_SIZE > 0 && p < FB_REGION_BASE + FB_REGION_SIZE && p + bytes > FB_REGION_BASE; if !overlaps { - SHADOW = v.as_ptr() as *mut u32; + SHADOW = p as *mut u32; SHADOW_WORDS = words; - origin = "heap"; - core::mem::forget(v); // shadow owns the memory now - } else { - drop(v); + origin = "page-alloc"; + for i in 0..words { + *SHADOW.add(i) = fb.bg; + } + } + } + if origin == "none" { + // Fallback: heap — must never panic here: an OOM would halt + // the kernel before the shell banner, a blank screen on real + // hardware (the old `vec![0; words]` could abort). On any + // failure we degrade to no-shadow mode instead. + #[allow(clippy::slow_vector_initialization)] // deliberate: try_reserve must not panic + let mut v: alloc::vec::Vec = alloc::vec::Vec::new(); + if v.try_reserve_exact(words).is_ok() { + v.resize(words, 0); // can't fail after the reserve + let p = v.as_ptr() as usize; + let overlaps = FB_REGION_SIZE > 0 + && p < FB_REGION_BASE + FB_REGION_SIZE + && p + bytes > FB_REGION_BASE; + if !overlaps { + SHADOW = v.as_ptr() as *mut u32; + SHADOW_WORDS = words; + origin = "heap"; + core::mem::forget(v); // shadow owns the memory now + } } } let (rbase, rsize) = (FB_REGION_BASE, FB_REGION_SIZE); @@ -319,6 +331,34 @@ pub fn init_shadow() { } } +/// Draw a one-line boot-progress marker to the display (row 0, +/// white-on-black). Real hardware has no serial console, so every +/// boot stage is written straight to the framebuffer — the last visible +/// marker before a hang identifies where the kernel died. Uses the +/// direct-display glyph path (the shadow may not exist yet in early +/// boot); the shell banner overwrites row 0 at first paint. +pub fn boot_marker(msg: &str) { + let Some(fb) = fb().as_ref() else { + return; + }; + let cols = fb.width / 8; + let white = (0xFFu8, 0xFFu8, 0xFFu8); + let black = (0x00u8, 0x00u8, 0x00u8); + let mut c = 0usize; + for &b in msg.as_bytes().iter().take(cols) { + if b < 0x80 { + blit_glyph(c, 0, b as u16, white, black); + c += 1; + } + } + // Blank the rest of the row so a shorter message can't leave stale + // glyphs behind. + while c < cols { + blit_glyph(c, 0, b' ' as u16, white, black); + c += 1; + } +} + // --------------------------------------------------------------------------- // Public text API — the text surface now lives in `crate::vt`; these wrappers // keep callers (shell, macros) unchanged. -- cgit v1.2.3