diff --git a/local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md b/local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md new file mode 100644 index 0000000000..982c0367df --- /dev/null +++ b/local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md @@ -0,0 +1,105 @@ +# Input Stack — Linux-Aligned Consolidation Plan + +**Status:** Design (implementation staged below). Goal: make the console-input path match the +Linux/CachyOS model — one keymap owner, one line discipline shared by console and pty — instead of +the current duplicated, two-discipline chain that loses typed characters (echoed but not executed). + +CachyOS's input/tty is stock upstream Linux (the `local/reference/cachyos/` tree is packaging + +kernel *performance* patches; the input subsystem is unmodified). So the reference model below is +`local/reference/linux-7.1/drivers/{input,tty}/`. + +## The Linux model (single owner per stage) + +| Stage | Linux file | Property | +|---|---|---| +| Raw input pipeline | `drivers/input/input.c` (`input_pass_values`) | one driver→core→handler pipeline | +| **Keymap → bytes** | `drivers/tty/vt/keyboard.c` (`put_queue`, `to_utf8`, `applkey`) | **applied ONCE**; emits the full byte stream incl. escape sequences for special keys | +| **Line discipline** | `drivers/tty/n_tty.c` (`read_buf` cooked, `echo_buf`, ICRNL, ISIG) | **ONE module**: canonical, echo, cooked buffer, CR/LF, signal keys | +| Console vs pty | `drivers/tty/pty.c` (`tty_port`, shared ldisc) | pty is a thin bridge that **reuses `n_tty`** — no second discipline | + +Key invariants Linux guarantees and we currently violate: +1. The keymap is applied **exactly once**. +2. CR/LF (`ICRNL`/`ONLCR`) is one `termios` policy in **one** place. +3. Canonical/echo/cooked lives in **one** buffer pair, and the **console tty and ptys share the + same line discipline**. "Echoed but not delivered" is structurally impossible because the reader + drains the very buffer the discipline cooked into. + +## Current RedBear path (what to fix) + +``` +ps2d ─▶ inputd ─▶ fbcond ─▶ getty(bridge) ─▶ ptyd ─▶ pty-slave ─▶ shell + keymap keymap+ byte pump FULL line discipline + (get_char) CR→LF (canonical/echo/cooked/CR→LF/signals) + + own input + queue +``` + +Confirmed wiring (`local/sources/userutils/src/bin/getty.rs`): +- getty opens `/scheme/fbcon/{vt}` (fbcond) **and** `/scheme/pty/ptmx` (ptyd), then byte-pumps + console↔pty; login/shell stdin = pty **slave**. + +Duplication: +- **Keymap twice**: inputd `active_keymap.get_char` (`drivers/inputd/src/main.rs:452`) **and** fbcond + `self.keymap.lookup` (`drivers/graphics/fbcond/src/text.rs:140`). +- **CR→LF twice**: fbcond (`text.rs:164`) **and** ptyd (`ICRNL`, `drivers/ptyd/src/pty.rs:76`). +- **Two disciplines**: fbcond has its own input queue/cooking; ptyd has the real one. The bridge + between them is where typed characters are lost while bare `\n` survives (the reported bug: + `tlc` echoes but never executes; manual Enters produce empty prompts). + +## Target (Linux-aligned) + +``` +ps2d ─▶ inputd ─▶ fbcond(display only) ─▶ getty(dumb pipe) ─▶ ptyd ─▶ slave ─▶ shell + keymap→ passthrough of the THE single line + BYTES already-decoded byte discipline (n_tty) + (incl. stream; renders output; + escapes) NO keymap, NO CR/LF, + NO input cooking +``` + +Rules: +1. **inputd owns the keymap** and emits the **full byte stream** for each key, including escape + sequences for special keys (arrows, F-keys, Home/End) — the `keyboard.c`/`applkey` role. Today it + only sets a single `character`; extend it to emit bytes. +2. **fbcond is display-only + a raw byte passthrough.** Remove `self.keymap.lookup` and the + `text.rs:164` CR→LF. It forwards inputd's decoded bytes verbatim into the console read stream and + renders output. It performs **no** cooking, echo, or CR/LF. +3. **ptyd is the sole line discipline** (the `n_tty`). Canonical/echo/cooked/CR→LF/signals stay here + and **only** here. This is already implemented (`pty.rs:41-249`); it just becomes the single owner. +4. **getty stays a dumb byte pipe** (no processing) — matches a terminal emulator feeding a pty. + +Result: keymap once (inputd), CR/LF once (ptyd `termios`), one cooked buffer (ptyd) that the shell +drains directly. Structurally identical to Linux → the "echoed but not delivered" class is gone. + +## Staged implementation (each stage builds + boots independently; validate on an idle host) + +1. **inputd: emit full byte sequences.** Add a keymap `get_bytes(scancode, mods) -> &[u8]` that + returns the char OR the escape sequence for special keys (model on `keyboard.c`). Deliver bytes on + the consumer stream (or keep events but carry the full byte payload). Keep behavior identical for + plain chars first; add escapes next. +2. **fbcond: drop keymap + CR/LF, become passthrough.** Remove `self.keymap.lookup` and the CR→LF at + `text.rs:164`; forward inputd's bytes verbatim. fbcond keeps only rendering + scrollback + the + serial mirror (which should also stop translating CR/LF — leave that to ptyd). +3. **Verify one discipline.** Confirm ptyd is the only place doing canonical/echo/cooked/CR→LF; delete + fbcond's residual input-cooking paths (`push_input_bytes` CR→LF at `pty.rs`-style sites in fbcond). +4. **Regression pass.** Console login on framebuffer AND serial: type a command, confirm execution; + test backspace, Ctrl-C (ISIG), arrows (escape sequences), and the serial mirror. + +Immediate stopgap (independent of this refactor, already partly done): the brush minimal backend +reads raw and accumulates a persistent line buffer; extend it to treat **both** `\r` and `\n` as +submit so the shell is robust even before the stack is consolidated. This is a shell-side band-aid, +not the fix — the fix is stages 1–3. + +## Validation + +- Framebuffer screendump (QMP) is ground truth. **One boot proves nothing** (input races); compare + across boots on an idle host — external load (opencode) contaminates timing. +- Success: type `echo RB=$((21*2))=OK` on the framebuffer console → `RB=42=OK` executes; backspace, + Ctrl-C, and arrows behave; serial-console login works the same way. + +## Risk / rollback + +- Boot-critical path; each stage is independently bootable and revertable (one component per stage). +- Do not land any stage validated only under external load. +- Build is currently blocked on `acpi-rs` (`aml/mod.rs:258` `connect_op_regions` undefined) — none of + this can be verified until that compiles.