summaryrefslogtreecommitdiffstats
path: root/rsnix
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 01:52:10 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 01:52:10 +0200
commit045c9ddf71b62dff538cd3ae1963166ab5c145a0 (patch)
treefd99757329f7b770f067aa5f0204c87068fea681 /rsnix
parentgop: rustfmt collapse in init_shadow warning band (diff)
downloadrs-dos-045c9ddf71b62dff538cd3ae1963166ab5c145a0.tar.gz
rs-dos-045c9ddf71b62dff538cd3ae1963166ab5c145a0.zip
v0.5.5: visible boot progress + panic-on-screen for real hardware
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 *** <msg> 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.
Diffstat (limited to '')
-rw-r--r--rsnix/src/gop.rs72
1 files changed, 56 insertions, 16 deletions
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() {
265 let mut origin = "none"; 265 let mut origin = "none";
266 266
267 // Primary: fb-aware page allocator (fb pages already marked 267 // Primary: fb-aware page allocator (fb pages already marked
268 // used by init_page_allocator). 268 // used by init_page_allocator). Defensively refuse the range if
269 // it ever overlaps the fb region anyway.
269 if let Some(p) = crate::memory::alloc_pages(pages) { 270 if let Some(p) = crate::memory::alloc_pages(pages) {
270 SHADOW = p as *mut u32;
271 SHADOW_WORDS = words;
272 origin = "page-alloc";
273 for i in 0..words {
274 *SHADOW.add(i) = fb.bg;
275 }
276 } else {
277 // Fallback: heap, rejected if it overlaps the fb region.
278 let v: alloc::vec::Vec<u32> = alloc::vec![0; words];
279 let p = v.as_ptr() as usize;
280 let overlaps = FB_REGION_SIZE > 0 271 let overlaps = FB_REGION_SIZE > 0
281 && p < FB_REGION_BASE + FB_REGION_SIZE 272 && p < FB_REGION_BASE + FB_REGION_SIZE
282 && p + bytes > FB_REGION_BASE; 273 && p + bytes > FB_REGION_BASE;
283 if !overlaps { 274 if !overlaps {
284 SHADOW = v.as_ptr() as *mut u32; 275 SHADOW = p as *mut u32;
285 SHADOW_WORDS = words; 276 SHADOW_WORDS = words;
286 origin = "heap"; 277 origin = "page-alloc";
287 core::mem::forget(v); // shadow owns the memory now 278 for i in 0..words {
288 } else { 279 *SHADOW.add(i) = fb.bg;
289 drop(v); 280 }
281 }
282 }
283 if origin == "none" {
284 // Fallback: heap — must never panic here: an OOM would halt
285 // the kernel before the shell banner, a blank screen on real
286 // hardware (the old `vec![0; words]` could abort). On any
287 // failure we degrade to no-shadow mode instead.
288 #[allow(clippy::slow_vector_initialization)] // deliberate: try_reserve must not panic
289 let mut v: alloc::vec::Vec<u32> = alloc::vec::Vec::new();
290 if v.try_reserve_exact(words).is_ok() {
291 v.resize(words, 0); // can't fail after the reserve
292 let p = v.as_ptr() as usize;
293 let overlaps = FB_REGION_SIZE > 0
294 && p < FB_REGION_BASE + FB_REGION_SIZE
295 && p + bytes > FB_REGION_BASE;
296 if !overlaps {
297 SHADOW = v.as_ptr() as *mut u32;
298 SHADOW_WORDS = words;
299 origin = "heap";
300 core::mem::forget(v); // shadow owns the memory now
301 }
290 } 302 }
291 } 303 }
292 let (rbase, rsize) = (FB_REGION_BASE, FB_REGION_SIZE); 304 let (rbase, rsize) = (FB_REGION_BASE, FB_REGION_SIZE);
@@ -319,6 +331,34 @@ pub fn init_shadow() {
319 } 331 }
320} 332}
321 333
334/// Draw a one-line boot-progress marker to the display (row 0,
335/// white-on-black). Real hardware has no serial console, so every
336/// boot stage is written straight to the framebuffer — the last visible
337/// marker before a hang identifies where the kernel died. Uses the
338/// direct-display glyph path (the shadow may not exist yet in early
339/// boot); the shell banner overwrites row 0 at first paint.
340pub fn boot_marker(msg: &str) {
341 let Some(fb) = fb().as_ref() else {
342 return;
343 };
344 let cols = fb.width / 8;
345 let white = (0xFFu8, 0xFFu8, 0xFFu8);
346 let black = (0x00u8, 0x00u8, 0x00u8);
347 let mut c = 0usize;
348 for &b in msg.as_bytes().iter().take(cols) {
349 if b < 0x80 {
350 blit_glyph(c, 0, b as u16, white, black);
351 c += 1;
352 }
353 }
354 // Blank the rest of the row so a shorter message can't leave stale
355 // glyphs behind.
356 while c < cols {
357 blit_glyph(c, 0, b' ' as u16, white, black);
358 c += 1;
359 }
360}
361
322// --------------------------------------------------------------------------- 362// ---------------------------------------------------------------------------
323// Public text API — the text surface now lives in `crate::vt`; these wrappers 363// Public text API — the text surface now lives in `crate::vt`; these wrappers
324// keep callers (shell, macros) unchanged. 364// keep callers (shell, macros) unchanged.