From 99a00abecadb1a1f6fbdfb102b0220c016997cc9 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:49:17 +0100 Subject: [PATCH 1/4] fbcond: Split a couple of helper functions out of open_vt --- graphics/fbcond/src/display.rs | 42 +++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/graphics/fbcond/src/display.rs b/graphics/fbcond/src/display.rs index 01f58ed2c8..11a2dce490 100644 --- a/graphics/fbcond/src/display.rs +++ b/graphics/fbcond/src/display.rs @@ -50,24 +50,42 @@ pub struct Display { impl Display { pub fn open_vt(vt: usize) -> io::Result { - let mut buffer = [0; 1024]; - - let input_handle = OpenOptions::new() + let mut input_handle = OpenOptions::new() .read(true) .custom_flags(O_NONBLOCK as i32) .open(format!("/scheme/input/consumer/{vt}"))?; - let fd = input_handle.as_raw_fd(); + let display_path = Self::display_path(&mut input_handle)?; + + let (display_file, width, height) = Self::open_display(&display_path)?; + + let offscreen_buffer = display_fd_map(width, height, display_file.as_raw_fd() as usize) + .unwrap_or_else(|e| panic!("failed to map display '{display_path}: {e}")); + Ok(Self { + input_handle, + display_file, + offscreen: offscreen_buffer, + width, + height, + }) + } + + fn display_path(input_handle: &mut File) -> io::Result { + let mut buffer = [0; 1024]; + let fd = input_handle.as_raw_fd(); let written = libredox::call::fpath(fd as usize, &mut buffer) .expect("init: failed to get the path to the display device"); assert!(written <= buffer.len()); - let display_path = - std::str::from_utf8(&buffer[..written]).expect("init: display path UTF-8 check failed"); + Ok(std::str::from_utf8(&buffer[..written]) + .expect("init: display path UTF-8 check failed") + .to_owned()) + } + fn open_display(display_path: &str) -> io::Result<(File, usize, usize)> { let display_file = - libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + libredox::call::open(&display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) .unwrap_or_else(|err| { panic!("failed to open display {}: {}", display_path, err); @@ -84,15 +102,7 @@ impl Display { let path = Self::url_parts(&url)?; let (width, height) = Self::parse_display_path(path); - let offscreen_buffer = display_fd_map(width, height, display_file.as_raw_fd() as usize) - .unwrap_or_else(|e| panic!("failed to map display '{display_path}: {e}")); - Ok(Self { - input_handle, - display_file, - offscreen: offscreen_buffer, - width, - height, - }) + Ok((display_file, width, height)) } fn url_parts(url: &str) -> io::Result<&str> { From 15cee6bb4851ae538bc6ba97ea7ac28dfaf33e2e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:15:56 +0100 Subject: [PATCH 2/4] fbcond: Resize ransid console on every write This is necessary to correctly handle async changes to the framebuffer size in the future. --- graphics/fbcond/src/text.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index 7f75c6fbc5..2adcc5a7dd 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -21,7 +21,8 @@ pub struct TextScreen { impl TextScreen { pub fn new(display: Display) -> TextScreen { TextScreen { - console: ransid::Console::new(display.width / 8, display.height / 16), + // Width and height will be filled in on the next write to the console + console: ransid::Console::new(0, 0), display, changed: BTreeSet::new(), ctrl: false, @@ -121,8 +122,6 @@ impl TextScreen { pub fn resize(&mut self, width: usize, height: usize) { self.display .resize(width.try_into().unwrap(), height.try_into().unwrap()); - self.console.state.w = width / 8; - self.console.state.h = height / 16; } pub fn input(&mut self, event: &Event) { @@ -223,6 +222,10 @@ impl TextScreen { } pub fn write(&mut self, buf: &[u8]) -> Result { + self.console.state.w = self.display.width / 8; + self.console.state.h = self.display.height / 16; + self.console.state.bottom_margin = (self.display.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 From 5f7ee8f646e7f107b86962e322d49170fae9863e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:45:27 +0100 Subject: [PATCH 3/4] Use inputd's Damage everywhere instead of separate SyncRect types --- graphics/fbcond/src/display.rs | 17 ++++------------- graphics/fbcond/src/text.rs | 9 +++++---- graphics/vesad/src/screen.rs | 29 ++++++++++------------------- inputd/src/lib.rs | 2 ++ 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/graphics/fbcond/src/display.rs b/graphics/fbcond/src/display.rs index 11a2dce490..bc9afc6a38 100644 --- a/graphics/fbcond/src/display.rs +++ b/graphics/fbcond/src/display.rs @@ -1,3 +1,4 @@ +use inputd::Damage; use libredox::flag; use std::fs::OpenOptions; use std::mem; @@ -11,16 +12,6 @@ use std::{ }; use syscall::{O_CLOEXEC, O_NONBLOCK, O_RDWR}; -// Keep synced with vesad -#[derive(Clone, Copy)] -#[repr(C, packed)] -pub struct SyncRect { - pub x: i32, - pub y: i32, - pub w: i32, - pub h: i32, -} - fn display_fd_map(width: usize, height: usize, display_fd: usize) -> syscall::Result<*mut [u32]> { unsafe { let display_ptr = libredox::call::mmap(libredox::call::MmapArgs { @@ -146,13 +137,13 @@ impl Display { } } - pub fn sync_rect(&mut self, sync_rect: SyncRect) -> syscall::Result<()> { + pub fn sync_rect(&mut self, sync_rect: Damage) -> syscall::Result<()> { unsafe { libredox::call::write( self.display_file.as_raw_fd().as_raw_fd() as usize, slice::from_raw_parts( - &sync_rect as *const SyncRect as *const u8, - mem::size_of::(), + &sync_rect as *const Damage as *const u8, + mem::size_of::(), ), )?; Ok(()) diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index 2adcc5a7dd..4f680496c8 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -5,10 +5,11 @@ use std::convert::{TryFrom, TryInto}; use std::os::fd::AsRawFd; use std::{cmp, ptr}; +use inputd::Damage; use orbclient::{Event, EventOption, FONT}; use syscall::error::*; -use crate::display::{Display, SyncRect}; +use crate::display::Display; pub struct TextScreen { console: ransid::Console, @@ -315,11 +316,11 @@ impl TextScreen { let width = self.display.width.try_into().unwrap(); for &change in self.changed.iter() { - self.display.sync_rect(SyncRect { + self.display.sync_rect(Damage { x: 0, y: i32::try_from(change).unwrap() * 16, - w: width, - h: 16, + width, + height: 16, })?; } self.changed.clear(); diff --git a/graphics/vesad/src/screen.rs b/graphics/vesad/src/screen.rs index 471340ce86..2b86e83d28 100644 --- a/graphics/vesad/src/screen.rs +++ b/graphics/vesad/src/screen.rs @@ -2,28 +2,19 @@ use std::collections::VecDeque; use std::convert::TryInto; use std::{cmp, mem, ptr, slice}; +use inputd::Damage; use orbclient::{Event, ResizeEvent}; use syscall::error::*; use crate::display::OffscreenBuffer; use crate::framebuffer::FrameBuffer; -// Keep synced with orbital -#[derive(Clone, Copy)] -#[repr(C, packed)] -pub struct SyncRect { - pub x: i32, - pub y: i32, - pub w: i32, - pub h: i32, -} - pub struct GraphicScreen { pub width: usize, pub height: usize, pub offscreen: OffscreenBuffer, pub input: VecDeque, - pub sync_rects: Vec, + pub sync_rects: Vec, } impl GraphicScreen { @@ -117,22 +108,22 @@ impl GraphicScreen { pub fn write(&mut self, buf: &[u8]) -> Result { let sync_rects = unsafe { slice::from_raw_parts( - buf.as_ptr() as *const SyncRect, - buf.len() / mem::size_of::(), + buf.as_ptr() as *const Damage, + buf.len() / mem::size_of::(), ) }; self.sync_rects.extend_from_slice(sync_rects); - Ok(sync_rects.len() * mem::size_of::()) + Ok(sync_rects.len() * mem::size_of::()) } pub fn sync(&mut self, framebuffer: &mut FrameBuffer) { for sync_rect in self.sync_rects.drain(..) { let x = sync_rect.x.try_into().unwrap_or(0); let y = sync_rect.y.try_into().unwrap_or(0); - let w = sync_rect.w.try_into().unwrap_or(0); - let h = sync_rect.h.try_into().unwrap_or(0); + let w = sync_rect.width.try_into().unwrap_or(0); + let h = sync_rect.height.try_into().unwrap_or(0); let start_y = cmp::min(self.height, y); let end_y = cmp::min(self.height, y + h); @@ -161,11 +152,11 @@ impl GraphicScreen { pub fn redraw(&mut self, framebuffer: &mut FrameBuffer) { let width = self.width.try_into().unwrap(); let height = self.height.try_into().unwrap(); - self.sync_rects.push(SyncRect { + self.sync_rects.push(Damage { x: 0, y: 0, - w: width, - h: height, + width, + height, }); self.sync(framebuffer); } diff --git a/inputd/src/lib.rs b/inputd/src/lib.rs index e48539f683..907ea53a9b 100644 --- a/inputd/src/lib.rs +++ b/inputd/src/lib.rs @@ -90,6 +90,8 @@ pub struct VtEvent { pub stride: u32, } +// Keep synced with orbital's SyncRect +#[derive(Debug, Copy, Clone)] #[repr(packed)] pub struct Damage { pub x: i32, From c97652a50832e6ab88bd27eab5def3172f389479 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:33:17 +0100 Subject: [PATCH 4/4] fbcond: Make sure we never block on the graphics driver To prevent deadlocks if the graphics driver in turn tries to write to fbcond for log messages. --- graphics/fbcond/src/display.rs | 134 ++++++++++++++++++++++----------- graphics/fbcond/src/text.rs | 83 ++++++++++---------- 2 files changed, 132 insertions(+), 85 deletions(-) diff --git a/graphics/fbcond/src/display.rs b/graphics/fbcond/src/display.rs index bc9afc6a38..39b615e5a9 100644 --- a/graphics/fbcond/src/display.rs +++ b/graphics/fbcond/src/display.rs @@ -3,6 +3,8 @@ use libredox::flag; use std::fs::OpenOptions; use std::mem; use std::os::unix::fs::OpenOptionsExt; +use std::sync::mpsc::{self, Sender}; +use std::sync::{Arc, Mutex}; use std::{ fs::File, io, @@ -12,10 +14,14 @@ use std::{ }; use syscall::{O_CLOEXEC, O_NONBLOCK, O_RDWR}; -fn display_fd_map(width: usize, height: usize, display_fd: usize) -> syscall::Result<*mut [u32]> { +fn display_fd_map( + width: usize, + height: usize, + display_file: &mut File, +) -> syscall::Result { unsafe { let display_ptr = libredox::call::mmap(libredox::call::MmapArgs { - fd: display_fd, + fd: display_file.as_raw_fd() as usize, offset: 0, length: (width * height * 4), prot: flag::PROT_READ | flag::PROT_WRITE, @@ -23,7 +29,11 @@ fn display_fd_map(width: usize, height: usize, display_fd: usize) -> syscall::Re addr: core::ptr::null_mut(), })?; let display_slice = slice::from_raw_parts_mut(display_ptr as *mut u32, width * height); - Ok(display_slice) + Ok(DisplayMap { + offscreen: display_slice, + width, + height, + }) } } @@ -31,14 +41,34 @@ unsafe fn display_fd_unmap(image: *mut [u32]) { let _ = libredox::call::munmap(image as *mut (), image.len()); } -pub struct Display { - pub input_handle: File, - pub display_file: File, +pub struct DisplayMap { 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 }, + SyncRects(Vec), +} + +pub struct Display { + pub input_handle: File, + cmd_tx: Sender, + pub map: Arc>, +} + impl Display { pub fn open_vt(vt: usize) -> io::Result { let mut input_handle = OpenOptions::new() @@ -48,16 +78,55 @@ impl Display { let display_path = Self::display_path(&mut input_handle)?; - let (display_file, width, height) = Self::open_display(&display_path)?; + let (mut display_file, width, height) = Self::open_display(&display_path)?; + + let map = Arc::new(Mutex::new( + display_fd_map(width, height, &mut display_file) + .unwrap_or_else(|e| panic!("failed to map display '{display_path}: {e}")), + )); + + let (cmd_tx, cmd_rx) = mpsc::channel(); + + 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::SyncRects(sync_rects) => { + for sync_rect in sync_rects { + unsafe { + libredox::call::write( + display_file.as_raw_fd() as usize, + slice::from_raw_parts( + &sync_rect as *const Damage as *const u8, + mem::size_of::(), + ), + ) + .unwrap(); + } + } + libredox::call::fsync(display_file.as_raw_fd() as usize).unwrap(); + } + } + } + }); - let offscreen_buffer = display_fd_map(width, height, display_file.as_raw_fd() as usize) - .unwrap_or_else(|e| panic!("failed to map display '{display_path}: {e}")); Ok(Self { input_handle, - display_file, - offscreen: offscreen_buffer, - width, - height, + cmd_tx, + map, }) } @@ -122,39 +191,14 @@ impl Display { } pub fn resize(&mut self, width: usize, height: usize) { - match display_fd_map(width, height, self.display_file.as_raw_fd() as usize) { - Ok(ok) => { - unsafe { - display_fd_unmap(self.offscreen); - } - self.offscreen = ok; - self.width = width; - self.height = height; - } - Err(err) => { - eprintln!("failed to resize display to {}x{}: {}", width, height, err); - } - } + self.cmd_tx + .send(DisplayCommand::Resize { width, height }) + .unwrap(); } - pub fn sync_rect(&mut self, sync_rect: Damage) -> syscall::Result<()> { - unsafe { - libredox::call::write( - self.display_file.as_raw_fd().as_raw_fd() as usize, - slice::from_raw_parts( - &sync_rect as *const Damage as *const u8, - mem::size_of::(), - ), - )?; - Ok(()) - } - } -} - -impl Drop for Display { - fn drop(&mut self) { - unsafe { - display_fd_unmap(self.offscreen); - } + pub fn sync_rects(&mut self, sync_rects: Vec) { + self.cmd_tx + .send(DisplayCommand::SyncRects(sync_rects)) + .unwrap(); } } diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index 4f680496c8..46afcc738c 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -2,14 +2,13 @@ extern crate ransid; use std::collections::{BTreeSet, VecDeque}; use std::convert::{TryFrom, TryInto}; -use std::os::fd::AsRawFd; use std::{cmp, ptr}; use inputd::Damage; use orbclient::{Event, EventOption, FONT}; use syscall::error::*; -use crate::display::Display; +use crate::display::{Display, DisplayMap}; pub struct TextScreen { console: ransid::Console, @@ -32,16 +31,16 @@ impl TextScreen { } /// Draw a rectangle - fn rect(display: &mut Display, x: usize, y: usize, w: usize, h: usize, color: u32) { - let start_y = cmp::min(display.height, y); - let end_y = cmp::min(display.height, y + h); + 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(display.width, x); - let len = cmp::min(display.width, x + w) - start_x; + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; - let mut offscreen_ptr = display.offscreen as *mut u8 as usize; + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - let stride = display.width * 4; + let stride = map.width * 4; let offset = y * stride + start_x * 4; offscreen_ptr += offset; @@ -59,16 +58,16 @@ impl TextScreen { } /// Invert a rectangle - fn invert(display: &mut Display, x: usize, y: usize, w: usize, h: usize) { - let start_y = cmp::min(display.height, y); - let end_y = cmp::min(display.height, y + h); + 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(display.width, x); - let len = cmp::min(display.width, x + w) - start_x; + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; - let mut offscreen_ptr = display.offscreen as *mut u8 as usize; + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - let stride = display.width * 4; + let stride = map.width * 4; let offset = y * stride + start_x * 4; offscreen_ptr += offset; @@ -92,7 +91,7 @@ impl TextScreen { /// Draw a character fn char( - display: &mut Display, + map: &mut DisplayMap, x: usize, y: usize, character: char, @@ -100,8 +99,8 @@ impl TextScreen { _bold: bool, _italic: bool, ) { - if x + 8 <= display.width && y + 16 <= display.height { - let mut dst = display.offscreen as *mut u8 as usize + (y * display.width + x) * 4; + 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() { @@ -114,7 +113,7 @@ impl TextScreen { } } } - dst += display.width * 4; + dst += map.width * 4; } } } @@ -223,9 +222,11 @@ impl TextScreen { } pub fn write(&mut self, buf: &[u8]) -> Result { - self.console.state.w = self.display.width / 8; - self.console.state.h = self.display.height / 16; - self.console.state.bottom_margin = (self.display.height / 16).saturating_sub(1); + 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 @@ -233,12 +234,11 @@ impl TextScreen { { let x = self.console.state.x; let y = self.console.state.y; - Self::invert(&mut self.display, x * 8, y * 16, 8, 16); + Self::invert(&mut map, x * 8, y * 16, 8, 16); self.changed.insert(y); } { - let display = &mut self.display; let changed = &mut self.changed; let input = &mut self.input; self.console.write(buf, |event| match event { @@ -250,14 +250,14 @@ impl TextScreen { bold, .. } => { - Self::char(display, x * 8, y * 16, c, color.as_rgb(), bold, false); + 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(display, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); + Self::rect(&mut map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); for y2 in y..y + h { changed.insert(y2); } @@ -271,8 +271,8 @@ impl TextScreen { w, h, } => { - let width = display.width; - let pixels = unsafe { &mut *display.offscreen }; + 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 }; @@ -310,21 +310,24 @@ impl TextScreen { { let x = self.console.state.x; let y = self.console.state.y; - Self::invert(&mut self.display, x * 8, y * 16, 8, 16); + Self::invert(&mut map, x * 8, y * 16, 8, 16); self.changed.insert(y); } - let width = self.display.width.try_into().unwrap(); - for &change in self.changed.iter() { - self.display.sync_rect(Damage { - x: 0, - y: i32::try_from(change).unwrap() * 16, - width, - height: 16, - })?; - } + 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(), + ); self.changed.clear(); - libredox::call::fsync(self.display.display_file.as_raw_fd().as_raw_fd() as usize)?; Ok(buf.len()) }