summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTEXT.md19
-rw-r--r--Cargo.lock6
-rw-r--r--Cargo.toml2
-rw-r--r--rsnix/src/gop.rs38
-rw-r--r--rsnix/src/sched.rs6
5 files changed, 49 insertions, 22 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index f068035..bf1e01f 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -113,13 +113,17 @@ Workspace version is `a.b.c` (single version for all crates):
113 113
114Bump the workspace version in the root `Cargo.toml` when a change of 114Bump the workspace version in the root `Cargo.toml` when a change of
115that class lands — do not leave the version to drift. Current: 115that class lands — do not leave the version to drift. Current:
116`0.5.2` — the GUI era landed with 0.5.0 (graphics layer + cursor 116`0.5.3` — the GUI era landed with 0.5.0 (graphics layer + cursor
117compositor); 0.5.1 fixed the canvas at 800×600; 0.5.2 added fault 117compositor); 0.5.1 fixed the canvas at 800×600; 0.5.2 added fault
118diagnostics (screen + serial dumps for every CPU exception) and 118diagnostics (screen + serial dumps for every CPU exception) and
119real-hardware hardening (framebuffer-size clamping, fb-region exclusion 119real-hardware hardening (framebuffer-size clamping, fb-region exclusion
120from the allocators, authoritative fb geometry logging). A future 120from the allocators, authoritative fb geometry logging); 0.5.3 fixed
121major GUI overhaul (graphics windows beyond the text surface) would be 121the real-hardware scroll wedge — the scroll's full-frame push is now
122the `a` bump (1.0.0). 122per-word volatile stores like the boot-time `clear()` instead of one
1231.92 MB rep-movsb, which can stall forever on an uncached real-GPU
124framebuffer (a stall, not an exception — hence no crash dump). A
125future major GUI overhaul (graphics windows beyond the text surface)
126would be the `a` bump (1.0.0).
123 127
124## Project layout 128## Project layout
125 129
@@ -553,7 +557,12 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
553 shadow too; scrolling is a cached shadow memcpy + one write-only 557 shadow too; scrolling is a cached shadow memcpy + one write-only
554 push to the display — the display is never *read* (uncached reads 558 push to the display — the display is never *read* (uncached reads
555 were the killer, since real-hardware GOP framebuffers are 559 were the killer, since real-hardware GOP framebuffers are
556 uncacheable/write-combining). 560 uncacheable/write-combining). **The push is per-word volatile
561 stores (0.5.3), not one bulk rep-movsb**: a 1.92 MB REP MOVS to an
562 uncached real-GPU framebuffer wedged the CPU on some hardware (a
563 stall, not an exception — no dump fired), while the boot-time
564 `clear()` — the same per-word pattern — always worked. QEMU's
565 RAM-backed framebuffer never showed it.
557 2. deferred scroll batching (`vt::PENDING_SCROLLS`): the logical grid 566 2. deferred scroll batching (`vt::PENDING_SCROLLS`): the logical grid
558 scrolls instantly in RAM while the framebuffer shift+push is 567 scrolls instantly in RAM while the framebuffer shift+push is
559 deferred; N pending scrolls cost one push. Bursts of streaming 568 deferred; N pending scrolls cost one push. Bursts of streaming
diff --git a/Cargo.lock b/Cargo.lock
index 942456c..65166d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
4 4
5[[package]] 5[[package]]
6name = "rsdos" 6name = "rsdos"
7version = "0.5.2" 7version = "0.5.3"
8dependencies = [ 8dependencies = [
9 "rsnix", 9 "rsnix",
10 "shell", 10 "shell",
@@ -19,11 +19,11 @@ dependencies = [
19 19
20[[package]] 20[[package]]
21name = "rsnix" 21name = "rsnix"
22version = "0.5.2" 22version = "0.5.3"
23 23
24[[package]] 24[[package]]
25name = "shell" 25name = "shell"
26version = "0.5.2" 26version = "0.5.3"
27dependencies = [ 27dependencies = [
28 "rsdosedit", 28 "rsdosedit",
29 "rsnix", 29 "rsnix",
diff --git a/Cargo.toml b/Cargo.toml
index 0aa38e2..6c16784 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ resolver = "2"
3members = ["rsnix", "shell", "rsdos", "rsdosedit"] 3members = ["rsnix", "shell", "rsdos", "rsdosedit"]
4 4
5[workspace.package] 5[workspace.package]
6version = "0.5.2" 6version = "0.5.3"
7edition = "2024" 7edition = "2024"
8 8
9# no_std UEFI kernel: no unwinder available, so panic must abort. 9# no_std UEFI kernel: no unwinder available, so panic must abort.
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index 47a2208..439dfb1 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -4,13 +4,13 @@
4/// module is the low-level rasteriser: glyph blits and pixel shifts on the 4/// module is the low-level rasteriser: glyph blits and pixel shifts on the
5/// linear framebuffer. 5/// linear framebuffer.
6use crate::font; 6use crate::font;
7use crate::uefi::{EFI_SUCCESS, GopModeInfo, GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; 7use crate::uefi::{GopModeInfo, GraphicsOutputProtocol, EFI_SUCCESS, PIXEL_BGR, PIXEL_RGB};
8use alloc::boxed::Box; 8use alloc::boxed::Box;
9use alloc::vec; 9use alloc::vec;
10use alloc::vec::Vec; 10use alloc::vec::Vec;
11use core::fmt; 11use core::fmt;
12use core::ptr; 12use core::ptr;
13use core::sync::atomic::{AtomicU8, AtomicU64, Ordering}; 13use core::sync::atomic::{AtomicU64, AtomicU8, Ordering};
14 14
15/// Framebuffer state captured before ExitBootServices. 15/// Framebuffer state captured before ExitBootServices.
16pub struct Framebuffer { 16pub struct Framebuffer {
@@ -590,12 +590,16 @@ impl Framebuffer {
590 unsafe { 590 unsafe {
591 if SHADOW.is_null() { 591 if SHADOW.is_null() {
592 // No shadow (allocation failed): fall back to shifting the 592 // No shadow (allocation failed): fall back to shifting the
593 // display directly. 593 // display directly — per-word volatile stores, never a
594 ptr::copy( 594 // bulk memcpy: a giant REP MOVS to an uncached MMIO
595 self.base.add(n * row_bytes), 595 // framebuffer can stall forever on real hardware (the
596 self.base, 596 // boot-time clear() uses the same per-word pattern and
597 total - n * row_bytes, 597 // provably works).
598 ); 598 let src = self.base.add(n * row_bytes) as *mut u32;
599 let n_words = (total - n * row_bytes) / 4;
600 for i in 0..n_words {
601 ptr::write_volatile(self.base.add(i * 4) as *mut u32, ptr::read(src.add(i)));
602 }
599 let last_row = self.base.add(total - n * row_bytes); 603 let last_row = self.base.add(total - n * row_bytes);
600 for i in 0..(n * row_words) { 604 for i in 0..(n * row_words) {
601 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg); 605 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg);
@@ -609,15 +613,25 @@ impl Framebuffer {
609 *SHADOW.add(i) = self.bg; 613 *SHADOW.add(i) = self.bg;
610 } 614 }
611 // Push the shadow to the display: write-only, no reads. 615 // Push the shadow to the display: write-only, no reads.
612 // A bulk copy (rep movsb) writes in 64-byte bursts that 616 //
613 // coalesce well on WC framebuffers — far fewer bus 617 // Per-word volatile stores, NOT a bulk rep-movsb: the shadow
614 // transactions than per-pixel stores. 618 // is cached RAM (reads are cheap) and each display store is
619 // individually posted, exactly like the boot-time clear(). A
620 // 1.92 MB REP MOVS to an uncached real-GPU framebuffer can
621 // wedge the CPU — the "fast string" burst path never drains
622 // on some hardware, the shell stalls mid-instruction, no
623 // exception fires, and the machine looks hung with a torn
624 // frame (the white bands). QEMU never shows this because its
625 // framebuffer is RAM.
615 // 626 //
616 // Clamp to the firmware's mapping size; a pitch-padded mode 627 // Clamp to the firmware's mapping size; a pitch-padded mode
617 // whose `pitch * height` exceeds the buffer degrades to a 628 // whose `pitch * height` exceeds the buffer degrades to a
618 // truncated view instead of faulting past the mapping. 629 // truncated view instead of faulting past the mapping.
619 let push_words = words.min(self.framebuffer_size / 4); 630 let push_words = words.min(self.framebuffer_size / 4);
620 ptr::copy_nonoverlapping(SHADOW, self.base as *mut u32, push_words); 631 let dst = self.base as *mut u32;
632 for i in 0..push_words {
633 ptr::write_volatile(dst.add(i), ptr::read(SHADOW.add(i)));
634 }
621 // The scroll copied the shadow (which never contains the 635 // The scroll copied the shadow (which never contains the
622 // cursor); re-composite it on top of the pushed frame. 636 // cursor); re-composite it on top of the pushed frame.
623 crate::gop::cursor_redraw(); 637 crate::gop::cursor_redraw();
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs
index cfce58f..f48e210 100644
--- a/rsnix/src/sched.rs
+++ b/rsnix/src/sched.rs
@@ -282,7 +282,11 @@ pub fn spawn(entry: fn(), name: &str) -> Option<usize> {
282 }; 282 };
283 let ok = spawn_at(id, entry, name); 283 let ok = spawn_at(id, entry, name);
284 unlock(); 284 unlock();
285 if ok { Some(id) } else { None } 285 if ok {
286 Some(id)
287 } else {
288 None
289 }
286} 290}
287 291
288unsafe fn reap(id: usize) { 292unsafe fn reap(id: usize) {