78aca1bb45
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).
106 lines
6.3 KiB
Markdown
106 lines
6.3 KiB
Markdown
# 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.
|