From 4e2f1a730caefd67e3f2dbaacb5ad6782132d14f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:05:26 +0100 Subject: [PATCH 1/2] graphics: Introduce a separate daemon for the boot log The daemon responsible for the boot log must never ever block to avoid deadlocks, but doing so while still accepting keyboard input is non-trivial. This commit splits the boot log out from fbcond into a separate daemon to make this a lot easier to implement. This will also allow making fbcond blocking again, which will simplify some things. --- Cargo.lock | 25 ++++ Cargo.toml | 2 + graphics/bgad/src/scheme.rs | 2 +- graphics/console-draw/Cargo.toml | 13 ++ graphics/console-draw/src/lib.rs | 230 +++++++++++++++++++++++++++++ graphics/fbbootlogd/Cargo.toml | 19 +++ graphics/fbbootlogd/src/display.rs | 202 +++++++++++++++++++++++++ graphics/fbbootlogd/src/main.rs | 68 +++++++++ graphics/fbbootlogd/src/scheme.rs | 124 ++++++++++++++++ graphics/fbbootlogd/src/text.rs | 41 +++++ graphics/fbcond/Cargo.toml | 1 + graphics/fbcond/src/main.rs | 4 - graphics/fbcond/src/text.rs | 223 +++------------------------- 13 files changed, 744 insertions(+), 210 deletions(-) create mode 100644 graphics/console-draw/Cargo.toml create mode 100644 graphics/console-draw/src/lib.rs create mode 100644 graphics/fbbootlogd/Cargo.toml create mode 100644 graphics/fbbootlogd/src/display.rs create mode 100644 graphics/fbbootlogd/src/main.rs create mode 100644 graphics/fbbootlogd/src/scheme.rs create mode 100644 graphics/fbbootlogd/src/text.rs diff --git a/Cargo.lock b/Cargo.lock index fc46dcb053..3bd00c9a93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,6 +322,15 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "console-draw" +version = "0.1.0" +dependencies = [ + "inputd", + "orbclient", + "ransid", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -415,10 +424,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fbbootlogd" +version = "0.1.0" +dependencies = [ + "console-draw", + "inputd", + "libredox", + "orbclient", + "ransid", + "redox-daemon", + "redox-scheme", + "redox_event", + "redox_syscall", +] + [[package]] name = "fbcond" version = "0.1.0" dependencies = [ + "console-draw", "inputd", "libredox", "orbclient", diff --git a/Cargo.toml b/Cargo.toml index a8d2519edb..2181fb2841 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ members = [ "audio/sb16d", "graphics/bgad", + "graphics/console-draw", + "graphics/fbbootlogd", "graphics/fbcond", "graphics/vesad", "graphics/virtio-gpud", diff --git a/graphics/bgad/src/scheme.rs b/graphics/bgad/src/scheme.rs index 197f0ec01d..0d536e4e9d 100644 --- a/graphics/bgad/src/scheme.rs +++ b/graphics/bgad/src/scheme.rs @@ -1,5 +1,5 @@ -use std::str; use inputd::ProducerHandle; +use std::str; use syscall::data::Stat; use syscall::{Error, Result, SchemeMut, EACCES, EINVAL, MODE_CHR}; diff --git a/graphics/console-draw/Cargo.toml b/graphics/console-draw/Cargo.toml new file mode 100644 index 0000000000..56d8d98562 --- /dev/null +++ b/graphics/console-draw/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "console-draw" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" + +inputd = { path = "../../inputd" } + +[features] +default = [] diff --git a/graphics/console-draw/src/lib.rs b/graphics/console-draw/src/lib.rs new file mode 100644 index 0000000000..d52e46c57f --- /dev/null +++ b/graphics/console-draw/src/lib.rs @@ -0,0 +1,230 @@ +extern crate ransid; + +use std::collections::{BTreeSet, VecDeque}; +use std::convert::{TryFrom, TryInto}; +use std::{cmp, ptr}; + +use inputd::Damage; +use orbclient::FONT; + +pub struct DisplayMap { + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +pub struct TextScreen { + console: ransid::Console, + changed: BTreeSet, +} + +impl TextScreen { + pub fn new() -> TextScreen { + TextScreen { + // Width and height will be filled in on the next write to the console + console: ransid::Console::new(0, 0), + changed: BTreeSet::new(), + } + } + + /// Draw a rectangle + fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + for i in 0..len { + unsafe { + *(offscreen_ptr as *mut u32).add(i) = color; + } + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Invert a rectangle + fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + let mut row_ptr = offscreen_ptr; + let mut cols = len; + while cols > 0 { + unsafe { + let color = *(row_ptr as *mut u32); + *(row_ptr as *mut u32) = !color; + } + row_ptr += 4; + cols -= 1; + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Draw a character + fn char( + map: &mut DisplayMap, + x: usize, + y: usize, + character: char, + color: u32, + _bold: bool, + _italic: bool, + ) { + if x + 8 <= map.width && y + 16 <= map.height { + let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; + + let font_i = 16 * (character as usize); + if font_i + 16 <= FONT.len() { + for row in 0..16 { + let row_data = FONT[font_i + row]; + for col in 0..8 { + if (row_data >> (7 - col)) & 1 == 1 { + unsafe { + *((dst + col * 4) as *mut u32) = color; + } + } + } + dst += map.width * 4; + } + } + } + } +} + +impl TextScreen { + pub fn write( + &mut self, + map: &mut DisplayMap, + buf: &[u8], + input: &mut VecDeque, + ) -> Vec { + self.console.state.w = map.width / 8; + self.console.state.h = map.height / 16; + self.console.state.bottom_margin = (map.height / 16).saturating_sub(1); + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + { + let changed = &mut self.changed; + self.console.write(buf, |event| match event { + ransid::Event::Char { + x, + y, + c, + color, + bold, + .. + } => { + Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false); + changed.insert(y); + } + ransid::Event::Input { data } => input.extend(data), + ransid::Event::Rect { x, y, w, h, color } => { + Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); + for y2 in y..y + h { + changed.insert(y2); + } + } + ransid::Event::ScreenBuffer { .. } => (), + ransid::Event::Move { + from_x, + from_y, + to_x, + to_y, + w, + h, + } => { + let width = map.width; + let pixels = unsafe { &mut *map.offscreen }; + + for raw_y in 0..h { + let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; + + for pixel_y in 0..16 { + { + let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; + let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; + let len = w * 8; + + if off_from + len <= pixels.len() && off_to + len <= pixels.len() { + unsafe { + let data_ptr = pixels.as_mut_ptr() as *mut u32; + ptr::copy( + data_ptr.offset(off_from as isize), + data_ptr.offset(off_to as isize), + len, + ); + } + } + } + } + + changed.insert(to_y + y); + } + } + ransid::Event::Resize { .. } => (), + ransid::Event::Title { .. } => (), + }); + } + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + let width = map.width.try_into().unwrap(); + let damage = self + .changed + .iter() + .map(|&change| Damage { + x: 0, + y: i32::try_from(change).unwrap() * 16, + width, + height: 16, + }) + .collect(); + + self.changed.clear(); + + damage + } +} diff --git a/graphics/fbbootlogd/Cargo.toml b/graphics/fbbootlogd/Cargo.toml new file mode 100644 index 0000000000..90862d2780 --- /dev/null +++ b/graphics/fbbootlogd/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "fbbootlogd" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" +redox_event = "0.4" +redox_syscall = "0.5" +redox-daemon = "0.1" +redox-scheme = { git = "https://gitlab.redox-os.org/redox-os/redox-scheme.git" } + +console-draw = { path = "../console-draw" } +inputd = { path = "../../inputd" } +libredox = "0.1.3" + +[features] +default = [] diff --git a/graphics/fbbootlogd/src/display.rs b/graphics/fbbootlogd/src/display.rs new file mode 100644 index 0000000000..ad3b39d4cb --- /dev/null +++ b/graphics/fbbootlogd/src/display.rs @@ -0,0 +1,202 @@ +use event::{user_data, EventQueue}; +use inputd::{ConsumerHandle, Damage}; +use libredox::errno::ESTALE; +use libredox::flag; +use orbclient::Event; +use std::mem; +use std::os::fd::BorrowedFd; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::{Arc, Mutex}; +use std::{fs::File, io, os::unix::io::AsRawFd, slice}; + +fn read_to_slice( + file: BorrowedFd, + buf: &mut [T], +) -> Result { + unsafe { + libredox::call::read( + file.as_raw_fd() as usize, + slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * mem::size_of::()), + ) + .map(|count| count / mem::size_of::()) + } +} + +fn display_fd_map(width: usize, height: usize, display_file: File) -> syscall::Result { + unsafe { + let display_ptr = libredox::call::mmap(libredox::call::MmapArgs { + fd: display_file.as_raw_fd() as usize, + offset: 0, + length: (width * height * 4), + prot: flag::PROT_READ | flag::PROT_WRITE, + flags: flag::MAP_SHARED, + addr: core::ptr::null_mut(), + })?; + let display_slice = slice::from_raw_parts_mut(display_ptr as *mut u32, width * height); + Ok(DisplayMap { + display_file: Arc::new(display_file), + offscreen: display_slice, + width, + height, + }) + } +} + +unsafe fn display_fd_unmap(image: *mut [u32]) { + let _ = libredox::call::munmap(image as *mut (), image.len()); +} + +pub struct DisplayMap { + display_file: Arc, + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +unsafe impl Send for DisplayMap {} +unsafe impl Sync for DisplayMap {} + +impl Drop for DisplayMap { + fn drop(&mut self) { + unsafe { + display_fd_unmap(self.offscreen); + } + } +} + +enum DisplayCommand { + SyncRects(Vec), +} + +pub struct Display { + cmd_tx: Sender, + pub map: Arc>, +} + +impl Display { + pub fn open_first_vt() -> io::Result { + let input_handle = ConsumerHandle::for_vt(1)?; + + let (display_file, width, height) = Self::open_display(&input_handle)?; + + let map = Arc::new(Mutex::new( + display_fd_map(width, height, display_file) + .unwrap_or_else(|e| panic!("failed to map display: {e}")), + )); + + let map_clone = map.clone(); + std::thread::spawn(move || { + Self::handle_input_events(map_clone, input_handle); + }); + + let (cmd_tx, cmd_rx) = mpsc::channel(); + let map_clone = map.clone(); + std::thread::spawn(move || { + Self::handle_sync_rect(map_clone, cmd_rx); + }); + + Ok(Self { cmd_tx, map }) + } + + fn open_display(input_handle: &ConsumerHandle) -> io::Result<(File, usize, usize)> { + let display_file = input_handle.open_display()?; + + let mut buf: [u8; 4096] = [0; 4096]; + let count = libredox::call::fpath(display_file.as_raw_fd() as usize, &mut buf) + .unwrap_or_else(|e| { + panic!("Could not read display path with fpath(): {e}"); + }); + + let url = + String::from_utf8(Vec::from(&buf[..count])).expect("Could not create Utf8 Url String"); + let path = url.split(':').nth(1).expect("Could not get path from url"); + + let mut path_parts = path.split('/').skip(1); + let width = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + let height = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + + Ok((display_file, width, height)) + } + + fn handle_input_events(map: Arc>, input_handle: ConsumerHandle) { + let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); + + user_data! { + enum Source { + Input, + } + } + + // FIXME listen for resize events from inputd and handle them + + event_queue + .subscribe( + input_handle.inner().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) + .expect("fbbootlogd: failed to subscribe to scheme events"); + + let mut events = [Event::new(); 16]; + for Source::Input in event_queue.map(|event| event.unwrap().user_data) { + match read_to_slice(input_handle.inner(), &mut events) { + Err(err) if err.errno() == ESTALE => { + eprintln!("fbbootlogd: handoff requested"); + + let (new_display_file, width, height) = + Self::open_display(&input_handle).unwrap(); + + match display_fd_map(width, height, new_display_file) { + Ok(ok) => { + *map.lock().unwrap() = ok; + + eprintln!("fbbootlogd: handoff finished"); + } + Err(err) => { + eprintln!("fbbootlogd: failed to open display: {}", err); + } + } + } + + Ok(_count) => {} + Err(err) => { + panic!("fbbootlogd: error while reading events: {err}"); + } + } + } + } + + fn handle_sync_rect(map: Arc>, cmd_rx: Receiver) { + while let Ok(cmd) = cmd_rx.recv() { + match cmd { + DisplayCommand::SyncRects(sync_rects) => unsafe { + // We may not hold this lock across the write call to avoid deadlocking if the + // graphics driver tries to write to the bootlog. + let display_file = map.lock().unwrap().display_file.clone(); + libredox::call::write( + display_file.as_raw_fd() as usize, + slice::from_raw_parts( + sync_rects.as_ptr() as *const u8, + sync_rects.len() * mem::size_of::(), + ), + ) + .unwrap(); + }, + } + } + } + + pub fn sync_rects(&mut self, sync_rects: Vec) { + self.cmd_tx + .send(DisplayCommand::SyncRects(sync_rects)) + .unwrap(); + } +} diff --git a/graphics/fbbootlogd/src/main.rs b/graphics/fbbootlogd/src/main.rs new file mode 100644 index 0000000000..a45601820d --- /dev/null +++ b/graphics/fbbootlogd/src/main.rs @@ -0,0 +1,68 @@ +//! Fbbootlogd renders the boot log and presents it on VT1. +//! +//! While fbbootlogd is superficially similar to fbcond, there are two major differences: +//! +//! * Fbbootlogd doesn't accept input coming from the keyboard. It only allows getting written to. +//! * Writing to fbbootlogd will never block. Not even on the graphics driver or inputd. This makes +//! it safe for graphics drivers and inputd to write to the boot log without risking deadlocks. +//! Fbcond will block on the graphics driver during handoff and will continously block on inputd +//! to get new input. Fbbootlogd does all blocking operations in background threads such that the +//! main thread will always keep accepting new input and writing it to the framebuffer. + +#![feature(io_error_more)] + +use redox_scheme::{RequestKind, SignalBehavior, Socket, V2}; + +use crate::scheme::FbbootlogScheme; + +mod display; +mod scheme; +mod text; + +fn main() { + redox_daemon::Daemon::new(|daemon| inner(daemon)).expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon) -> ! { + let socket: Socket = + Socket::create("fbbootlog").expect("fbbootlogd: failed to create fbbootlog scheme"); + + let mut scheme = FbbootlogScheme::new(); + + let mut inputd_control_handle = inputd::ControlHandle::new().unwrap(); + + // This is not possible for now as fbbootlogd 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("fbbootlogd: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + inputd_control_handle.activate_vt(1).unwrap(); + + loop { + let request = match socket + .next_request(SignalBehavior::Restart) + .expect("fbbootlogd: failed to read display scheme") + { + Some(request) => request, + None => { + // Scheme likely got unmounted + std::process::exit(0); + } + }; + + match request.kind() { + RequestKind::Call(call_request) => { + socket + .write_response( + call_request.handle_scheme_mut(&mut scheme), + SignalBehavior::Restart, + ) + .expect("fbbootlogd: failed to write display scheme"); + } + RequestKind::Cancellation(_cancellation_request) => {} + RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => { + unreachable!() + } + } + } +} diff --git a/graphics/fbbootlogd/src/scheme.rs b/graphics/fbbootlogd/src/scheme.rs new file mode 100644 index 0000000000..fa258d6c53 --- /dev/null +++ b/graphics/fbbootlogd/src/scheme.rs @@ -0,0 +1,124 @@ +use std::collections::BTreeMap; + +use redox_scheme::SchemeMut; +use syscall::{Error, EventFlags, Result, EBADF, EINVAL, ENOENT}; + +use crate::display::Display; +use crate::text::TextScreen; + +#[derive(Clone)] +pub struct Handle { + pub events: EventFlags, + pub notified_read: bool, +} + +pub struct FbbootlogScheme { + pub screen: TextScreen, + next_id: usize, + pub handles: BTreeMap, +} + +impl FbbootlogScheme { + pub fn new() -> FbbootlogScheme { + let display = Display::open_first_vt().expect("Failed to open display for vt"); + let screen = TextScreen::new(display); + + FbbootlogScheme { + screen, + next_id: 0, + handles: BTreeMap::new(), + } + } +} + +impl SchemeMut for FbbootlogScheme { + fn open(&mut self, path_str: &str, _flags: usize, _uid: u32, _gid: u32) -> Result { + if !path_str.is_empty() { + return Err(Error::new(ENOENT)); + } + + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + Handle { + events: EventFlags::empty(), + notified_read: false, + }, + ); + + Ok(id) + } + + fn dup(&mut self, id: usize, buf: &[u8]) -> Result { + if !buf.is_empty() { + return Err(Error::new(EINVAL)); + } + + let handle = self + .handles + .get(&id) + .map(|handle| handle.clone()) + .ok_or(Error::new(EBADF))?; + + let new_id = self.next_id; + self.next_id += 1; + + self.handles.insert(new_id, handle); + + Ok(new_id) + } + + fn fevent(&mut self, id: usize, flags: syscall::EventFlags) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + handle.notified_read = false; + + handle.events = flags; + Ok(syscall::EventFlags::empty()) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let path = b"fbbootlog:"; + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + + fn fsync(&mut self, id: usize) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + return Ok(0); + } + + fn read( + &mut self, + id: usize, + _buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + Err(Error::new(EINVAL)) + } + + fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + self.screen.write(buf) + } + + fn close(&mut self, id: usize) -> Result { + self.handles.remove(&id).ok_or(Error::new(EBADF))?; + Ok(0) + } +} diff --git a/graphics/fbbootlogd/src/text.rs b/graphics/fbbootlogd/src/text.rs new file mode 100644 index 0000000000..5dff07d565 --- /dev/null +++ b/graphics/fbbootlogd/src/text.rs @@ -0,0 +1,41 @@ +extern crate ransid; + +use std::collections::VecDeque; + +use syscall::error::*; + +use crate::display::Display; + +pub struct TextScreen { + pub display: Display, + inner: console_draw::TextScreen, +} + +impl TextScreen { + pub fn new(display: Display) -> TextScreen { + TextScreen { + display, + inner: console_draw::TextScreen::new(), + } + } +} + +impl TextScreen { + pub fn write(&mut self, buf: &[u8]) -> Result { + let map = self.display.map.lock().unwrap(); + let damage = self.inner.write( + &mut console_draw::DisplayMap { + offscreen: map.offscreen, + width: map.width, + height: map.height, + }, + buf, + &mut VecDeque::new(), + ); + drop(map); + + self.display.sync_rects(damage); + + Ok(buf.len()) + } +} diff --git a/graphics/fbcond/Cargo.toml b/graphics/fbcond/Cargo.toml index a22c102f40..fffb1a0729 100644 --- a/graphics/fbcond/Cargo.toml +++ b/graphics/fbcond/Cargo.toml @@ -11,6 +11,7 @@ redox_syscall = "0.5" redox-daemon = "0.1" redox-scheme = { git = "https://gitlab.redox-os.org/redox-os/redox-scheme.git" } +console-draw = { path = "../console-draw" } inputd = { path = "../../inputd" } libredox = "0.1.3" diff --git a/graphics/fbcond/src/main.rs b/graphics/fbcond/src/main.rs index 11ac25cec2..7d270eb0d0 100644 --- a/graphics/fbcond/src/main.rs +++ b/graphics/fbcond/src/main.rs @@ -51,16 +51,12 @@ fn inner(daemon: redox_daemon::Daemon, vt_ids: &[usize]) -> ! { let mut scheme = FbconScheme::new(vt_ids, &mut event_queue); - let mut inputd_control_handle = inputd::ControlHandle::new().unwrap(); - // 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"); daemon.ready().expect("failed to notify parent"); - inputd_control_handle.activate_vt(1).unwrap(); - let mut blocked = Vec::new(); // Handle all events that could have happened before registering with the event queue. diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index bbe519b117..696adf0ba2 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -1,19 +1,16 @@ extern crate ransid; -use std::collections::{BTreeSet, VecDeque}; -use std::convert::{TryFrom, TryInto}; -use std::{cmp, ptr}; +use std::collections::VecDeque; +use std::convert::TryInto; -use inputd::Damage; -use orbclient::{Event, EventOption, FONT}; +use orbclient::{Event, EventOption}; use syscall::error::*; -use crate::display::{Display, DisplayMap}; +use crate::display::Display; pub struct TextScreen { - console: ransid::Console, pub display: Display, - changed: BTreeSet, + inner: console_draw::TextScreen, ctrl: bool, input: VecDeque, } @@ -21,104 +18,13 @@ pub struct TextScreen { impl TextScreen { pub fn new(display: Display) -> TextScreen { TextScreen { - // Width and height will be filled in on the next write to the console - console: ransid::Console::new(0, 0), display, - changed: BTreeSet::new(), + inner: console_draw::TextScreen::new(), ctrl: false, input: VecDeque::new(), } } - /// Draw a rectangle - fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) { - let start_y = cmp::min(map.height, y); - let end_y = cmp::min(map.height, y + h); - - let start_x = cmp::min(map.width, x); - let len = cmp::min(map.width, x + w) - start_x; - - let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - - let stride = map.width * 4; - - let offset = y * stride + start_x * 4; - offscreen_ptr += offset; - - let mut rows = end_y - start_y; - while rows > 0 { - for i in 0..len { - unsafe { - *(offscreen_ptr as *mut u32).add(i) = color; - } - } - offscreen_ptr += stride; - rows -= 1; - } - } - - /// Invert a rectangle - fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) { - let start_y = cmp::min(map.height, y); - let end_y = cmp::min(map.height, y + h); - - let start_x = cmp::min(map.width, x); - let len = cmp::min(map.width, x + w) - start_x; - - let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - - let stride = map.width * 4; - - let offset = y * stride + start_x * 4; - offscreen_ptr += offset; - - let mut rows = end_y - start_y; - while rows > 0 { - let mut row_ptr = offscreen_ptr; - let mut cols = len; - while cols > 0 { - unsafe { - let color = *(row_ptr as *mut u32); - *(row_ptr as *mut u32) = !color; - } - row_ptr += 4; - cols -= 1; - } - offscreen_ptr += stride; - rows -= 1; - } - } - - /// Draw a character - fn char( - map: &mut DisplayMap, - x: usize, - y: usize, - character: char, - color: u32, - _bold: bool, - _italic: bool, - ) { - if x + 8 <= map.width && y + 16 <= map.height { - let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; - - let font_i = 16 * (character as usize); - if font_i + 16 <= FONT.len() { - for row in 0..16 { - let row_data = FONT[font_i + row]; - for col in 0..8 { - if (row_data >> (7 - col)) & 1 == 1 { - unsafe { - *((dst + col * 4) as *mut u32) = color; - } - } - } - dst += map.width * 4; - } - } - } - } - pub fn handle_handoff(&mut self) { self.display.reopen_for_handoff(); } @@ -222,112 +128,19 @@ impl TextScreen { } pub fn write(&mut self, buf: &[u8]) -> Result { - let mut map = self.display.map.lock().unwrap(); - - self.console.state.w = map.width / 8; - self.console.state.h = map.height / 16; - self.console.state.bottom_margin = (map.height / 16).saturating_sub(1); - - if self.console.state.cursor - && self.console.state.x < self.console.state.w - && self.console.state.y < self.console.state.h - { - let x = self.console.state.x; - let y = self.console.state.y; - Self::invert(&mut map, x * 8, y * 16, 8, 16); - self.changed.insert(y); - } - - { - let changed = &mut self.changed; - let input = &mut self.input; - self.console.write(buf, |event| match event { - ransid::Event::Char { - x, - y, - c, - color, - bold, - .. - } => { - Self::char(&mut map, x * 8, y * 16, c, color.as_rgb(), bold, false); - changed.insert(y); - } - ransid::Event::Input { data } => { - input.extend(data); - } - ransid::Event::Rect { x, y, w, h, color } => { - Self::rect(&mut map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); - for y2 in y..y + h { - changed.insert(y2); - } - } - ransid::Event::ScreenBuffer { .. } => (), - ransid::Event::Move { - from_x, - from_y, - to_x, - to_y, - w, - h, - } => { - let width = map.width; - let pixels = unsafe { &mut *map.offscreen }; - - for raw_y in 0..h { - let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; - - for pixel_y in 0..16 { - { - let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; - let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; - let len = w * 8; - - if off_from + len <= pixels.len() && off_to + len <= pixels.len() { - unsafe { - let data_ptr = pixels.as_mut_ptr() as *mut u32; - ptr::copy( - data_ptr.offset(off_from as isize), - data_ptr.offset(off_to as isize), - len, - ); - } - } - } - } - - changed.insert(to_y + y); - } - } - ransid::Event::Resize { .. } => (), - ransid::Event::Title { .. } => (), - }); - } - - if self.console.state.cursor - && self.console.state.x < self.console.state.w - && self.console.state.y < self.console.state.h - { - let x = self.console.state.x; - let y = self.console.state.y; - Self::invert(&mut map, x * 8, y * 16, 8, 16); - self.changed.insert(y); - } - - let width = map.width.try_into().unwrap(); - drop(map); - self.display.sync_rects( - self.changed - .iter() - .map(|&change| Damage { - x: 0, - y: i32::try_from(change).unwrap() * 16, - width, - height: 16, - }) - .collect(), + let map = self.display.map.lock().unwrap(); + let damage = self.inner.write( + &mut console_draw::DisplayMap { + offscreen: map.offscreen, + width: map.width, + height: map.height, + }, + buf, + &mut self.input, ); - self.changed.clear(); + drop(map); + + self.display.sync_rects(damage); Ok(buf.len()) } From 12fc9597375f95a45198678c97e2c22b8fc44e31 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:39:31 +0100 Subject: [PATCH 2/2] graphics/fbcond: Move blocking operations back to the main thread With fbbootlogd split out it is no longer essential to avoid blocking operations on the main thread of fbcond. Moving them back to the main thread simplifies things a fair bit. --- graphics/fbcond/src/display.rs | 170 ++++++++++++--------------------- graphics/fbcond/src/text.rs | 8 +- 2 files changed, 63 insertions(+), 115 deletions(-) diff --git a/graphics/fbcond/src/display.rs b/graphics/fbcond/src/display.rs index d38d99981e..46785796df 100644 --- a/graphics/fbcond/src/display.rs +++ b/graphics/fbcond/src/display.rs @@ -1,15 +1,14 @@ use inputd::{ConsumerHandle, Damage}; use libredox::flag; -use std::mem; -use std::sync::mpsc::{self, Sender}; -use std::sync::{Arc, Mutex}; -use std::{fs::File, io, os::unix::io::AsRawFd, slice}; +use std::fs::File; +use std::os::unix::io::AsRawFd; +use std::{io, mem, ptr, slice}; fn display_fd_map( width: usize, height: usize, display_file: &mut File, -) -> syscall::Result { +) -> syscall::Result<*mut [u32]> { unsafe { let display_ptr = libredox::call::mmap(libredox::call::MmapArgs { fd: display_file.as_raw_fd() as usize, @@ -19,12 +18,10 @@ fn display_fd_map( flags: flag::MAP_SHARED, addr: core::ptr::null_mut(), })?; - let display_slice = slice::from_raw_parts_mut(display_ptr as *mut u32, width * height); - Ok(DisplayMap { - offscreen: display_slice, - width, - height, - }) + Ok(ptr::slice_from_raw_parts_mut( + display_ptr as *mut u32, + width * height, + )) } } @@ -32,118 +29,51 @@ unsafe fn display_fd_unmap(image: *mut [u32]) { let _ = libredox::call::munmap(image as *mut (), image.len()); } -pub struct DisplayMap { +pub struct Display { + pub input_handle: ConsumerHandle, + pub display_file: File, pub offscreen: *mut [u32], pub width: usize, pub height: usize, } -unsafe impl Send for DisplayMap {} -unsafe impl Sync for DisplayMap {} - -impl Drop for DisplayMap { - fn drop(&mut self) { - unsafe { - display_fd_unmap(self.offscreen); - } - } -} - -enum DisplayCommand { - Resize { width: usize, height: usize }, - ReopenForHandoff, - SyncRects(Vec), -} - -pub struct Display { - pub input_handle: Arc, - cmd_tx: Sender, - pub map: Arc>, -} - impl Display { pub fn open_vt(vt: usize) -> io::Result { - let input_handle = Arc::new(ConsumerHandle::for_vt(vt)?); + let input_handle = ConsumerHandle::for_vt(vt)?; let (mut display_file, width, height) = Self::open_display(&input_handle)?; - let map = Arc::new(Mutex::new( - display_fd_map(width, height, &mut display_file) - .unwrap_or_else(|e| panic!("failed to map display for VT #{vt}: {e}")), - )); - - let (cmd_tx, cmd_rx) = mpsc::channel(); - - let input_handle_clone = input_handle.clone(); - let map_clone = map.clone(); - std::thread::spawn(move || { - while let Ok(cmd) = cmd_rx.recv() { - match cmd { - DisplayCommand::Resize { width, height } => { - match display_fd_map(width, height, &mut display_file) { - Ok(ok) => { - *map_clone.lock().unwrap() = ok; - } - Err(err) => { - eprintln!( - "failed to resize display to {}x{}: {}", - width, height, err - ); - } - } - } - DisplayCommand::ReopenForHandoff => { - eprintln!("fbcond: Performing handoff"); - - let (mut new_display_file, width, height) = - Self::open_display(&input_handle_clone).unwrap(); - - eprintln!("fbcond: Opened new display for VT #{vt}"); - - match display_fd_map(width, height, &mut new_display_file) { - Ok(ok) => { - *map_clone.lock().unwrap() = ok; - display_file = new_display_file; - - eprintln!("fbcond: Mapped new display for VT #{vt}"); - } - Err(err) => { - eprintln!( - "failed to resize display to {}x{}: {}", - width, height, err - ); - } - } - } - DisplayCommand::SyncRects(sync_rects) => unsafe { - libredox::call::write( - display_file.as_raw_fd() as usize, - slice::from_raw_parts( - sync_rects.as_ptr() as *const u8, - sync_rects.len() * mem::size_of::(), - ), - ) - .unwrap(); - }, - } - } - }); + let offscreen = display_fd_map(width, height, &mut display_file) + .unwrap_or_else(|e| panic!("failed to map display for VT #{vt}: {e}")); Ok(Self { input_handle, - cmd_tx, - map, + display_file, + offscreen, + width, + height, }) } /// Re-open the display after a handoff. - /// - /// Once re-opening is finished, you must call [`resize`] to map the new framebuffer. - /// - /// Warning: This must be called in a background thread to avoid a deadlock when the - /// graphics driver (indirectly) writes logs to fbcond. pub fn reopen_for_handoff(&mut self) { - self.cmd_tx.send(DisplayCommand::ReopenForHandoff).unwrap(); + eprintln!("fbcond: Performing handoff"); + + let (mut new_display_file, width, height) = Self::open_display(&self.input_handle).unwrap(); + + eprintln!("fbcond: Opened new display"); + + match display_fd_map(width, height, &mut new_display_file) { + Ok(offscreen) => { + self.offscreen = offscreen; + self.display_file = new_display_file; + + eprintln!("fbcond: Mapped new display"); + } + Err(err) => { + eprintln!("failed to resize display to {}x{}: {}", width, height, err); + } + } } fn open_display(input_handle: &ConsumerHandle) -> io::Result<(File, usize, usize)> { @@ -175,14 +105,34 @@ impl Display { } pub fn resize(&mut self, width: usize, height: usize) { - self.cmd_tx - .send(DisplayCommand::Resize { width, height }) - .unwrap(); + match display_fd_map(width, height, &mut self.display_file) { + Ok(offscreen) => { + self.offscreen = offscreen; + } + Err(err) => { + eprintln!("failed to resize display to {}x{}: {}", width, height, err); + } + } } pub fn sync_rects(&mut self, sync_rects: Vec) { - self.cmd_tx - .send(DisplayCommand::SyncRects(sync_rects)) + unsafe { + libredox::call::write( + self.display_file.as_raw_fd() as usize, + slice::from_raw_parts( + sync_rects.as_ptr() as *const u8, + sync_rects.len() * mem::size_of::(), + ), + ) .unwrap(); + } + } +} + +impl Drop for Display { + fn drop(&mut self) { + unsafe { + display_fd_unmap(self.offscreen); + } } } diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index 696adf0ba2..e01dab8ae6 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -128,17 +128,15 @@ impl TextScreen { } pub fn write(&mut self, buf: &[u8]) -> Result { - let map = self.display.map.lock().unwrap(); let damage = self.inner.write( &mut console_draw::DisplayMap { - offscreen: map.offscreen, - width: map.width, - height: map.height, + offscreen: self.display.offscreen, + width: self.display.width, + height: self.display.height, }, buf, &mut self.input, ); - drop(map); self.display.sync_rects(damage);