summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 21:30:48 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 21:30:48 +0200
commitd9c47d52039be7a61d3e9d3652f73322fb6f47ba (patch)
tree6e195f0819c97378ec5e0a3a049365d5e1f59ea5
parentrsnix v0.5.0: graphics layer + cursor compositor (diff)
downloadrs-dos-d9c47d52039be7a61d3e9d3652f73322fb6f47ba.tar.gz
rs-dos-d9c47d52039be7a61d3e9d3652f73322fb6f47ba.zip
rsnix v0.5.1: force the GUI canvas to 800x600 via GOP SetMode
The kernel used to inherit whatever GOP mode the firmware picked (QEMU/OVMF gave 1280x800). Now every surface derives from a fixed 800x600 canvas: - uefi: typed EfiGopQueryMode/SetMode/GetMode fn pointers in GraphicsOutputProtocol (were raw *mut c_void, never called). - gop::select_gop_mode: scan the mode list for an exact 800x600 (32-bit BGR/RGB only, the driver assumes 4 Bpp), SetMode it; fall back to the smallest mode that fits the canvas, then to the firmware's choice. SetMode may move both the mode info pointer and the framebuffer base, so both are re-read after the switch. Boot log: 'gop: mode 3 selected — 800x600 (was 1280x800)'. - tests: relative assertions are resolution-agnostic; test-gfx and test-mouse had a boot-log race (shell line prints before the cursor task line) — now wait for the cursor line instead of grepping once. All four suites pass at 800x600. 0 build warnings.
Diffstat (limited to '')
-rw-r--r--CONTEXT.md16
-rw-r--r--Cargo.lock6
-rw-r--r--Cargo.toml2
-rw-r--r--rsnix/src/gop.rs87
-rw-r--r--rsnix/src/uefi.rs22
-rwxr-xr-xscripts/test-gfx.sh2
-rwxr-xr-xscripts/test-mouse.sh4
7 files changed, 124 insertions, 15 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index bfe0170..375bf57 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -113,10 +113,10 @@ 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.0` — the GUI era landed (graphics layer + cursor compositor) as a 116`0.5.1` — the GUI era landed with 0.5.0 (graphics layer + cursor
117`b` bump from the 0.4.0 input foundation; a future major GUI overhaul 117compositor) and the canvas was fixed at 800×600 as a `c` bump; a
118(graphics windows beyond the text surface) would be the `a` bump 118future major GUI overhaul (graphics windows beyond the text surface)
119(1.0.0). 119would be the `a` bump (1.0.0).
120 120
121## Project layout 121## Project layout
122 122
@@ -515,6 +515,14 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target:
515 the cursor tip is white on the display but absent from the shadow 515 the cursor tip is white on the display but absent from the shadow
516 (overlay purity), and prints PASS/FAIL per stage. 516 (overlay purity), and prints PASS/FAIL per stage.
517 517
518- **Canvas resolution fixed at 800×600** — `gop::init` scans the GOP
519 mode list and calls `SetMode` for an exact 800×600 (BGR/RGB 32-bit;
520 QEMU std-vga lists it as mode 3, real GPUs list it as a standard
521 mode), falling back to the smallest mode that fits, then to the
522 firmware's choice. Everything derives from it: the vt text grid,
523 the cursor clamp, the demo strip. `SetMode` may move the info
524 pointer and framebuffer base, so they are re-read after the switch.
525
518 Verified in QEMU (`make test-gfx`): `fill_rect/shadow: OK`, 526 Verified in QEMU (`make test-gfx`): `fill_rect/shadow: OK`,
519 `display push: OK`, `cursor overlay: OK`, `cursor not in shadow: 527 `display push: OK`, `cursor overlay: OK`, `cursor not in shadow:
520 OK`, `gfx check: PASS`; the demo draws; the cursor tracks injected 528 OK`, `gfx check: PASS`; the demo draws; the cursor tracks injected
diff --git a/Cargo.lock b/Cargo.lock
index 19806d4..0d31a3b 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.0" 7version = "0.5.1"
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.0" 22version = "0.5.1"
23 23
24[[package]] 24[[package]]
25name = "shell" 25name = "shell"
26version = "0.5.0" 26version = "0.5.1"
27dependencies = [ 27dependencies = [
28 "rsdosedit", 28 "rsdosedit",
29 "rsnix", 29 "rsnix",
diff --git a/Cargo.toml b/Cargo.toml
index 273966b..9bf5854 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.0" 6version = "0.5.1"
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 9dedcb8..dad4505 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -4,7 +4,7 @@
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::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; 7use crate::uefi::{EFI_SUCCESS, GopModeInfo, GraphicsOutputProtocol, 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;
@@ -37,8 +37,93 @@ static mut FB: Option<Framebuffer> = None;
37static mut SHADOW: *mut u32 = core::ptr::null_mut(); 37static mut SHADOW: *mut u32 = core::ptr::null_mut();
38static mut SHADOW_WORDS: usize = 0; 38static mut SHADOW_WORDS: usize = 0;
39 39
40/// The GUI canvas. Every display surface assumes this resolution:
41/// `vt` derives its text grid from it, the compositor's cursor clamps
42/// to it, and the future window manager lays out windows on it. We
43/// force it through GOP `SetMode` at boot — QEMU's std-vga offers it,
44/// and real GPUs list it as a standard mode — falling back to the
45/// smallest mode that fits, then to whatever the firmware chose.
46const WANTED_W: usize = 800;
47const WANTED_H: usize = 600;
48
49/// Pick a GOP mode: prefer an exact 800×600 (BGR/RGB 32-bit), else the
50/// smallest mode that fits the canvas, else leave the firmware's choice
51/// alone. Call before reading the framebuffer — `SetMode` may move
52/// both the mode info pointer and the framebuffer base.
53fn select_gop_mode(gop: &mut GraphicsOutputProtocol) {
54 let mode = unsafe { &*gop.mode };
55 let cur = mode.mode;
56 let info = unsafe { &*mode.info };
57 let cur_w = info.horizontal_resolution as usize;
58 let cur_h = info.vertical_resolution as usize;
59 if cur_w == WANTED_W && cur_h == WANTED_H {
60 crate::serial_println!("gop: mode {} — already {}x{}", cur, cur_w, cur_h);
61 return;
62 }
63
64 let mut best: Option<(u32, usize, usize)> = None; // (mode#, w, h)
65 for i in 0..mode.max_mode {
66 let mut size: usize = 0;
67 let mut info_ptr: *mut GopModeInfo = core::ptr::null_mut();
68 let st = unsafe { (gop.query_mode)(gop, i, &mut size, &mut info_ptr) };
69 if st != EFI_SUCCESS || info_ptr.is_null() {
70 continue;
71 }
72 let mi = unsafe { &*info_ptr };
73 if mi.pixel_format != PIXEL_BGR && mi.pixel_format != PIXEL_RGB {
74 continue; // 32-bit BGR/RGB only (the driver assumes 4 Bpp)
75 }
76 let w = mi.horizontal_resolution as usize;
77 let h = mi.vertical_resolution as usize;
78 if w == WANTED_W && h == WANTED_H {
79 best = Some((i, w, h));
80 break;
81 }
82 if w >= WANTED_W && h >= WANTED_H {
83 match best {
84 Some((_, bw, bh)) if bw * bh <= w * h => {}
85 _ => best = Some((i, w, h)),
86 }
87 }
88 }
89
90 match best {
91 Some((n, w, h)) => {
92 let st = unsafe { (gop.set_mode)(gop, n) };
93 if st != EFI_SUCCESS {
94 crate::serial_println!(
95 "gop: SetMode({}) failed 0x{:x} — keeping current {}x{}",
96 n,
97 st,
98 cur_w,
99 cur_h
100 );
101 return;
102 }
103 crate::serial_println!(
104 "gop: mode {} selected — {}x{} (was {}x{})",
105 n,
106 w,
107 h,
108 cur_w,
109 cur_h
110 );
111 }
112 None => crate::serial_println!(
113 "gop: no mode ≥ {}x{} — keeping current {}x{}",
114 WANTED_W,
115 WANTED_H,
116 cur_w,
117 cur_h
118 ),
119 }
120}
121
40/// Initialise the framebuffer from GOP. Call *before* ExitBootServices. 122/// Initialise the framebuffer from GOP. Call *before* ExitBootServices.
41pub fn init(gop: &mut GraphicsOutputProtocol) { 123pub fn init(gop: &mut GraphicsOutputProtocol) {
124 // Force the 800×600 canvas first; SetMode may move the info
125 // pointer and framebuffer base, so everything is re-read after.
126 select_gop_mode(gop);
42 let mode = unsafe { &*gop.mode }; 127 let mode = unsafe { &*gop.mode };
43 let info = unsafe { &*mode.info }; 128 let info = unsafe { &*mode.info };
44 let base = mode.framebuffer_base as *mut u8; 129 let base = mode.framebuffer_base as *mut u8;
diff --git a/rsnix/src/uefi.rs b/rsnix/src/uefi.rs
index f947c90..c311ad9 100644
--- a/rsnix/src/uefi.rs
+++ b/rsnix/src/uefi.rs
@@ -321,12 +321,28 @@ pub struct GopMode {
321 321
322#[repr(C)] 322#[repr(C)]
323pub struct GraphicsOutputProtocol { 323pub struct GraphicsOutputProtocol {
324 pub query_mode: *mut c_void, 324 pub query_mode: EfiGopQueryMode,
325 pub set_mode: *mut c_void, 325 pub set_mode: EfiGopSetMode,
326 pub get_current_mode: *mut c_void, 326 pub get_current_mode: EfiGopGetMode,
327 pub mode: *mut GopMode, 327 pub mode: *mut GopMode,
328} 328}
329 329
330pub type EfiGopQueryMode = unsafe extern "efiapi" fn(
331 *mut GraphicsOutputProtocol,
332 u32, // mode number
333 *mut usize, // out: size of info
334 *mut *mut GopModeInfo, // out: info pointer (firmware-owned)
335) -> Status;
336
337pub type EfiGopSetMode = unsafe extern "efiapi" fn(*mut GraphicsOutputProtocol, u32) -> Status;
338
339pub type EfiGopGetMode = unsafe extern "efiapi" fn(
340 *mut GraphicsOutputProtocol,
341 *mut u32, // out: current mode
342 *mut u32, // out: size of info
343 *mut *mut GopModeInfo, // out: info pointer
344) -> Status;
345
330// --------------------------------------------------------------------------- 346// ---------------------------------------------------------------------------
331// System table 347// System table
332// --------------------------------------------------------------------------- 348// ---------------------------------------------------------------------------
diff --git a/scripts/test-gfx.sh b/scripts/test-gfx.sh
index a6e4016..7bee4ee 100755
--- a/scripts/test-gfx.sh
+++ b/scripts/test-gfx.sh
@@ -85,7 +85,7 @@ rm -f "$SOCK" "$LOG"
85echo $! >"$QPID" 85echo $! >"$QPID"
86 86
87wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" 87wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell"
88grep -aq "cursor: task " "$LOG" || fail "cursor task did not spawn" 88wait_log "$LOG" "cursor: task " 10 || fail "cursor task did not spawn"
89 89
90echo "==> 4/5 in-kernel pixel self-test (gfx check)" 90echo "==> 4/5 in-kernel pixel self-test (gfx check)"
91python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \ 91python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \
diff --git a/scripts/test-mouse.sh b/scripts/test-mouse.sh
index e58d474..ea58f91 100755
--- a/scripts/test-mouse.sh
+++ b/scripts/test-mouse.sh
@@ -95,7 +95,7 @@ echo $! >"$QPID"
95 95
96wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" 96wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell"
97grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot" 97grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot"
98grep -aq "cursor: task " "$LOG" || fail "cursor task did not spawn" 98wait_log "$LOG" "cursor: task " 10 || fail "cursor task did not spawn"
99 99
100echo "==> 4/6 cursor position tracks injected movement" 100echo "==> 4/6 cursor position tracks injected movement"
101INIT=$(cursor_state) 101INIT=$(cursor_state)
@@ -153,7 +153,7 @@ qemu_hmp "sendkey ctrl-c"
153grep -aq "mouse: routing events to task 3" "$LOG" || fail "mouse 3 did not arm routing" 153grep -aq "mouse: routing events to task 3" "$LOG" || fail "mouse 3 did not arm routing"
154grep -aq "msgrecv\[3\]: Mouse(+" "$LOG" || fail "task 3 queue did not receive the routed Mouse event" 154grep -aq "msgrecv\[3\]: Mouse(+" "$LOG" || fail "task 3 queue did not receive the routed Mouse event"
155# Watch line: x/y/buttons format (any occurrence after watching started). 155# Watch line: x/y/buttons format (any occurrence after watching started).
156grep -aq "mouse: x=$((X3 + 3)) y=$((Y3 + 2)) buttons=---" "$LOG" || fail "watch did not print the moved cursor (x=$((X3+3)) y=$((Y3+2)))" 156grep -aq "mouse: x=$((X3 + 3)) y=$((Y3 + 2)) buttons=---" "$LOG" || fail "watch did not print the moved cursor (x=$((X3 + 3)) y=$((Y3 + 2)))"
157grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse watch" 157grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse watch"
158 158
159python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "poweroff" 159python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "poweroff"