fbcond blocked indefinitely in open_display_v2() while handing the console
over to the real graphics driver: boot stopped at "fbcond: Performing
handoff" and never reached the "Opened new display" that follows a
successful open.
fbcond is single-threaded and owns the console, so while it waited there it
stopped draining console writers. getty, login and the login shell all
blocked on their writes, which made the failure look like a slow or hung
shell -- the stall point just tracked whichever process next wrote to the
console. If the graphics driver was itself waiting on the console, the two
deadlocked outright and the console never came back.
The wait comes from the scheme-open handshake: inputd signals the handoff as
soon as the driver registers its path, but the driver may not be serving its
scheme yet, and O_NONBLOCK does not bound the open (it governs later reads).
Split the two halves in inputd's ConsumerHandle: display_path_v2() only does
an fpath on our own handle and returns promptly, while open_display_path_v2()
carries the blocking open. fbcond now resolves the path inline and hands the
open to a worker thread, then picks the result up from the event loop via
poll_pending_handoff(). Console output produced meanwhile is buffered in
pending_writes (already the behaviour while the display is unmapped) and
flushed once the new display is mapped.
The handoff is one-shot: inputd signals it the moment the real display
driver registers its path, but the driver's scheme may not serve the v2
open / capability query for a few more milliseconds. Without a retry, a
transient not-ready driver leaves the framebuffer permanently blank —
the login prompt is drawn to the framebuffer VT, so the console appears
dead even though boot continues on the serial mirror (observed as the
2026-07-20 '-vga std' boot freeze at 'Performing handoff').
Retry the reopen for up to 250 x 10ms; the driver becomes ready well
within this bound in practice. On success after retries, log the retry
count at info level. If the driver is still not ready after the bound,
emit a single explicit warning instead of per-attempt warn spam (the
per-attempt message in display.rs is demoted to debug). Serial mirror
remains unaffected in the failure case.
The keyboard input path pushed the raw character bytes, so the Enter key
(a bare CR on the keymap) never produced an LF. Line-oriented readers on
the console (e.g. a shell's read_line) then never saw a completed line and
appeared to hang. Convert CR->LF here, matching the serial input path
(push_input_bytes).
fbcond now mirrors all framebuffer-console output to the kernel debug (serial)
scheme AND feeds serial input back into the active VT's input queue. Previously,
once fbcond took over the console after display handoff the serial line went
silent (headless operators saw the boot stop at 'Performing handoff' and never
saw the getty login prompt, which draws only to the framebuffer VT). Now the
same console — boot log, login prompt, and shell — is fully usable over serial,
reusing the working framebuffer getty for both surfaces.
Re-adds the archived-but-unapplied scrollback support: a 1000-line ring
buffer in TextScreen capturing all output (incl. pre-handoff boot log), a
Handle::Scrollback variant opened via /scheme/fbcon/<vt>/scrollback, and a
read path returning the retained buffer. Adapted to the current single-step
openat scheme protocol.