From ab7d87bcde31c1681ea09c30534dde840a58d6fa Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 22:28:34 +0200 Subject: rsnix v0.5.2: fault diagnostics + real-hardware scroll-crash hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real-hardware scroll crash (white bands + instant freeze, QEMU-clean) was invisible because the only trace was a single serial line on machines with no serial console. This makes every crash self-identifying and hardens the real-hardware failure surfaces: - Every CPU exception vector 0-31 now has an IDT entry (macro-generated stubs; 8/14 keep their IST stacks). A fault in the shell prints a white-on-red band to the framebuffer via gop::dump_lines (raw fb writes bypassing the vt, fully clamped) plus a parseable serial line: '!!! vec=N err=… rip=… cr2=… rsp=… rflags=… task=N "name"', then halts — instead of converging to a cryptic double fault. Background-task faults abort the task as before. - Boot log now reports the authoritative post-SetMode geometry: 'gop: fb base=… size=… pitch=… WxH bpp=… bgr=…', so the next real machine's report identifies whether pitch*height exceeds the mapping. - Framebuffer writes (clear, push_rect, shift_up_n bulk push, cursor overlay) are clamped to the firmware's framebuffer_size: a pitch- padded mode degrades to a truncated view instead of faulting past the mapping. - The GOP framebuffer region is excluded from the page allocator (and subtracted from the heap's conventional-region pick) even when the firmware lists it as ConventionalMemory — kernel allocations can never overwrite the display again. The GOP protocol is located and peeked before the memory map is captured so the exclusion is known at heap-init time. - Shell gains a hidden 'fault' command (deliberate unmapped write) and 'make test-fault' verifies the dump: vec=14, cr2=0x400000000000, task=0, halt — no triple fault. All other suites re-pass. - CONTEXT.md: fault-dump format documented for real-hardware reports. --- rsnix/src/memory.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'rsnix/src/memory.rs') diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs index e03af8b..a947c53 100644 --- a/rsnix/src/memory.rs +++ b/rsnix/src/memory.rs @@ -354,14 +354,29 @@ pub unsafe fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Resul fn init_heap() { unsafe { + // Region the firmware maps for the GOP framebuffer, if known — + // exclude it even when the memory map lists it as conventional + // (some real firmware does exactly that, which would let heap + // allocations overwrite the display). + let fb = crate::gop::framebuffer_region(); let mut best_start = 0usize; let mut best_size = 0usize; for i in 0..MAP_ENTRIES { let d = &MEMORY_MAP[i]; if d.kind == MEMORY_TYPE_CONVENTIONAL { - let sz = d.number_of_pages as usize * 4096; + let s = d.physical_start as usize; + let e = s + d.number_of_pages as usize * 4096; + // Subtract any overlap with the framebuffer region. + let (cs, ce) = match fb { + Some((b, sz)) if e > b && s < b + sz => (s.max(b), e.min(b + sz)), + _ => (e, s), // no overlap → empty range + }; + let mut sz = e - s; + if ce > cs { + sz -= ce - cs; + } if sz > best_size { - best_start = d.physical_start as usize; + best_start = s; best_size = sz; } } @@ -634,6 +649,22 @@ pub fn init_page_allocator() -> bool { } } + // Reserve the GOP framebuffer region even if the map lists it as + // conventional — kernel allocations (stacks, DMA buffers) must + // never land on the display. + if let Some((fbase, fsize)) = crate::gop::framebuffer_region() + && fsize > 0 + { + let s = fbase / PAGE_SIZE; + let e = (fbase + fsize).div_ceil(PAGE_SIZE); + for p in s..e { + let idx = p - base_page; + if idx < span_pages { + *bm.add(idx / 8) |= 1 << (idx % 8); + } + } + } + // Never hand out page 0 (the null-pointer trap page). if base_page == 0 { *bm |= 0x01; -- cgit v1.2.3