From 6f96502fdfbb3698d0b5193f15c1810010a7e069 Mon Sep 17 00:00:00 2001 From: rajames Date: Mon, 26 Oct 2026 13:47:11 -0400 Subject: [PATCH] docs(v4): the stick, increment 1 plan: v4's screen and keyboard Approved by Captain Bob 2026-10-26. Co-Authored-By: Claude Opus 5.5 --- .../2026-10-26-stick-1-screen-and-keyboard.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md diff --git a/docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md b/docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md new file mode 100644 index 00000000..2df80e87 --- /dev/null +++ b/docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md @@ -0,0 +1,125 @@ +# The stick, increment 1: v4's screen and keyboard — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans (Captain Bob prefers native execution). Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** On the v4 path the screen is dark until Zuse logs in and is then the console, and the PC's +keyboards (PS/2, USB, and virtio on aarch64/riscv64) type to it as they do to v3's console. + +**Architecture:** v3's keyboard bridge (`repl.c`'s key tables, lock state and per-tick USB servicing) +moves into a HAL file both paths call, unchanged in behaviour. The console gains a switch for whether +text is mirrored to the framebuffer; the v4 boot brings the framebuffer up with the mirror off, turns it +on (and TTF, and the cursor) when Zuse logs in, and turns it off and clears the screen when she logs out. +The splash itself is increment 7; until then "splash only" is a dark screen. + +**Tech Stack:** freestanding C99 kernel (`kernel/`), QEMU (OVMF/AAVMF/OpenSBI), QMP (`send-key`, +`input-send-event`, `screendump`), Python 3 for the session harness. + +**Spec:** `docs/v4.0.0/STICK.md` §2, §5.3, §5.4, §9, §10, §11 increment 1. The identity gate: +`docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md` and its final review. + +## Global Constraints + +- Branch `StarForth-v4.0.0`, main checkout; no branch, no stash, never `--no-verify`. Commit with the + LFS filter off (`git -c filter.lfs.process= -c filter.lfs.clean=cat -c filter.lfs.smudge=cat -c + filter.lfs.required=false`); push with GitKraken's git-lfs on `PATH`. Messages end + `Co-Authored-By: Claude Opus 5.5 `. +- Strict C99, `-Wall -Wextra -Werror` (`.claude/CLAUDE.md` Code Standards). +- "Splash only, log on serial": before Zuse logs in, nothing the console prints reaches the screen. +- v3's behaviour does not change (STICK.md §10); shown by a `system=v3` boot typed to by keyboard. +- "QEMU and bare metal ALWAYS hold parity": the keyboards and display QEMU gives are the same drivers + a PC uses; no QEMU-only input path. +- Never apply a fix that is not in this plan; report it. +- Suite per task: `make -C v4 test test-32 sanitize hosted-check` (nothing in `v4/` changes; it must + still pass). Full acceptance on every kernel-affecting task (`.claude/CLAUDE.md`): three `clean qemu` + boots to `[zuse@Hera] ok> `, POST 538/538, equal `dict_hash` (`0xfec8c47cfc6f437f` unless a task + says otherwise), `make -C v4 test-32` 0 failures; logs committed. + +## Review Focus + +1. **Keys pressed on a keyboard while headless** are dropped, as serial bytes are (the gate's review + item 4): `sk_v4_identity_wait` discards keyboard events too. Pinned in Task 2 (`kbd_typeahead`). +2. **Logging out leaves nothing of the session on screen:** the screen is cleared when Zuse's session + ends. Pinned in Task 2 (`screen_dark_after_logout`). +3. **A PC with no framebuffer** (GOP gave none): v4 runs on serial alone and does not fault. Pinned in + Task 2 (`no_framebuffer`, QEMU with `-vga none` on amd64). +4. **Alt+Tab** toggles the graphics/text view on v4 as on v3 (the bridge's own interception moves with + it). Covered by Task 1's move being byte-for-byte; checked by eye in Task 2's screendumps. +5. **v3's console** still takes keys from every keyboard: Task 1's yardstick, a `system=v3` boot typed + to with `send-key`. + +--- + +### Task 1: The keyboard bridge, shared + +**Files:** +- Create: `kernel/src/hal/kbd_input.c`, `kernel/include/starkernel/kbd_input.h` +- Modify: `kernel/src/repl.c` (remove the moved code; `sk_console_getc_raw` calls the new function) +- Create: `scripts/qemu_session.py` (the session harness, kept in the repo from here on) + +**Interfaces:** +- Produces: `int sk_input_getc(void);` — one raw byte from the serial line or a keyboard + (PS/2, USB boot keyboard, virtio), non-blocking, -1 when none; services the xHCI event ring once + per timer tick while a USB keyboard runs; Alt+Tab toggles the graphics view and returns nothing. + Exactly `repl.c`'s `sk_console_getc_raw()` today, with `sk_kbd_getc()`, its tables, the + Shift/Alt/Caps Lock/Num Lock state and `sk_kbd_show_locks()` moved with it. +- Produces: `void sk_input_drop(void);` — read and drop every pending byte and key event (for + Task 2's headless wait). +- Produces: `scripts/qemu_session.py MODE ARCH START_EPOCH [ARGS]` — today's scratch `gate_test.py` + (modes `headless`, `zuse`, `hotplug`, `midpull`, `waitpull`, `typeahead`, `emergency`) plus the + modes added below; it prints `PASS:`/`FAIL:` lines and `RESULT: PASS|FAIL`, and quits QEMU over QMP. + +- [ ] **Step 1: Test (the yardstick, before):** add mode `v3_keys` to the harness: boot + `system=v3` with the default disks, wait for `[zuse@Hera] ok> `, type `3 4 + .` with QMP `send-key` + (one `send-key` per character, `qcode`s from QEMU's list; Enter `ret`), and check `7 ok` follows. + Run on amd64 (PS/2) and riscv64 (virtio keyboard). Expected: PASS both (this is the reference). +- [ ] **Step 2:** Move the code; `repl.c` keeps `g_console_pending_key` and calls `sk_input_getc()`. +- [ ] **Step 3:** `v3_keys` again on amd64 and riscv64. Expected: PASS, the same lines. +- [ ] **Step 4:** Suite; commit `refactor(kernel): the keyboard bridge, shared` with the four logs + and `scripts/qemu_session.py`. + +### Task 2: v4's screen and keyboard + +**Files:** +- Modify: `kernel/src/hal/console.c`, `kernel/include/starkernel/console.h` +- Modify: `kernel/src/v4/sk_v4.c` (the line editor reads `sk_input_getc()`; cursor), + `kernel/src/v4/sk_v4_identity.c` (framebuffer and keyboards brought up; mirror on at login, off + and cleared at logout; `sk_input_drop()` in the wait), `kernel/src/kernel_main.c` (the v4 branch + passes the framebuffer: `sk_v4_boot_info` already holds it) + +**Interfaces:** +- Consumes: Task 1's `sk_input_getc()`, `sk_input_drop()`. +- Produces: `void console_fb_mirror(int on);` — whether `console_putc` also draws on the + framebuffer; on by default, so v3 is unchanged. `void console_fb_clear(void);` — erase the + screen and home the cursor (`vt100_puts("\x1b[2J\x1b[H")` when the framebuffer is up). +- Bring-up order on the v4 path (in `sk_v4_identity_boot`, before xHCI): `console_fb_init` with the + pixel format chosen as `kernel_main.c`'s v3 path does, then `console_fb_mirror(0)`; + `virtio_input_find_keyboard()` (as the v3 path does; a no-op where there is none). +- At login (where `sk_v4_drive_arrived` starts her session): `console_fb_mirror(1)`, + `console_fb_enable_ttf()`. At logout (`sk_v4_drive_left`): `console_fb_mirror(0)`, + `console_fb_clear()`. +- The v4 line editor: `sk_input_getc()` in place of `console_getc()`; the cursor shown at the prompt + and after each echo or rub-out (`console_fb_draw_cursor`), and blinking every 50 ticks while idle, + as `sk_console_readline` does. + +- [ ] **Step 1: Failing tests** (harness modes; amd64 unless named): + - `screen_dark_until_login`: no Zuse drive; 30 s after `POST: PASSED`, QMP `screendump` (PPM); + every pixel black. Today: FAIL (the loader's text is still on screen). + - `screen_console_after_login`: hot-add Zuse; after `[zuse@Hera] ok> `, a screendump has at least + 500 non-black pixels; type `3 4 + .` by `send-key`; `7 ok` on serial and a later screendump + differs from the first. Today: FAIL. + - `screen_dark_after_logout`: then `device_del` her drive; after "Zuse: session ended", a + screendump is all black. Today: FAIL. + - `kbd_typeahead`: no drive; `send-key` `3 4 + .` while headless; hot-add Zuse; no `7 ok`; + then `1 2 + .` by `send-key` gives `3 ok`. Today: FAIL (keys are not read at all). + - `usb_kbd`: `QEMU_EXTRA="-device usb-kbd,bus=xhci0.0,port=3,id=ukbd"`; after login, type + `3 4 + .` with `input-send-event` (`device: ukbd`); `7 ok`. Today: FAIL. + - `no_framebuffer`: amd64 with `-vga none`; the default disks; `[zuse@Hera] ok> ` and `3 4 + .` + over serial gives `7 ok`. Today: PASS (keep it passing). + - `virtio_kbd`: riscv64 and aarch64, after login, `send-key` `3 4 + .` gives `7 ok`. Today: FAIL. +- [ ] **Step 2:** Run each; record the failures. +- [ ] **Step 3:** Build Task 2 as above. +- [ ] **Step 4:** Every mode passes; the gate's sessions still pass (`headless`, `zuse`, `hotplug`, + `midpull`, `waitpull`, `typeahead`, `emergency`); `v3_keys` still passes. +- [ ] **Step 5:** Suite; full acceptance; commit `feat(v4): the screen and the keyboards` and the logs. + Record in the commit message whether `dict_hash` changed (it should not: nothing on the node + changes).