diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-02 01:52:10 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-02 01:52:10 +0200 |
| commit | 045c9ddf71b62dff538cd3ae1963166ab5c145a0 (patch) | |
| tree | fd99757329f7b770f067aa5f0204c87068fea681 /rsnix | |
| parent | gop: rustfmt collapse in init_shadow warning band (diff) | |
| download | rs-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.rs | 72 |
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. | ||
| 340 | pub 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. |
