Files
RedBear-OS/drivers/graphics/fbcond/src/main.rs
T
Red Bear OS 54e89a337a 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.
2026-07-20 18:10:59 +09:00

303 lines
11 KiB
Rust

use event::EventQueue;
use inputd::ConsumerHandleEvent;
use libredox::errno::{EAGAIN, EINTR};
use orbclient::Event;
use redox_scheme::{
scheme::{Op, SchemeResponse, SchemeState, SchemeSync},
CallerCtx, RequestKind, Response, SignalBehavior, Socket,
};
use std::env;
use syscall::{EOPNOTSUPP, EVENT_READ};
use crate::scheme::{FbconScheme, Handle, VtIndex};
mod display;
mod keymap;
mod scheme;
mod text;
fn main() {
daemon::SchemeDaemon::new(daemon);
}
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
let vt_ids = env::args()
.skip(1)
.map(|arg| arg.parse().expect("invalid vt number"))
.collect::<Vec<_>>();
common::setup_logging(
"graphics",
"fbcond",
"fbcond",
common::output_level(),
common::file_level(),
);
let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue");
// FIXME listen for resize events from inputd and handle them
let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme");
event_queue
.subscribe(
socket.inner().raw(),
VtIndex::SCHEMA_SENTINEL,
event::EventFlags::READ,
)
.expect("fbcond: failed to subscribe to scheme events");
// Open the kernel debug (serial) scheme and subscribe to its readable
// events, so serial-line input can be fed into the console. Combined with
// the output mirror in TextScreen, this makes the framebuffer console fully
// usable over a serial line (headless login).
let serial_fd = libredox::call::open(
"/scheme/debug",
(libredox::flag::O_RDWR | libredox::flag::O_NONBLOCK) as _,
0,
)
.ok();
if let Some(fd) = serial_fd {
let _ = event_queue.subscribe(fd, VtIndex::SERIAL_SENTINEL, event::EventFlags::READ);
}
let mut state = SchemeState::new();
let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue);
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
// This is not possible for now as fbcond needs to open new displays at runtime for graphics
// driver handoff. In the future inputd may directly pass a handle to the display instead.
// libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace");
let mut blocked = Vec::new();
// Handle all events that could have happened before registering with the event queue.
handle_event(
&mut socket,
&mut scheme,
&mut state,
&mut blocked,
VtIndex::SCHEMA_SENTINEL,
);
for vt_i in scheme.vts.keys().copied().collect::<Vec<_>>() {
handle_event(&mut socket, &mut scheme, &mut state, &mut blocked, vt_i);
}
for event in event_queue {
let event = event.expect("fbcond: failed to read event from event queue");
// Pick up any display handoff whose (blocking) open has completed on its
// worker. Cheap and non-blocking; done per event so the swap lands as
// soon as the driver starts serving, without the console ever waiting on
// it. Output produced meanwhile is buffered and flushed on install.
for vt in scheme.vts.values_mut() {
vt.poll_pending_handoff();
}
if let Some(fd) = serial_fd {
if event.user_data == VtIndex::SERIAL_SENTINEL {
let mut buf = [0u8; 256];
loop {
match libredox::call::read(fd, &mut buf) {
Ok(0) => break,
Ok(n) => {
for vt in scheme.vts.values_mut() {
vt.push_input_bytes(&buf[..n]);
}
}
Err(err) if err.errno() == EAGAIN => break,
Err(_) => break,
}
}
// Run a scheme pass so blocked getty reads get their fevent
// notification now that input is available.
handle_event(
&mut socket,
&mut scheme,
&mut state,
&mut blocked,
VtIndex::SCHEMA_SENTINEL,
);
continue;
}
}
handle_event(
&mut socket,
&mut scheme,
&mut state,
&mut blocked,
event.user_data,
);
}
std::process::exit(0);
}
fn handle_event(
socket: &mut Socket,
scheme: &mut FbconScheme,
state: &mut SchemeState,
blocked: &mut Vec<(Op, CallerCtx)>,
event: VtIndex,
) {
match event {
VtIndex::SCHEMA_SENTINEL => loop {
let request = match socket.next_request(SignalBehavior::Restart) {
Ok(Some(request)) => request,
Ok(None) => {
// Scheme likely got unmounted
std::process::exit(0);
}
Err(err) if err.errno == EAGAIN => {
break;
}
Err(err) => panic!("fbcond: failed to read display scheme: {err}"),
};
match request.kind() {
RequestKind::Call(req) => {
let caller = req.caller();
let mut op = match req.op() {
Ok(op) => op,
Err(req) => {
let _ = socket
.write_response(
Response::err(EOPNOTSUPP, req),
SignalBehavior::Restart,
)
.expect("fbcond: failed to write responses to fbcon scheme");
continue;
}
};
match op.handle_sync_dont_consume(&caller, scheme, state) {
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
if libredox::error::Error::from(e).is_wouldblock()
&& !op.is_explicitly_nonblock() =>
{
blocked.push((op, caller));
}
SchemeResponse::Regular(r) => {
let _ = socket
.write_response(Response::new(r, op), SignalBehavior::Restart)
.expect("fbcond: failed to write responses to fbcon scheme");
}
SchemeResponse::Opened(o) => {
let _ = socket
.write_response(
Response::open_dup_like(o, op),
SignalBehavior::Restart,
)
.expect("fbcond: failed to write responses to fbcon scheme");
}
SchemeResponse::RegularAndNotifyOnDetach(status) => {
let _ = socket
.write_response(
Response::new_notify_on_detach(status, op),
SignalBehavior::Restart,
)
.expect("fbcond: failed to write scheme");
}
}
}
RequestKind::OnClose { id } => {
scheme.on_close(id);
}
RequestKind::Cancellation(cancellation_request) => {
if let Some(i) = blocked
.iter()
.position(|(_op, caller)| caller.id == cancellation_request.id)
{
let (blocked_req, _) = blocked.remove(i);
let resp = Response::err(EINTR, blocked_req);
socket
.write_response(resp, SignalBehavior::Restart)
.expect("vesad: failed to write display scheme");
}
}
_ => {}
}
},
vt_i => {
let vt = scheme.vts.get_mut(&vt_i).unwrap();
let mut events = [Event::new(); 16];
loop {
match vt
.display
.input_handle
.read_events(&mut events)
.expect("fbcond: Error while reading events")
{
ConsumerHandleEvent::Events(&[]) => break,
ConsumerHandleEvent::Events(events) => {
for event in events {
vt.input(event)
}
}
ConsumerHandleEvent::Handoff => vt.handle_handoff(),
}
}
}
}
// If there are blocked readers, try to handle them.
{
let mut i = 0;
while i < blocked.len() {
let (op, caller) = blocked
.get_mut(i)
.expect("vesad: Failed to get blocked request");
let resp = match op.handle_sync_dont_consume(&caller, scheme, state) {
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
if libredox::error::Error::from(e).is_wouldblock()
&& !op.is_explicitly_nonblock() =>
{
i += 1;
continue;
}
SchemeResponse::Regular(r) => {
let (op, _) = blocked.remove(i);
Response::new(r, op)
}
SchemeResponse::Opened(o) => {
let (op, _) = blocked.remove(i);
Response::open_dup_like(o, op)
}
SchemeResponse::RegularAndNotifyOnDetach(status) => {
let (op, _) = blocked.remove(i);
Response::new_notify_on_detach(status, op)
}
};
let _ = socket
.write_response(resp, SignalBehavior::Restart)
.expect("vesad: failed to write display scheme");
}
}
for (handle_id, handle) in scheme.handles.iter_mut() {
let handle = match handle {
Handle::SchemeRoot | Handle::Scrollback(_) => continue,
Handle::Vt(handle) => handle,
};
if !handle.events.contains(EVENT_READ) {
continue;
}
let can_read = scheme
.vts
.get(&handle.vt_i)
.map_or(false, |console| console.can_read());
if can_read {
if !handle.notified_read {
handle.notified_read = true;
let response = Response::post_fevent(*handle_id, EVENT_READ.bits());
socket
.write_response(response, SignalBehavior::Restart)
.expect("fbcond: failed to write display event");
}
} else {
handle.notified_read = false;
}
}
}