fbcond: perform the display handoff open off the console loop

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.
This commit is contained in:
Red Bear OS
2026-07-20 18:10:59 +09:00
parent ad40fffd80
commit 54e89a337a
4 changed files with 138 additions and 19 deletions
+23
View File
@@ -78,6 +78,17 @@ impl ConsumerHandle {
}
pub fn open_display_v2(&self) -> io::Result<File> {
let display_path_str = self.display_path_v2()?;
Self::open_display_path_v2(&display_path_str)
}
/// Resolve the v2 display path for this consumer handle.
///
/// This only talks to inputd (an `fpath` on our own handle), so it returns
/// promptly. Separated from the open below so that a caller with a
/// single-threaded event loop can resolve the path inline and perform the
/// blocking open elsewhere.
pub fn display_path_v2(&self) -> io::Result<String> {
let mut buffer = [0; 1024];
let fd = self.0.as_raw_fd();
let written = libredox::call::fpath(fd as usize, &mut buffer)?;
@@ -120,6 +131,18 @@ impl ConsumerHandle {
)
})?;
Ok(display_path_str.to_owned())
}
/// Open a v2 display path previously resolved by [`Self::display_path_v2`].
///
/// NOTE: this can block indefinitely. `O_NONBLOCK` governs later reads, not
/// the scheme-open handshake, so if the graphics driver has registered its
/// path but is not yet serving its scheme, the open waits for it. Callers
/// that own a single-threaded event loop (fbcond owns the console) must not
/// call this inline, or they stop servicing their own clients while it
/// waits — which deadlocks if the driver is in turn waiting on them.
pub fn open_display_path_v2(display_path_str: &str) -> io::Result<File> {
let display_file =
libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
.map(|socket| unsafe { File::from_raw_fd(socket as RawFd) })