docs: Linux-aligned input-stack consolidation plan
Cross-referenced the console-input path against Linux/CachyOS
(local/reference/linux-7.1/drivers/{input,tty}/). Linux applies the
keymap once (vt/keyboard.c) and runs one line discipline (n_tty.c)
shared by the console tty and ptys; RedBear duplicates the keymap
(inputd + fbcond) and CR/LF (fbcond + ptyd) and runs two disciplines
bridged by getty — which is where typed characters are lost (echoed but
not executed). Plan: inputd owns the keymap and emits the full byte
stream (incl. escape sequences); fbcond becomes display-only passthrough
(no keymap, no CR/LF, no cooking); ptyd is the sole line discipline.
Staged, each stage independently bootable; validation needs the build
green (acpi-rs currently blocks it).
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user