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:
@@ -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) })
|
||||
|
||||
Reference in New Issue
Block a user