summaryrefslogtreecommitdiffstats
path: root/rsnix/src/gop.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-03 02:23:35 +0200
committersalmonik <salmonik@tuta.io>2026-08-03 02:23:35 +0200
commit9fde94d068f87b34e0bc6bfa48388d89bb54f1e4 (patch)
treea5a4ce95363e7a316a23371489f2b0b85c1a8735 /rsnix/src/gop.rs
parentv0.5.7: fix vt verify content check — expect BLANK (0x70020), not zero (diff)
downloadrs-dos-9fde94d068f87b34e0bc6bfa48388d89bb54f1e4.tar.gz
rs-dos-9fde94d068f87b34e0bc6bfa48388d89bb54f1e4.zip
v0.5.8: heap-free QR encoder + crash-dump QR on framebuffer
Real hardware has no serial console, so a kernel crash only leaves a frozen screen. Every dump_lines() report now also renders the crash text as a scannable QR below the white-on-red band (photograph the screen instead of relaying text). - rsnix/src/qr.rs: from-scratch no_std/no_alloc byte-mode QR encoder (EC-L, v1..v40, .bss statics only — safe on the fault path): RS over GF(256), codeword interleave, zigzag placement, 8 masks with N1-N4 penalty auto-selection, format/version info. - gop.rs: dump_qr() joins dump lines into a static buffer, truncates with '...' at the v40-L capacity, encodes, and renders centered black-on-white with a 4-module quiet zone. - tools/qrcheck: host harness (embedds qr.rs) + qrcheck.py cross-check against segno's placement primitives. Verified bit-exact: 12 versions x 8 masks forced, plus auto-mask choice matches segno's penalty evaluation. Caught 3 encoder bugs (multi-block interleave offsets, transposed version info, mask eval ordering). - QEMU e2e: page-fault dump screendump decodes via zbarimg, content matches the serial log line-for-line. - test-msg.sh and test-fault.sh both PASS; debug+release 0 warnings.
Diffstat (limited to '')
-rw-r--r--rsnix/src/gop.rs83
1 files changed, 83 insertions, 0 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index 012a9d0..ecf8fa9 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -516,6 +516,89 @@ pub fn dump_lines(lines: &[&str]) {
516 } 516 }
517 } 517 }
518 } 518 }
519 // Scannable QR of the same text below the band (heap-free encoder;
520 // real hardware has no serial console — photograph the screen).
521 dump_qr(lines);
522}
523
524/// Render the crash/fault lines as a QR code below the dump band.
525///
526/// The joined text is built in a `.bss` buffer (the fault path must not
527/// allocate) and encoded byte-mode EC-L by `crate::qr` (also heap-free,
528/// static matrix). Over-long text is truncated with a "…" marker. If
529/// the text does not fit a QR or there is no framebuffer, this is a no-op
530/// and the band text remains the only channel.
531static mut QR_TEXT: [u8; crate::qr::MAX_BYTES + 4] = [0; crate::qr::MAX_BYTES + 4];
532
533pub fn dump_qr(lines: &[&str]) {
534 let Some(fb) = fb().as_ref() else {
535 return;
536 };
537 unsafe {
538 let text = &mut *core::ptr::addr_of_mut!(QR_TEXT);
539 let mut n = 0usize;
540 for line in lines {
541 let b = line.as_bytes();
542 let take = b.len().min(text.len() - n);
543 text[n..n + take].copy_from_slice(&b[..take]);
544 n += take;
545 if n < text.len() {
546 text[n] = b'\n';
547 n += 1;
548 }
549 }
550 while n > 0 && text[n - 1] == b'\n' {
551 n -= 1;
552 }
553 if n == 0 {
554 return;
555 }
556 let cap = crate::qr::MAX_BYTES.saturating_sub(3);
557 if n > cap {
558 n = cap;
559 text[n..n + 3].copy_from_slice(b"...");
560 n += 3;
561 }
562 if crate::qr::encode(&text[..n]) == 0 {
563 return;
564 }
565 }
566 render_qr(fb, lines.len() * 16);
567}
568
569/// Draw the current QR matrix (black on white, quiet zone) centred below
570/// the band, scaled to the largest square that fits.
571fn render_qr(fb: &Framebuffer, band_px: usize) {
572 let size = crate::qr::size();
573 if size == 0 {
574 return;
575 }
576 let total = size + 8; // 4-module quiet zone each side
577 let avail_w = fb.width.saturating_sub(24);
578 let avail_h = fb.height.saturating_sub(band_px + 16);
579 let scale = (avail_w / total).min(avail_h / total);
580 if scale < 1 {
581 return;
582 }
583 let px = total * scale;
584 let x0 = (fb.width - px) / 2;
585 let y0 = band_px + 8 + (avail_h.saturating_sub(px) / 2);
586 let white = rgb(0xFF, 0xFF, 0xFF);
587 let black = rgb(0, 0, 0);
588 fill_rect(x0, y0, px, px, white);
589 for y in 0..size {
590 for x in 0..size {
591 if crate::qr::module(x, y) {
592 fill_rect(
593 x0 + (x + 4) * scale,
594 y0 + (y + 4) * scale,
595 scale,
596 scale,
597 black,
598 );
599 }
600 }
601 }
519} 602}
520 603
521/// Read one pixel back from the shadow (RAM; cheap). Test/demo use only. 604/// Read one pixel back from the shadow (RAM; cheap). Test/demo use only.