54e89a337a
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.
303 lines
11 KiB
Rust
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;
|
|
}
|
|
}
|
|
}
|