summaryrefslogtreecommitdiffstats
path: root/rsnix/src/vt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/vt.rs')
-rw-r--r--rsnix/src/vt.rs132
1 files changed, 104 insertions, 28 deletions
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 2906258..6e1f12f 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -173,48 +173,124 @@ pub fn debug_verify() {
173 }) 173 })
174 .unwrap_or(false); 174 .unwrap_or(false);
175 // Content check: at shell entry nothing has been printed yet, so 175 // Content check: at shell entry nothing has been printed yet, so
176 // every cell must still be BLANK (all-zero). A stale cells data 176 // every cell must still hold the grid's init fill. NOTE: that
177 // pointer that happens to be in-heap still passes the range 177 // fill is BLANK (space + fg 7 + bg 0 = raw 0x70020), NOT zero —
178 // checks (len/cap look fine) but its "cells" read back as the 178 // `vec![BLANK; n]` fills the grid with BLANK, and all boot
179 // underlying memory's leftover fill — the OptiPlex shows the 179 // output to this point is serial-only (boot markers are a
180 // firmware 0x00AAAAAA scrub there. This catches that. 180 // direct-FB blit, never the vt grid). The v0.5.7 check
181 // compared against 0 and therefore tripped on every healthy
182 // boot (the "0x70020 at grid(0,0)" mystery — the grid's own
183 // init value, with the probe loop's early `break` leaving the
184 // other three probe slots as zero-initialized). A stale cells
185 // data pointer that happens to be in-heap still passes the
186 // range checks (len/cap look fine) but its "cells" read back as
187 // the underlying memory's leftover fill — the OptiPlex shows
188 // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs
189 // scrub (0x00AAAAAA) is cleanly distinguishable.
190 let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24);
181 let mut content_ok = true; 191 let mut content_ok = true;
192 let mut rcontent_ok = true;
193 let mut c_probes_raw: [u32; 4] = [0; 4];
194 let mut r_probes_raw: [u32; 4] = [0; 4];
182 if heap_ok && c.len() == total && total > 0 { 195 if heap_ok && c.len() == total && total > 0 {
183 let mut i = 0usize; 196 // Sample the first cell, the last cell, and two interior
184 // Sample the first cell, the last cell, and a few interior
185 // cells — reading the whole grid here would be ~60 KiB of 197 // cells — reading the whole grid here would be ~60 KiB of
186 // checks at every boot for no real benefit. 198 // checks at every boot for no real benefit. All four
199 // probes are recorded unconditionally (no early break) so
200 // a serial log shows every value.
187 let probes = [0usize, total / 3, (2 * total) / 3, total - 1]; 201 let probes = [0usize, total / 3, (2 * total) / 3, total - 1];
188 for &p in probes.iter() { 202 for (pi, &p) in probes.iter().enumerate() {
189 if i >= probes.len() {
190 break;
191 }
192 i += 1;
193 let cell = *c.as_ptr().add(p); 203 let cell = *c.as_ptr().add(p);
194 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); 204 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24);
195 if raw != 0 { 205 c_probes_raw[pi] = raw;
206 if raw != blank_raw {
196 content_ok = false; 207 content_ok = false;
197 break; 208 }
209 }
210 // Same probe on the rendered grid: it is a *separate*
211 // allocation, so its content discriminates "only the cells
212 // VecData's ptr field is stale" (rendered ok) from "both
213 // grids' memory was clobbered / both VecData ptrs are
214 // stale" (rendered garbage too).
215 for (pi, &p) in probes.iter().enumerate() {
216 let cell = *r.as_ptr().add(p);
217 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24);
218 r_probes_raw[pi] = raw;
219 if raw != blank_raw {
220 rcontent_ok = false;
221 }
222 }
223 }
224 // Full-grid summary (cheap: a few thousand reads, serial-only).
225 // Definitive health signal on QEMU: healthy = every cell is
226 // BLANK (nz == blanks == total); the probes above are just the
227 // tripwire, this catches a partial clobber that the four
228 // samples happen to miss.
229 let (mut c_nz, mut c_blank) = (0usize, 0usize);
230 let (mut r_nz, mut r_blank) = (0usize, 0usize);
231 if heap_ok && c.len() == total && r.len() == total && total > 0 {
232 for i in 0..total {
233 let cell = *c.as_ptr().add(i);
234 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24);
235 if raw != 0 {
236 c_nz += 1;
237 }
238 if raw == blank_raw {
239 c_blank += 1;
240 }
241 let cell = *r.as_ptr().add(i);
242 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24);
243 if raw != 0 {
244 r_nz += 1;
245 }
246 if raw == blank_raw {
247 r_blank += 1;
198 } 248 }
199 } 249 }
200 } 250 }
201 if c.len() != total || r.len() != total || !heap_ok || !content_ok { 251 msg(&alloc::format!(
252 "verify: grids c nz={} blank={} r nz={} blank={} (of {})",
253 c_nz, c_blank, r_nz, r_blank, total
254 ));
255 if c.len() != total || r.len() != total || !heap_ok || !content_ok || !rcontent_ok {
202 msg("verify: FAILED — cells/rendered not sane at shell entry"); 256 msg("verify: FAILED — cells/rendered not sane at shell entry");
203 crate::gop::boot_marker(&alloc::format!( 257 msg(&alloc::format!(
204 "vt: cells {:#x} {}/{} {}", 258 "verify: probes c=[{:#x},{:#x},{:#x},{:#x}] r=[{:#x},{:#x},{:#x},{:#x}]",
205 c.as_ptr() as usize, 259 c_probes_raw[0],
206 c.len(), 260 c_probes_raw[1],
207 total, 261 c_probes_raw[2],
208 if content_ok { "range" } else { "CONTENT-GARBAGE" } 262 c_probes_raw[3],
263 r_probes_raw[0],
264 r_probes_raw[1],
265 r_probes_raw[2],
266 r_probes_raw[3]
209 )); 267 ));
268 // Bottom-anchored: row 0-1 get overwritten by the shell
269 // banner, row 2 by the prompt (and a stale crash.log
270 // replays at rows 2+), so this marker must live at the very
271 // bottom to stay readable until the user can copy it.
272 crate::gop::boot_marker_at(
273 &alloc::format!(
274 "vt: c {:#x} cap={:#x} {} | r {:#x} {}",
275 c.as_ptr() as usize,
276 c.capacity(),
277 if content_ok { "ok" } else { "GARBAGE" },
278 r.as_ptr() as usize,
279 if rcontent_ok { "ok" } else { "GARBAGE" }
280 ),
281 crate::gop::bottom_row(1),
282 );
210 } else { 283 } else {
211 msg("verify: ok"); 284 msg("verify: ok");
212 crate::gop::boot_marker(&alloc::format!( 285 crate::gop::boot_marker_at(
213 "vt: cells {:#x} {}/{} ok", 286 &alloc::format!(
214 c.as_ptr() as usize, 287 "vt: c {:#x} cap={:#x} ok | r {:#x} ok",
215 c.len(), 288 c.as_ptr() as usize,
216 total 289 c.capacity(),
217 )); 290 r.as_ptr() as usize
291 ),
292 crate::gop::bottom_row(1),
293 );
218 } 294 }
219 } 295 }
220} 296}