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).
6.3 KiB
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:
- The keymap is applied exactly once.
- CR/LF (
ICRNL/ONLCR) is onetermiospolicy in one place. - 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 fbcondself.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
\nsurvives (the reported bug:tlcechoes 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:
- 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/applkeyrole. Today it only sets a singlecharacter; extend it to emit bytes. - fbcond is display-only + a raw byte passthrough. Remove
self.keymap.lookupand thetext.rs:164CR→LF. It forwards inputd's decoded bytes verbatim into the console read stream and renders output. It performs no cooking, echo, or CR/LF. - 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. - 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)
- 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 onkeyboard.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. - fbcond: drop keymap + CR/LF, become passthrough. Remove
self.keymap.lookupand the CR→LF attext.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). - Verify one discipline. Confirm ptyd is the only place doing canonical/echo/cooked/CR→LF; delete
fbcond's residual input-cooking paths (
push_input_bytesCR→LF atpty.rs-style sites in fbcond). - 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))=OKon the framebuffer console →RB=42=OKexecutes; 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:258connect_op_regionsundefined) — none of this can be verified until that compiles.