Files
RedBear-OS/local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md
T
vasilito 78aca1bb45 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).
2026-07-24 11:04:01 +09:00

6.3 KiB
Raw Blame History

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 13.

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.