diff options
Diffstat (limited to '')
| -rw-r--r-- | CONTEXT.md | 16 | ||||
| -rw-r--r-- | Cargo.lock | 6 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | rsnix/src/gop.rs | 87 | ||||
| -rw-r--r-- | rsnix/src/uefi.rs | 22 | ||||
| -rwxr-xr-x | scripts/test-gfx.sh | 2 | ||||
| -rwxr-xr-x | scripts/test-mouse.sh | 4 |
7 files changed, 124 insertions, 15 deletions
| @@ -113,10 +113,10 @@ Workspace version is `a.b.c` (single version for all crates): | |||
| 113 | 113 | ||
| 114 | Bump the workspace version in the root `Cargo.toml` when a change of | 114 | Bump the workspace version in the root `Cargo.toml` when a change of |
| 115 | that class lands — do not leave the version to drift. Current: | 115 | that 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 | 117 | compositor) 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 | 118 | future major GUI overhaul (graphics windows beyond the text surface) |
| 119 | (1.0.0). | 119 | would 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 |
| @@ -4,7 +4,7 @@ version = 4 | |||
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "rsdos" | 6 | name = "rsdos" |
| 7 | version = "0.5.0" | 7 | version = "0.5.1" |
| 8 | dependencies = [ | 8 | dependencies = [ |
| 9 | "rsnix", | 9 | "rsnix", |
| 10 | "shell", | 10 | "shell", |
| @@ -19,11 +19,11 @@ dependencies = [ | |||
| 19 | 19 | ||
| 20 | [[package]] | 20 | [[package]] |
| 21 | name = "rsnix" | 21 | name = "rsnix" |
| 22 | version = "0.5.0" | 22 | version = "0.5.1" |
| 23 | 23 | ||
| 24 | [[package]] | 24 | [[package]] |
| 25 | name = "shell" | 25 | name = "shell" |
| 26 | version = "0.5.0" | 26 | version = "0.5.1" |
| 27 | dependencies = [ | 27 | dependencies = [ |
| 28 | "rsdosedit", | 28 | "rsdosedit", |
| 29 | "rsnix", | 29 | "rsnix", |
| @@ -3,7 +3,7 @@ resolver = "2" | |||
| 3 | members = ["rsnix", "shell", "rsdos", "rsdosedit"] | 3 | members = ["rsnix", "shell", "rsdos", "rsdosedit"] |
| 4 | 4 | ||
| 5 | [workspace.package] | 5 | [workspace.package] |
| 6 | version = "0.5.0" | 6 | version = "0.5.1" |
| 7 | edition = "2024" | 7 | edition = "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. |
| 6 | use crate::font; | 6 | use crate::font; |
| 7 | use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; | 7 | use crate::uefi::{EFI_SUCCESS, GopModeInfo, GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; |
| 8 | use alloc::boxed::Box; | 8 | use alloc::boxed::Box; |
| 9 | use alloc::vec; | 9 | use alloc::vec; |
| 10 | use alloc::vec::Vec; | 10 | use alloc::vec::Vec; |
| @@ -37,8 +37,93 @@ static mut FB: Option<Framebuffer> = None; | |||
| 37 | static mut SHADOW: *mut u32 = core::ptr::null_mut(); | 37 | static mut SHADOW: *mut u32 = core::ptr::null_mut(); |
| 38 | static mut SHADOW_WORDS: usize = 0; | 38 | static 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. | ||
| 46 | const WANTED_W: usize = 800; | ||
| 47 | const 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. | ||
| 53 | fn 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. |
| 41 | pub fn init(gop: &mut GraphicsOutputProtocol) { | 123 | pub 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)] |
| 323 | pub struct GraphicsOutputProtocol { | 323 | pub 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 | ||
| 330 | pub 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 | |||
| 337 | pub type EfiGopSetMode = unsafe extern "efiapi" fn(*mut GraphicsOutputProtocol, u32) -> Status; | ||
| 338 | |||
| 339 | pub 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" | |||
| 85 | echo $! >"$QPID" | 85 | echo $! >"$QPID" |
| 86 | 86 | ||
| 87 | wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" | 87 | wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" |
| 88 | grep -aq "cursor: task " "$LOG" || fail "cursor task did not spawn" | 88 | wait_log "$LOG" "cursor: task " 10 || fail "cursor task did not spawn" |
| 89 | 89 | ||
| 90 | echo "==> 4/5 in-kernel pixel self-test (gfx check)" | 90 | echo "==> 4/5 in-kernel pixel self-test (gfx check)" |
| 91 | python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \ | 91 | python3 "$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 | ||
| 96 | wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" | 96 | wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" |
| 97 | grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot" | 97 | grep -aq "ps2: mouse ready" "$LOG" || fail "mouse driver did not come up at boot" |
| 98 | grep -aq "cursor: task " "$LOG" || fail "cursor task did not spawn" | 98 | wait_log "$LOG" "cursor: task " 10 || fail "cursor task did not spawn" |
| 99 | 99 | ||
| 100 | echo "==> 4/6 cursor position tracks injected movement" | 100 | echo "==> 4/6 cursor position tracks injected movement" |
| 101 | INIT=$(cursor_state) | 101 | INIT=$(cursor_state) |
| @@ -153,7 +153,7 @@ qemu_hmp "sendkey ctrl-c" | |||
| 153 | grep -aq "mouse: routing events to task 3" "$LOG" || fail "mouse 3 did not arm routing" | 153 | grep -aq "mouse: routing events to task 3" "$LOG" || fail "mouse 3 did not arm routing" |
| 154 | grep -aq "msgrecv\[3\]: Mouse(+" "$LOG" || fail "task 3 queue did not receive the routed Mouse event" | 154 | grep -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). |
| 156 | grep -aq "mouse: x=$((X3 + 3)) y=$((Y3 + 2)) buttons=---" "$LOG" || fail "watch did not print the moved cursor (x=$((X3+3)) y=$((Y3+2)))" | 156 | grep -aq "mouse: x=$((X3 + 3)) y=$((Y3 + 2)) buttons=---" "$LOG" || fail "watch did not print the moved cursor (x=$((X3 + 3)) y=$((Y3 + 2)))" |
| 157 | grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse watch" | 157 | grep -aq "mouse: stopped" "$LOG" || fail "^C did not stop the mouse watch" |
| 158 | 158 | ||
| 159 | python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "poweroff" | 159 | python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" "poweroff" |
