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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a54f9ef59
commit
6f96502fdf
@@ -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 <noreply@anthropic.com>`.
|
||||||
|
- 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).
|
||||||
Reference in New Issue
Block a user