summaryrefslogtreecommitdiffstats
path: root/rsnix/src/vt.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-04 01:44:05 +0200
committersalmonik <salmonik@tuta.io>2026-08-04 01:44:05 +0200
commit3e2261d83176752f3ea21e0c97f330b4c1757437 (patch)
tree8bbc92f917121fafaf193f1af1c42fba624abda9 /rsnix/src/vt.rs
parentv0.5.8: heap-free QR encoder + crash-dump QR on framebuffer (diff)
downloadrs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.tar.gz
rs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.zip
v0.5.9: fix OptiPlex heap crash — EBS retry must not rebuild the free list
Root cause: on real hardware SetMode can invalidate the pre-captured ExitBootServices map key, so EBS fails and the retry refreshed the map via capture_memory_map -> init_heap, which REBUILT the free list from the raw map and wiped the allocation state of every pre-EBS live object (the display shadow and the vt grids). Their spans became free while still live, later allocations carved inside the shadow, and the display writes clobbered them — zeroed back-pointers (family A ptr=0x404443e0 block=0x0) and gray glyph pixels (family B 0xaaaaaa00aaaaaa = 0x00AAAAAA = PALETTE[7] fg-7 text colour, not a firmware scrub). Fix: refresh_and_exit now re-captures the map and returns a fresh EBS key without rebuilding the free list (refresh_map_key); the free list is the authoritative live-allocation state. Verified in QEMU under a forced EBS failure (SIMULATE_EBS_FAILURE test hook): verify ok, probe NOT free, flhead outside the shadow — identical to a clean boot, and the full shell sequence is crash-free. Also: removed the crash-family debug markers from the screen (boot-time heapdbg lines, vt verify marker, shadow marker) — the vt grid self-check stays serial-only. Version 0.5.8 -> 0.5.9.
Diffstat (limited to '')
-rw-r--r--rsnix/src/vt.rs69
1 files changed, 44 insertions, 25 deletions
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 88f8d43..4ffb44d 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -138,6 +138,29 @@ pub fn size() -> (usize, usize) {
138 unsafe { (ROWS, COLS) } 138 unsafe { (ROWS, COLS) }
139} 139}
140 140
141/// DEBUG (crash-family): read one cell's raw word (ch | fg<<16 | bg<<24)
142/// from the live cells grid and the rendered grid, for the given text
143/// row/col. Returns (raw_cell, raw_rendered).
144pub fn cell_raw(r: usize, c: usize) -> (u32, u32) {
145 unsafe {
146 if CELLS.is_null() || RENDERED.is_null() {
147 return (0xFFFF_FFFF, 0xFFFF_FFFF);
148 }
149 let raw = |v: &Vec<Cell>| -> u32 {
150 if r < ROWS && c < COLS {
151 let cell = v[r * COLS + c];
152 cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24)
153 } else {
154 0xFFFF_FFFE
155 }
156 };
157 (
158 raw(&*(CELLS as *const Vec<Cell>)),
159 raw(&*(RENDERED as *const Vec<Cell>)),
160 )
161 }
162}
163
141/// Shell-entry self-check: dump the vt grid's heap structs so a real- 164/// Shell-entry self-check: dump the vt grid's heap structs so a real-
142/// hardware boot shows whether the cells VecData / data arrays are sane 165/// hardware boot shows whether the cells VecData / data arrays are sane
143/// at shell entry (the OptiPlex's first banner write #GPs when the 166/// at shell entry (the OptiPlex's first banner write #GPs when the
@@ -184,9 +207,12 @@ pub fn debug_verify() {
184 // other three probe slots as zero-initialized). A stale cells 207 // other three probe slots as zero-initialized). A stale cells
185 // data pointer that happens to be in-heap still passes the 208 // data pointer that happens to be in-heap still passes the
186 // range checks (len/cap look fine) but its "cells" read back as 209 // range checks (len/cap look fine) but its "cells" read back as
187 // the underlying memory's leftover fill — the OptiPlex shows 210 // the underlying memory's leftover fill. NOTE: the OptiPlex
188 // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs 211 // dump value 0x00AAAAAA is the VGA GRAY text colour
189 // scrub (0x00AAAAAA) is cleanly distinguishable. 212 // (rgb(170,170,170) = PALETTE[7] = fg 7), i.e. rendered shadow
213 // pixels — the memory read as heap held display text, not a
214 // firmware scrub. BLANK (0x70020) vs 0x00AAAAAA is cleanly
215 // distinguishable.
190 let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); 216 let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24);
191 let mut content_ok = true; 217 let mut content_ok = true;
192 let mut rcontent_ok = true; 218 let mut rcontent_ok = true;
@@ -269,31 +295,24 @@ pub fn debug_verify() {
269 r_probes_raw[2], 295 r_probes_raw[2],
270 r_probes_raw[3] 296 r_probes_raw[3]
271 )); 297 ));
272 // Bottom-anchored: row 0-1 get overwritten by the shell 298 // NOTE: debug markers are serial-only now; the crash family
273 // banner, row 2 by the prompt (and a stale crash.log 299 // was resolved (EBS-retry free-list rebuild) and the screen
274 // replays at rows 2+), so this marker must live at the very 300 // must stay clean for users.
275 // bottom to stay readable until the user can copy it. 301 crate::serial_println!(
276 crate::gop::boot_marker_at( 302 "vt: c {:#x} cap={:#x} {} | r {:#x} {}",
277 &alloc::format!( 303 c.as_ptr() as usize,
278 "vt: c {:#x} cap={:#x} {} | r {:#x} {}", 304 c.capacity(),
279 c.as_ptr() as usize, 305 if content_ok { "ok" } else { "GARBAGE" },
280 c.capacity(), 306 r.as_ptr() as usize,
281 if content_ok { "ok" } else { "GARBAGE" }, 307 if rcontent_ok { "ok" } else { "GARBAGE" }
282 r.as_ptr() as usize,
283 if rcontent_ok { "ok" } else { "GARBAGE" }
284 ),
285 crate::gop::bottom_row(1),
286 ); 308 );
287 } else { 309 } else {
288 msg("verify: ok"); 310 msg("verify: ok");
289 crate::gop::boot_marker_at( 311 crate::serial_println!(
290 &alloc::format!( 312 "vt: c {:#x} cap={:#x} ok | r {:#x} ok",
291 "vt: c {:#x} cap={:#x} ok | r {:#x} ok", 313 c.as_ptr() as usize,
292 c.as_ptr() as usize, 314 c.capacity(),
293 c.capacity(), 315 r.as_ptr() as usize
294 r.as_ptr() as usize
295 ),
296 crate::gop::bottom_row(1),
297 ); 316 );
298 } 317 }
299 } 318 }