From 7cc2f4eff7bb94d457e215a8b028d69d9e29cb5e Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Mon, 3 Jul 2023 17:12:59 +1000 Subject: [PATCH 01/11] virtio-gpu: start working on the scheme Signed-off-by: Anhad Singh --- Cargo.lock | 16 ++- bgad/src/main.rs | 2 +- fmt.sh | 4 + ps2d/src/main.rs | 2 +- usbhidd/src/main.rs | 2 +- vboxd/src/main.rs | 2 +- vesad/src/main.rs | 2 +- virtio-core/src/probe.rs | 8 +- virtio-core/src/transport.rs | 13 +- virtio-core/src/utils.rs | 13 +- virtio-gpud/Cargo.toml | 5 + virtio-gpud/patches/00-init.patch | 45 +++++++ virtio-gpud/patches/01-orbital.patch | 13 ++ virtio-gpud/src/main.rs | 184 ++++++++++++++++++++++++++- virtio-gpud/src/scheme.rs | 108 ++++++++++++++++ 15 files changed, 394 insertions(+), 25 deletions(-) create mode 100644 virtio-gpud/patches/00-init.patch create mode 100644 virtio-gpud/patches/01-orbital.patch create mode 100644 virtio-gpud/src/scheme.rs diff --git a/Cargo.lock b/Cargo.lock index 69722a682a..fb801dd351 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -773,12 +773,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "matches" @@ -1131,9 +1128,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] @@ -1952,9 +1949,14 @@ dependencies = [ name = "virtio-gpud" version = "0.1.0" dependencies = [ + "anyhow", + "futures", "log", + "orbclient", "pcid", "redox-daemon", + "redox_syscall 0.3.5", + "static_assertions", "virtio-core", ] diff --git a/bgad/src/main.rs b/bgad/src/main.rs index 2d5387aeae..781e68ae8f 100644 --- a/bgad/src/main.rs +++ b/bgad/src/main.rs @@ -38,7 +38,7 @@ fn main() { let mut scheme = BgaScheme { bga: bga, - display: File::open("display:input").ok() + display: File::open("display/vesa:input").ok() }; scheme.update_size(); diff --git a/fmt.sh b/fmt.sh index b0b137080b..450e518f26 100755 --- a/fmt.sh +++ b/fmt.sh @@ -11,3 +11,7 @@ popd pushd virtio-blkd cargo fmt popd + +pushd virtio-gpud +cargo fmt +popd diff --git a/ps2d/src/main.rs b/ps2d/src/main.rs index 5a845e415f..96df72a2b0 100644 --- a/ps2d/src/main.rs +++ b/ps2d/src/main.rs @@ -45,7 +45,7 @@ fn daemon(daemon: redox_daemon::Daemon) -> ! { let input = OpenOptions::new() .write(true) - .open("display:input") + .open("display/vesa:input") .expect("ps2d: failed to open display:input"); let mut event_file = OpenOptions::new() diff --git a/usbhidd/src/main.rs b/usbhidd/src/main.rs index df3f6d60ca..bfefd2f798 100644 --- a/usbhidd/src/main.rs +++ b/usbhidd/src/main.rs @@ -382,7 +382,7 @@ fn main() { let report_ty = ReportTy::Input; let report_id = 0; - let mut display = File::open("display:input").expect("Failed to open orbital input socket"); + let mut display = File::open("display/vesa:input").expect("Failed to open orbital input socket"); //TODO: get dynamically let mut display_width = 0; diff --git a/vboxd/src/main.rs b/vboxd/src/main.rs index 4ad50c5598..c42c8da424 100644 --- a/vboxd/src/main.rs +++ b/vboxd/src/main.rs @@ -206,7 +206,7 @@ fn main() { let mut width = 0; let mut height = 0; - let mut display_opt = File::open("display:input").ok(); + let mut display_opt = File::open("display/vesa:input").ok(); if let Some(ref display) = display_opt { let mut buf: [u8; 4096] = [0; 4096]; if let Ok(count) = syscall::fpath(display.as_raw_fd() as usize, &mut buf) { diff --git a/vesad/src/main.rs b/vesad/src/main.rs index 10ae4df44a..3d7cf98f4c 100644 --- a/vesad/src/main.rs +++ b/vesad/src/main.rs @@ -90,7 +90,7 @@ fn main() { redox_daemon::Daemon::new(|daemon| inner(daemon, framebuffers, &spec)).expect("failed to create daemon"); } fn inner(daemon: redox_daemon::Daemon, framebuffers: Vec, spec: &[bool]) -> ! { - let mut socket = File::create(":display").expect("vesad: failed to create display scheme"); + let mut socket = File::create(":display/vesa").expect("vesad: failed to create display scheme"); let mut scheme = DisplayScheme::new(framebuffers, &spec); diff --git a/virtio-core/src/probe.rs b/virtio-core/src/probe.rs index f627de9e68..58e9466659 100644 --- a/virtio-core/src/probe.rs +++ b/virtio-core/src/probe.rs @@ -12,7 +12,7 @@ use syscall::{Io, PHYSMAP_NO_CACHE, PHYSMAP_WRITE}; use crate::spec::*; use crate::transport::{Error, StandardTransport}; -use crate::utils::{VolatileCell, align_down}; +use crate::utils::{align_down, VolatileCell}; pub struct Device<'a> { pub transport: Arc>, @@ -186,11 +186,7 @@ pub fn probe_device<'a>(pcid_handle: &mut PcidServerHandle) -> Result let size = offset + capability.length as usize; - let addr = syscall::physmap( - aligned_addr, - size, - PHYSMAP_WRITE | PHYSMAP_NO_CACHE, - )?; + let addr = syscall::physmap(aligned_addr, size, PHYSMAP_WRITE | PHYSMAP_NO_CACHE)?; addr + offset }; diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index ae94c70316..5bcc693f59 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -86,6 +86,12 @@ impl<'a> Future for PendingRequest<'a> { .insert(self.first_descriptor, cx.waker().clone()); let used_head = self.queue.used.head_index(); + + if used_head == self.queue.used_head.load(Ordering::SeqCst) { + // No new requests have been completed. + return Poll::Pending; + } + let used_element = self.queue.used.get_element_at((used_head - 1) as usize); let written = used_element.written.get(); @@ -109,6 +115,8 @@ impl<'a> Future for PendingRequest<'a> { .lock() .unwrap() .remove(&self.first_descriptor); + + self.queue.used_head.store(used_head, Ordering::SeqCst); return Poll::Ready(written); } else { return Poll::Pending; @@ -122,6 +130,7 @@ pub struct Queue<'a> { pub used: Used<'a>, pub descriptor: Dma<[Descriptor]>, pub available: Available<'a>, + pub used_head: AtomicU16, notification_bell: &'a mut AtomicU16, descriptor_stack: crossbeam_queue::SegQueue, @@ -148,6 +157,7 @@ impl<'a> Queue<'a> { waker: Mutex::new(std::collections::HashMap::new()), queue_index, descriptor_stack, + used_head: AtomicU16::new(0), sref: sref.clone(), }) } @@ -190,8 +200,6 @@ impl<'a> Queue<'a> { self.notification_bell .store(self.queue_index, Ordering::SeqCst); - assert_eq!(self.used.flags(), 0); - PendingRequest { queue: self.sref.upgrade().unwrap(), first_descriptor: first_descriptor as u32, @@ -470,6 +478,7 @@ impl<'a> StandardTransport<'a> { event_queue .add(irq_fd, move |_| -> Result, std::io::Error> { + // Wake up the tasks waiting on the queue. for (_, task) in queue_copy.waker.lock().unwrap().iter() { task.wake_by_ref(); } diff --git a/virtio-core/src/utils.rs b/virtio-core/src/utils.rs index 85d39e54af..6f71dac4ea 100644 --- a/virtio-core/src/utils.rs +++ b/virtio-core/src/utils.rs @@ -1,7 +1,7 @@ use core::cell::UnsafeCell; +use core::fmt::Debug; use core::marker::PhantomData; -#[derive(Debug)] #[repr(C)] pub struct VolatileCell { value: UnsafeCell, @@ -28,6 +28,17 @@ impl VolatileCell { } } +impl Debug for VolatileCell +where + T: Debug + Copy, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VolatileCell") + .field("value", &self.get()) + .finish() + } +} + unsafe impl Sync for VolatileCell {} #[repr(C)] diff --git a/virtio-gpud/Cargo.toml b/virtio-gpud/Cargo.toml index 17fdf4384a..7a09708fc4 100644 --- a/virtio-gpud/Cargo.toml +++ b/virtio-gpud/Cargo.toml @@ -6,8 +6,13 @@ authors = ["Anhad Singh "] [dependencies] log = "0.4" +static_assertions = "1.1.0" +futures = { version = "0.3.28", features = ["executor"] } +anyhow = "1.0.71" virtio-core = { path = "../virtio-core" } pcid = { path = "../pcid" } redox-daemon = "0.1" +redox_syscall = "0.3" +orbclient = "0.3.27" diff --git a/virtio-gpud/patches/00-init.patch b/virtio-gpud/patches/00-init.patch new file mode 100644 index 0000000000..dc20be1df6 --- /dev/null +++ b/virtio-gpud/patches/00-init.patch @@ -0,0 +1,45 @@ +diff --git a/src/main.rs b/src/main.rs +index a99488a..ec97fdd 100644 +--- a/src/main.rs ++++ b/src/main.rs +@@ -78,6 +78,40 @@ pub fn run(file: &Path) -> Result<()> { + println!("init: failed to run: no argument"); + }, + "run.d" => if let Some(new_dir) = args.next() { ++ // On startup, the VESA display driver is started which basically makes use of the framebuffer ++ // provided by the firmware. The GPU device are latter started by `pcid` (such as `virtio-gpu`). ++ let mut devices = vec![]; ++ let schemes = std::fs::read_dir(":").unwrap(); ++ ++ for entry in schemes { ++ let path = entry.unwrap().path(); ++ let path_str = path ++ .into_os_string() ++ .into_string() ++ .expect("init: failed to convert path to string"); ++ ++ if path_str.contains("display") { ++ println!("init: found display scheme {}", path_str); ++ devices.push(path_str); ++ } ++ } ++ ++ let device = devices.iter().filter(|d| !d.contains("vesa")).collect::>(); ++ let device = if device.is_empty() { ++ // No GPU available, fallback to VESA display which *should* always be accessible via `display/vesa:`. ++ "vesa" ++ } else { ++ // Parts: ++ // :/display/virtio-gpu ++ // ++ // 1st: ":" ++ // 2nd: "display" ++ // 3rd: "virtio-gpu" (the one we want!) ++ device[0].split("/").nth(2).unwrap() ++ }; ++ ++ std::env::set_var("DISPLAY", &format!("display/{}:3/activate", device)); ++ + let mut entries = vec![]; + match read_dir(&new_dir) { + Ok(list) => for entry_res in list { diff --git a/virtio-gpud/patches/01-orbital.patch b/virtio-gpud/patches/01-orbital.patch new file mode 100644 index 0000000000..493ec66735 --- /dev/null +++ b/virtio-gpud/patches/01-orbital.patch @@ -0,0 +1,13 @@ +diff --git a/src/main.rs b/src/main.rs +index 1cdf9cf..3859ac0 100644 +--- a/src/main.rs ++++ b/src/main.rs +@@ -63,7 +63,7 @@ fn orbital(daemon: Daemon) -> Result<(), String> { + .enable(); + + let mut args = env::args().skip(1); +- let display_path = args.next().ok_or("no display argument")?; ++ let display_path = env::var("DISPLAY").unwrap(); + let login_cmd = args.next().ok_or("no login manager argument")?; + + core::fix_env(&display_path).map_err(|_| "error setting env vars")?; diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index 140c73ac2c..b989fa77d7 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -1,7 +1,154 @@ -use pcid_interface::PcidServerHandle; -use virtio_core::transport::Error; +//! `virtio-gpu` is a virtio based graphics adapter. It can operate in 2D mode and in 3D mode. +//! +//! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support +//! on the host machine. -fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> { +#![feature(async_closure)] + +use std::fs::File; +use std::io::{Read, Write}; + +use pcid_interface::PcidServerHandle; + +use syscall::{Packet, SchemeMut}; +use virtio_core::utils::VolatileCell; +use virtio_core::MSIX_PRIMARY_VECTOR; + +mod scheme; + +// const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; +const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; + +#[repr(C)] +pub struct GpuConfig { + /// Signals pending events to the driver. + pub events_read: VolatileCell, // read-only + /// Clears pending events in the device (write-to-clear). + pub events_clear: VolatileCell, // write-only + + pub min_scanouts: VolatileCell, + pub num_capsets: VolatileCell, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u32)] +pub enum CommandTy { + Undefined = 0, + + // 2D commands + GetDisplayInfo = 0x0100, + ResourceCreate2d, + ResourceUnref, + SetScanout, + ResourceFlush, + TransferToHost2d, + ResourceAttachBacking, + ResourceDetachBacking, + GetCapsetInfo, + GetCapset, + GetEdid, + ResourceAssignUuid, + ResourceCreateBlob, + SetScanoutBlob, + + // 3D commands + CtxCreate = 0x0200, + CtxDestroy, + CtxAttachResource, + CtxDetachResource, + ResourceCreate3d, + TransferToHost3d, + TransferFromHost3d, + Submit3d, + ResourceMapBlob, + ResourceUnmapBlob, + + // cursor commands + UpdateCursor = 0x0300, + MoveCursor, + + // success responses + RespOkNodata = 0x1100, + RespOkDisplayInfo, + RespOkCapsetInfo, + RespOkCapset, + RespOkEdid, + RespOkResourceUuid, + RespOkMapInfo, + + // error responses + RespErrUnspec = 0x1200, + RespErrOutOfMemory, + RespErrInvalidScanoutId, + RespErrInvalidResourceId, + RespErrInvalidContextId, + RespErrInvalidParameter, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 4); + +#[derive(Debug)] +#[repr(C)] +pub struct ControlHeader { + pub ty: VolatileCell, + pub flags: VolatileCell, + pub fence_id: VolatileCell, + pub ctx_id: VolatileCell, + pub ring_index: VolatileCell, + padding: [u8; 3], +} + +impl Default for ControlHeader { + fn default() -> Self { + Self { + ty: VolatileCell::new(CommandTy::Undefined), + flags: VolatileCell::new(0), + fence_id: VolatileCell::new(0), + ctx_id: VolatileCell::new(0), + ring_index: VolatileCell::new(0), + padding: [0; 3], + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct GpuRect { + pub x: VolatileCell, + pub y: VolatileCell, + pub width: VolatileCell, + pub height: VolatileCell, +} + +#[derive(Debug)] +#[repr(C)] +pub struct DisplayInfo { + pub rect: GpuRect, + pub enabled: VolatileCell, + pub flags: VolatileCell, +} + +#[derive(Debug)] +#[repr(C)] +pub struct GetDisplayInfo { + pub header: ControlHeader, + pub display_info: [DisplayInfo; VIRTIO_GPU_MAX_SCANOUTS], +} + +impl Default for GetDisplayInfo { + fn default() -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::GetDisplayInfo), + ..Default::default() + }, + + display_info: unsafe { core::mem::zeroed() }, + } + } +} + +fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { let mut pcid_handle = PcidServerHandle::connect_default()?; // Double check that we have the right device. @@ -13,7 +160,36 @@ fn deamon(deamon: redox_daemon::Daemon) -> Result<(), Error> { log::info!("virtio-gpu: initiating startup sequence :^)"); let device = virtio_core::probe_device(&mut pcid_handle)?; - loop {} + + // Negotiate features. + device.transport.finalize_features(); + + // Queue for sending control commands. + let control_queue = device + .transport + .setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + // Queue for sending cursor updates. + let cursor_queue = device + .transport + .setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?; + + device.transport.run_device(); + deamon.ready().unwrap(); + + let mut socket_file = File::create(":display/virtio-gpu")?; + let mut scheme = scheme::Display::new(control_queue, cursor_queue); + + loop { + let mut packet = Packet::default(); + socket_file + .read(&mut packet) + .expect("virtio-gpud: failed to read disk scheme"); + scheme.handle(&mut packet); + socket_file + .write(&packet) + .expect("virtio-gpud: failed to read disk scheme"); + } } fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs new file mode 100644 index 0000000000..7a95d4c4ee --- /dev/null +++ b/virtio-gpud/src/scheme.rs @@ -0,0 +1,108 @@ +use std::{ + collections::BTreeMap, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; + +use orbclient::Event; +use syscall::{Dma, Error as SysError, SchemeMut, EINVAL, EPERM}; +use virtio_core::{ + spec::{Buffer, ChainBuilder, DescriptorFlags}, + transport::{Error, Queue}, + utils::VolatileCell, +}; + +use crate::{CommandTy, ControlHeader, GetDisplayInfo}; + +pub enum Handle { + Input(InputHandle), + Screen(ScreenHandle), +} + +pub struct InputHandle {} +pub struct ScreenHandle { + id: usize, +} + +pub struct Display<'a> { + control_queue: Arc>, + cursor_queue: Arc>, + + display_id: usize, + handles: BTreeMap, + next_id: AtomicUsize, +} + +impl<'a> Display<'a> { + pub fn new(control_queue: Arc>, cursor_queue: Arc>) -> Self { + Self { + control_queue, + cursor_queue, + + display_id: 0, + handles: BTreeMap::new(), + next_id: AtomicUsize::new(0), + } + } + + async fn get_display_info(&self) -> Result, Error> { + let header = Dma::new(ControlHeader { + ty: VolatileCell::new(CommandTy::GetDisplayInfo), + ..Default::default() + })?; + + let response = Dma::new(GetDisplayInfo::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&header)) + .chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert!(response.header.ty.get() == CommandTy::RespOkDisplayInfo); + + Ok(response) + } +} + +impl<'a> SchemeMut for Display<'a> { + fn open(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> syscall::Result { + if path == "input" { + if uid != 0 { + return Err(SysError::new(EPERM)); + } + + let fd = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.insert(fd, Handle::Input(InputHandle {})); + + Ok(fd) + } else { + let mut parts = path.split('/'); + let screen = parts.next().unwrap_or("").split('.'); + dbg!(screen); + + todo!(); + } + } + + fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { + todo!() + } + + fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + match handle { + Handle::Input(_) => todo!(), + Handle::Screen(_) => { + let size = buf.len() / core::mem::size_of::(); + let events = + unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::(), size) }; + + dbg!(events); + todo!() + } + } + } +} From c0950d06867f4451a6fd59153049303857ff57bf Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 4 Jul 2023 13:17:58 +1000 Subject: [PATCH 02/11] virtio-gpu: start working on the scheme Signed-off-by: Anhad Singh --- Cargo.lock | 7 ++ virtio-gpud/Cargo.toml | 1 + virtio-gpud/src/main.rs | 190 +++++++++++++++++++++++++++++++++++++- virtio-gpud/src/scheme.rs | 165 +++++++++++++++++++++++++++++---- 4 files changed, 344 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb801dd351..afb50c966b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,6 +1011,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "paste" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" + [[package]] name = "paw" version = "1.0.0" @@ -1953,6 +1959,7 @@ dependencies = [ "futures", "log", "orbclient", + "paste", "pcid", "redox-daemon", "redox_syscall 0.3.5", diff --git a/virtio-gpud/Cargo.toml b/virtio-gpud/Cargo.toml index 7a09708fc4..02249c294c 100644 --- a/virtio-gpud/Cargo.toml +++ b/virtio-gpud/Cargo.toml @@ -9,6 +9,7 @@ log = "0.4" static_assertions = "1.1.0" futures = { version = "0.3.28", features = ["executor"] } anyhow = "1.0.71" +paste = "1.0.13" virtio-core = { path = "../virtio-core" } pcid = { path = "../pcid" } diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index b989fa77d7..1527925b04 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -3,7 +3,7 @@ //! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support //! on the host machine. -#![feature(async_closure)] +#![feature(int_roundings)] use std::fs::File; use std::io::{Read, Write}; @@ -19,6 +19,28 @@ mod scheme; // const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0; const VIRTIO_GPU_MAX_SCANOUTS: usize = 16; +macro_rules! make_getter_setter { + ($($field:ident: $return_ty:ty),*) => { + $( + pub fn $field(&self) -> $return_ty { + self.$field.get() + } + + paste::item! { + pub fn [](&mut self, value: $return_ty) { + self.$field.set(value) + } + } + )* + }; + + (@$field:ident: $return_ty:ty) => { + pub fn $field(&mut self, value: $return_ty) { + self.$field.set(value) + } + }; +} + #[repr(C)] pub struct GpuConfig { /// Signals pending events to the driver. @@ -120,6 +142,28 @@ pub struct GpuRect { pub height: VolatileCell, } +impl GpuRect { + pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self { + Self { + x: VolatileCell::new(x), + y: VolatileCell::new(y), + width: VolatileCell::new(width), + height: VolatileCell::new(height), + } + } + + #[inline] + pub fn width(&self) -> u32 { + self.width.get() + } + + #[inline] + pub fn height(&self) -> u32 { + self.height.get() + } +} + + #[derive(Debug)] #[repr(C)] pub struct DisplayInfo { @@ -148,6 +192,150 @@ impl Default for GetDisplayInfo { } } +#[derive(Debug, Copy, Clone)] +#[repr(u32)] +pub enum ResourceFormat { + Unknown = 0, + + Bgrx = 2, + Xrgb = 4, +} + +#[derive(Debug)] +#[repr(C)] +pub struct ResourceCreate2d { + pub header: ControlHeader, + + resource_id: VolatileCell, + format: VolatileCell, + width: VolatileCell, + height: VolatileCell, +} + +impl ResourceCreate2d { + make_getter_setter!(resource_id: u32, format: ResourceFormat, width: u32, height: u32); +} + +impl Default for ResourceCreate2d { + fn default() -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::ResourceCreate2d), + ..Default::default() + }, + + resource_id: VolatileCell::new(0), + format: VolatileCell::new(ResourceFormat::Unknown), + width: VolatileCell::new(0), + height: VolatileCell::new(0), + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct MemEntry { + pub address: u64, + pub length: u32, + pub padding: u32 +} + +#[derive(Debug)] +#[repr(C)] +pub struct AttachBacking { + pub header: ControlHeader, + pub resource_id: u32, + pub num_entries: u32 +} + +impl AttachBacking { + pub fn new(resource_id: u32, num_entries: u32) -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::ResourceAttachBacking), + ..Default::default() + }, + resource_id, + num_entries + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct ResourceFlush { + pub header: ControlHeader, + pub rect: GpuRect, + pub resource_id: u32, + pub padding: u32 +} + +impl ResourceFlush { + pub fn new(resource_id: u32, rect: GpuRect) -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::ResourceFlush), + ..Default::default() + }, + + rect, + resource_id, + padding: 0 + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct SetScanout { + pub header: ControlHeader, + pub rect: GpuRect, + pub scanout_id: u32, + pub resource_id: u32, +} + +impl SetScanout { + pub fn new(scanout_id: u32, resource_id: u32, rect: GpuRect) -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::SetScanout), + ..Default::default() + }, + + rect, + scanout_id, + resource_id, + } + } +} + + +#[derive(Debug)] +#[repr(C)] +pub struct XferToHost2d { + pub header: ControlHeader, + pub rect: GpuRect, + pub offset: u64, + pub resource_id: u32, + pub padding: u32, +} + +impl XferToHost2d { + pub fn new(resource_id: u32, rect: GpuRect) -> Self { + Self { + header: ControlHeader { + ty: VolatileCell::new(CommandTy::TransferToHost2d), + ..Default::default() + }, + + rect, + resource_id, + offset: 0, + padding: 0, + } + } +} + fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { let mut pcid_handle = PcidServerHandle::connect_default()?; diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index 7a95d4c4ee..decd2b40f5 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -14,16 +14,12 @@ use virtio_core::{ utils::VolatileCell, }; -use crate::{CommandTy, ControlHeader, GetDisplayInfo}; +use crate::*; pub enum Handle { - Input(InputHandle), - Screen(ScreenHandle), -} - -pub struct InputHandle {} -pub struct ScreenHandle { - id: usize, + Screen { + id: usize + }, } pub struct Display<'a> { @@ -64,29 +60,163 @@ impl<'a> Display<'a> { Ok(response) } + + async fn get_resolution(&self) -> Result<(u32, u32), Error> { + let display_info = self.get_display_info().await?; + + let width = display_info.display_info[self.display_id].rect.width(); + let height = display_info.display_info[self.display_id].rect.height(); + + Ok((width, height)) + } + + async fn get_fpath(&mut self, buffer: &mut [u8]) -> syscall::Result { + let display_info = self.get_display_info().await.unwrap(); + + let width = display_info.display_info[self.display_id].rect.width(); + let height = display_info.display_info[self.display_id].rect.height(); + + let path = format!("display:0.0/{}/{}", width, height); + + // Copy the path into the target buffer. + buffer[..path.len()].copy_from_slice(path.as_bytes()); + Ok(path.len()) + } + + async fn send_request(&mut self, request: Dma) -> Result, Error> { + let header = Dma::new(ControlHeader::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&request)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + Ok(header) + } + + async fn flush_resource(&mut self, flush: ResourceFlush) -> Result<(), Error> { + let header = self.send_request(Dma::new(flush)?).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + Ok(()) + } + + async fn map_screen(&mut self, offset: usize, size: usize) -> Result { + // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. + let (width, height) = self.get_resolution().await?; + let mut request = Dma::new(ResourceCreate2d::default())?; + + request.set_width(width); + request.set_height(height); + request.set_format(ResourceFormat::Bgrx); + request.set_resource_id(1); // FIXME(andypython): dynamically allocate resource identifiers + + self.send_request(request).await?; + + // Allocate a framebuffer from guest ram, and attach it as backing storage to the + // resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. Scatter + // lists are supported, so the framebuffer doesn’t need to be contignous in guest + // physical memory. + let bpp = 32; + let fb_size = (width as usize * height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); + let mut entries = unsafe { Dma::zeroed_unsized(fb_size / syscall::PAGE_SIZE) }?; + + for i in 0..fb_size / syscall::PAGE_SIZE { + let address = unsafe { syscall::physalloc(syscall::PAGE_SIZE) }? as u64; + let mapped = unsafe {syscall::physmap(address as usize, syscall::PAGE_SIZE, syscall::PhysmapFlags::PHYSMAP_WRITE)}?; + unsafe { + core::ptr::write_bytes(mapped as *mut u8, 69, syscall::PAGE_SIZE); + } + + let entry = MemEntry { + address, + length: syscall::PAGE_SIZE as u32, + padding: 0, + }; + + entries[i]= entry; + } + + let attach_request = Dma::new(AttachBacking::new(1, entries.len() as u32))?; + let header = Dma::new(ControlHeader::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&attach_request)) + .chain(Buffer::new_unsized(&entries)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + let rect = GpuRect::new(0,0,width,height); + let scanout_request = Dma::new(SetScanout::new(0, 1, rect))?; + let header = self.send_request(scanout_request).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + let rect = GpuRect::new(0,0,width,height); + let req = Dma::new(XferToHost2d::new(1, rect))?; + let header = self.send_request(req).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + let rect = GpuRect::new(0,0,width,height); + self.flush_resource(ResourceFlush::new(1, rect)).await?; + todo!() + } } impl<'a> SchemeMut for Display<'a> { - fn open(&mut self, path: &str, flags: usize, uid: u32, gid: u32) -> syscall::Result { + fn open(&mut self, path: &str, _flags: usize, uid: u32, _gid: u32) -> syscall::Result { if path == "input" { if uid != 0 { return Err(SysError::new(EPERM)); } - let fd = self.next_id.fetch_add(1, Ordering::SeqCst); - self.handles.insert(fd, Handle::Input(InputHandle {})); - - Ok(fd) + unimplemented!("input is only supported via `display/vesa:input`") } else { let mut parts = path.split('/'); - let screen = parts.next().unwrap_or("").split('.'); - dbg!(screen); + let mut screen = parts.next().unwrap_or("").split('.'); - todo!(); + let vt_index = screen.next().unwrap_or("").parse::().unwrap_or(1); + let id = screen.next().unwrap_or("").parse::().unwrap_or(0); + + dbg!(&vt_index, &id); + + let fd = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.insert(fd, Handle::Screen { id }); + + Ok(fd) } } + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + let bytes_copied = match handle { + Handle::Screen { .. } => { + futures::executor::block_on(self.get_fpath(buf))? + } + }; + + Ok(bytes_copied) + } + + fn fmap(&mut self, id: usize, map: &syscall::Map) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + if let Handle::Screen { .. } = handle { + futures::executor::block_on(self.map_screen(map.offset, map.size)); + } + + unreachable!() + } + fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + let path = match handle { + Handle::Screen { .. } => { + futures::executor::block_on(self.get_fpath(buf))? + } + }; + todo!() } @@ -94,8 +224,7 @@ impl<'a> SchemeMut for Display<'a> { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; match handle { - Handle::Input(_) => todo!(), - Handle::Screen(_) => { + Handle::Screen { .. } => { let size = buf.len() / core::mem::size_of::(); let events = unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::(), size) }; From caa435eae3a2ed9a21431f349e910efad5ae44f6 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 4 Jul 2023 13:21:41 +1000 Subject: [PATCH 03/11] virtio_gpud: able to get orbital running :^) Signed-off-by: Anhad Singh --- virtio-gpud/src/main.rs | 20 ++-- virtio-gpud/src/scheme.rs | 190 +++++++++++++++++++++++++------------- 2 files changed, 136 insertions(+), 74 deletions(-) diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index 1527925b04..15fd0509ea 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -163,7 +163,6 @@ impl GpuRect { } } - #[derive(Debug)] #[repr(C)] pub struct DisplayInfo { @@ -234,10 +233,10 @@ impl Default for ResourceCreate2d { #[derive(Debug)] #[repr(C)] -pub struct MemEntry { - pub address: u64, - pub length: u32, - pub padding: u32 +pub struct MemEntry { + pub address: u64, + pub length: u32, + pub padding: u32, } #[derive(Debug)] @@ -245,7 +244,7 @@ pub struct MemEntry { pub struct AttachBacking { pub header: ControlHeader, pub resource_id: u32, - pub num_entries: u32 + pub num_entries: u32, } impl AttachBacking { @@ -256,7 +255,7 @@ impl AttachBacking { ..Default::default() }, resource_id, - num_entries + num_entries, } } } @@ -267,7 +266,7 @@ pub struct ResourceFlush { pub header: ControlHeader, pub rect: GpuRect, pub resource_id: u32, - pub padding: u32 + pub padding: u32, } impl ResourceFlush { @@ -277,10 +276,10 @@ impl ResourceFlush { ty: VolatileCell::new(CommandTy::ResourceFlush), ..Default::default() }, - + rect, resource_id, - padding: 0 + padding: 0, } } } @@ -309,7 +308,6 @@ impl SetScanout { } } - #[derive(Debug)] #[repr(C)] pub struct XferToHost2d { diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index decd2b40f5..0dbec3326f 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -17,9 +17,7 @@ use virtio_core::{ use crate::*; pub enum Handle { - Screen { - id: usize - }, + Screen { id: usize }, } pub struct Display<'a> { @@ -29,6 +27,7 @@ pub struct Display<'a> { display_id: usize, handles: BTreeMap, next_id: AtomicUsize, + mapped: Option, } impl<'a> Display<'a> { @@ -40,6 +39,7 @@ impl<'a> Display<'a> { display_id: 0, handles: BTreeMap::new(), next_id: AtomicUsize::new(0), + mapped: None, } } @@ -76,7 +76,7 @@ impl<'a> Display<'a> { let width = display_info.display_info[self.display_id].rect.width(); let height = display_info.display_info[self.display_id].rect.height(); - let path = format!("display:0.0/{}/{}", width, height); + let path = format!("display:3.0/{}/{}", width, height); // Copy the path into the target buffer. buffer[..path.len()].copy_from_slice(path.as_bytes()); @@ -84,7 +84,7 @@ impl<'a> Display<'a> { } async fn send_request(&mut self, request: Dma) -> Result, Error> { - let header = Dma::new(ControlHeader::default())?; + let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() .chain(Buffer::new(&request)) .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) @@ -95,16 +95,16 @@ impl<'a> Display<'a> { } async fn flush_resource(&mut self, flush: ResourceFlush) -> Result<(), Error> { - let header = self.send_request(Dma::new(flush)?).await?; + let header = self.send_request(Dma::new(flush)?).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); Ok(()) } - async fn map_screen(&mut self, offset: usize, size: usize) -> Result { + async fn map_screen(&mut self, offset: usize) -> Result { // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. let (width, height) = self.get_resolution().await?; - let mut request = Dma::new(ResourceCreate2d::default())?; + let mut request = Dma::new(ResourceCreate2d::default())?; request.set_width(width); request.set_height(height); @@ -113,54 +113,63 @@ impl<'a> Display<'a> { self.send_request(request).await?; - // Allocate a framebuffer from guest ram, and attach it as backing storage to the - // resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. Scatter - // lists are supported, so the framebuffer doesn’t need to be contignous in guest - // physical memory. + // Allocate a framebuffer from guest ram, and attach it as backing storage to the + // resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. Scatter + // lists are supported, so the framebuffer doesn’t need to be contignous in guest + // physical memory. let bpp = 32; - let fb_size = (width as usize * height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); - let mut entries = unsafe { Dma::zeroed_unsized(fb_size / syscall::PAGE_SIZE) }?; + let fb_size = + (width as usize * height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); + let address = unsafe { syscall::physalloc(fb_size) }? as u64; + let mapped = unsafe { + syscall::physmap( + address as usize, + fb_size, + syscall::PhysmapFlags::PHYSMAP_WRITE, + ) + }?; - for i in 0..fb_size / syscall::PAGE_SIZE { - let address = unsafe { syscall::physalloc(syscall::PAGE_SIZE) }? as u64; - let mapped = unsafe {syscall::physmap(address as usize, syscall::PAGE_SIZE, syscall::PhysmapFlags::PHYSMAP_WRITE)}?; - unsafe { - core::ptr::write_bytes(mapped as *mut u8, 69, syscall::PAGE_SIZE); - } - - let entry = MemEntry { - address, - length: syscall::PAGE_SIZE as u32, - padding: 0, - }; - - entries[i]= entry; + unsafe { + core::ptr::write_bytes(mapped as *mut u8, 255, fb_size); } - let attach_request = Dma::new(AttachBacking::new(1, entries.len() as u32))?; - let header = Dma::new(ControlHeader::default())?; + let entry = Dma::new(MemEntry { + address, + length: fb_size as u32, + padding: 0, + })?; + + let attach_request = Dma::new(AttachBacking::new(1, 1))?; + let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() .chain(Buffer::new(&attach_request)) - .chain(Buffer::new_unsized(&entries)) + .chain(Buffer::new(&entry)) .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) .build(); self.control_queue.send(command).await; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + self.flush().await?; + self.mapped = Some(mapped); + Ok(mapped + offset) + } - let rect = GpuRect::new(0,0,width,height); + async fn flush(&mut self) -> Result<(), Error> { + let (width, height) = self.get_resolution().await?; + + let rect = GpuRect::new(0, 0, width, height); let scanout_request = Dma::new(SetScanout::new(0, 1, rect))?; let header = self.send_request(scanout_request).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0,0,width,height); + let rect = GpuRect::new(0, 0, width, height); let req = Dma::new(XferToHost2d::new(1, rect))?; let header = self.send_request(req).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0,0,width,height); + let rect = GpuRect::new(0, 0, width, height); self.flush_resource(ResourceFlush::new(1, rect)).await?; - todo!() + Ok(()) } } @@ -179,59 +188,114 @@ impl<'a> SchemeMut for Display<'a> { let vt_index = screen.next().unwrap_or("").parse::().unwrap_or(1); let id = screen.next().unwrap_or("").parse::().unwrap_or(0); + if id != self.display_id { + return Err(SysError::new(syscall::ENOENT)); + } + dbg!(&vt_index, &id); let fd = self.next_id.fetch_add(1, Ordering::SeqCst); self.handles.insert(fd, Handle::Screen { id }); - + Ok(fd) } } + fn dup(&mut self, _old_id: usize, _buf: &[u8]) -> syscall::Result { + todo!() + } + + fn fevent( + &mut self, + _id: usize, + _flags: syscall::EventFlags, + ) -> syscall::Result { + log::warn!("fevent is a stub!"); + Ok(syscall::EventFlags::empty()) + } + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; let bytes_copied = match handle { - Handle::Screen { .. } => { - futures::executor::block_on(self.get_fpath(buf))? - } + Handle::Screen { .. } => futures::executor::block_on(self.get_fpath(buf))?, }; Ok(bytes_copied) } + fn fmap_old(&mut self, id: usize, map: &syscall::OldMap) -> syscall::Result { + self.fmap( + id, + &syscall::Map { + offset: map.offset, + size: map.size, + flags: map.flags, + address: 0, + }, + ) + } + fn fmap(&mut self, id: usize, map: &syscall::Map) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - if let Handle::Screen { .. } = handle { - futures::executor::block_on(self.map_screen(map.offset, map.size)); - } - - unreachable!() - } - - fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { - let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - let path = match handle { + let a = match handle { Handle::Screen { .. } => { - futures::executor::block_on(self.get_fpath(buf))? + if let Some(mapped) = self.mapped { + // already mapped + mapped + map.offset + } else { + // create the resource + futures::executor::block_on(self.map_screen(map.offset)).unwrap() + } } }; + Ok(a) + } + fn fsync(&mut self, id: usize) -> syscall::Result { + let _handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + futures::executor::block_on(self.flush()).unwrap(); + + Ok(0) + } + + fn read(&mut self, _id: usize, _buf: &mut [u8]) -> syscall::Result { + // TODO: figure out how to get input lol + log::warn!("virtio_gpu::read is a stub!"); + Ok(0) + } + + fn write(&mut self, _id: usize, buf: &[u8]) -> syscall::Result { + // let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + // match handle { + // Handle::Screen { .. } => { + // let size = buf.len() / core::mem::size_of::(); + // let events = + // unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::(), size) }; + + // dbg!(events); + // todo!() + // } + // } + + // SAFETY: lmao + unsafe { + core::ptr::copy_nonoverlapping( + buf.as_ptr(), + self.mapped.unwrap() as *mut u8, + buf.len(), + ); + futures::executor::block_on(self.flush()).unwrap(); + } + Ok(buf.len()) + } + + fn seek(&mut self, _id: usize, _pos: isize, _whence: usize) -> syscall::Result { todo!() } - fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { - let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - - match handle { - Handle::Screen { .. } => { - let size = buf.len() / core::mem::size_of::(); - let events = - unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::(), size) }; - - dbg!(events); - todo!() - } - } + fn close(&mut self, _id: usize) -> syscall::Result { + Ok(0) } } From 2f2720263f653425463d3e705156ead5e3a474a9 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 5 Jul 2023 10:52:33 +1000 Subject: [PATCH 04/11] drivers: add `inputd` Take out the input coalescing from `vesad` into `inputd`. Signed-off-by: Anhad Singh --- Cargo.lock | 15 ++- Cargo.toml | 1 + fmt.sh | 28 ++--- inputd/Cargo.toml | 12 ++ inputd/src/main.rs | 224 +++++++++++++++++++++++++++++++++++ ps2d/src/main.rs | 4 +- virtio-core/src/transport.rs | 4 +- 7 files changed, 268 insertions(+), 20 deletions(-) create mode 100644 inputd/Cargo.toml create mode 100644 inputd/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index afb50c966b..9960ef6c71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,6 +674,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inputd" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "redox-daemon", + "redox-log", + "redox_syscall 0.3.5", +] + [[package]] name = "instant" version = "0.1.12" @@ -742,9 +753,9 @@ checksum = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "link-cplusplus" diff --git a/Cargo.toml b/Cargo.toml index 2ccefbc9b4..b992570d25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "usbctl", "usbhidd", "usbscsid", + "inputd", "virtio-blkd", "virtio-netd", diff --git a/fmt.sh b/fmt.sh index 450e518f26..c9f29df18f 100755 --- a/fmt.sh +++ b/fmt.sh @@ -1,17 +1,17 @@ #!/usr/bin/bash -pushd virtio-core -cargo fmt -popd +function fmt() { + for dir in "$@" + do + pushd $dir + printf "\e[1;32mFormatting\e[0m $dir\n" + cargo fmt + popd + done +} -pushd virtio-netd -cargo fmt -popd - -pushd virtio-blkd -cargo fmt -popd - -pushd virtio-gpud -cargo fmt -popd +fmt virtio-core \ + virtio-netd \ + virtio-blkd \ + virtio-gpud \ + inputd diff --git a/inputd/Cargo.toml b/inputd/Cargo.toml new file mode 100644 index 0000000000..75c1a048a5 --- /dev/null +++ b/inputd/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inputd" +version = "0.1.0" +edition = "2021" +authors = ["Anhad Singh "] + +[dependencies] +anyhow = "1.0.71" +log = "0.4.19" +redox-daemon = "0.1.0" +redox-log = "0.1.1" +redox_syscall = "0.3.5" diff --git a/inputd/src/main.rs b/inputd/src/main.rs new file mode 100644 index 0000000000..f349f6dec8 --- /dev/null +++ b/inputd/src/main.rs @@ -0,0 +1,224 @@ +//! `:input` +//! +//! A seperate scheme is required since all of the input from different input devices is required +//! to be combined into a single stream which is later going to be processed by the "consumer" +//! which usually is Orbital. +//! +//! ## Input Device ("producer") +//! Write events to `input:producer`. +//! +//! ## Input Consumer ("consumer") +//! Read events from `input:consumer`. Optionally, set the `EVENT_READ` flag to be notified when +//! events are available. + +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{Read, Write}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use syscall::{Error as SysError, EventFlags, Packet, SchemeMut, EINVAL}; + +#[derive(Debug)] +enum Handle { + Producer, + Consumer { + events: EventFlags, + pending: Vec, + notified: bool + }, +} + +impl Handle { + pub fn is_producer(&self) -> bool { + matches!(self, Handle::Producer) + } +} + +struct InputScheme { + handles: BTreeMap, + next_id: AtomicUsize, +} + +impl InputScheme { + pub fn new() -> Self { + Self { + next_id: AtomicUsize::new(0), + handles: BTreeMap::new(), + } + } +} + +impl SchemeMut for InputScheme { + fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result { + let handle_ty = match path { + "producer" => Handle::Producer, + "consumer" => Handle::Consumer { + events: EventFlags::empty(), + pending: Vec::new(), + notified: false + }, + + _ => unreachable!("inputd: invalid path {path}"), + }; + + log::info!("inputd: {path} channel has been opened"); + + let fd = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.insert(fd, handle_ty); + + Ok(fd) + } + + fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + if let Handle::Consumer { pending, .. } = handle { + let copy = core::cmp::min(pending.len(), buf.len()); + + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + + Ok(copy) + } else { + // A producer cannot read from the channel. + unreachable!() + } + } + + fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { + if buf.len() == 1 && buf[0] > 0xf4 { + return Ok(1); + } + + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + assert!(handle.is_producer()); + + for handle in self.handles.values_mut() { + match handle { + Handle::Consumer { + ref mut pending, ref mut notified, .. + } => { + pending.extend_from_slice(buf); + *notified = false; + }, + _ => continue, + } + } + + Ok(buf.len()) + } + + fn fevent( + &mut self, + id: usize, + flags: syscall::EventFlags, + ) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + + match handle { + Handle::Consumer { ref mut events, ref mut notified, .. } => { + *events = flags; + *notified = false;}, + _ => unreachable!(), + } + + Ok(EventFlags::empty()) + } + + fn close(&mut self, _id: usize) -> syscall::Result { + todo!() + } +} + +fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { + // Create the ":input" scheme. + let mut socket_file = File::create(":input")?; + let mut scheme = InputScheme::new(); + + deamon.ready().unwrap(); + + loop { + let mut should_handle = false; + let mut packet = Packet::default(); + socket_file.read(&mut packet)?; + + // The producer has written to the channel; the consumers should be notified. + if packet.a == syscall::SYS_WRITE { + should_handle = true; + } + + scheme.handle(&mut packet); + socket_file.write(&packet)?; + + if !should_handle { + continue; + } + + for (id, handle) in scheme.handles.iter_mut() { + if let Handle::Consumer { events, pending, ref mut notified } = handle { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + // Notify the consumer that we have some events to read. Yum yum. + let mut event_packet = Packet::default(); + event_packet.a = syscall::SYS_FEVENT; + event_packet.b = *id; + event_packet.c = EventFlags::EVENT_READ.bits(); + // Specifies the number of bytes that can be read non-blocking. + event_packet.d = pending.len(); + socket_file.write(&event_packet)?; + + *notified = true; + } + } + } +} + +fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { + deamon(redox_daemon).unwrap(); + unreachable!(); +} + +#[cfg(target_os = "redox")] +pub fn setup_logging(level: log::LevelFilter, name: &str) { + use redox_log::{OutputBuilder, RedoxLogger}; + + let mut logger = RedoxLogger::new().with_output( + OutputBuilder::stderr() + .with_filter(level) + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ); + + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.log")) { + Ok(builder) => { + logger = logger.with_output(builder.with_filter(level).flush_on_newline(true).build()) + } + Err(err) => eprintln!("inputd: failed to create log: {}", err), + } + + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.ansi.log")) { + Ok(builder) => { + logger = logger.with_output( + builder + .with_filter(level) + .with_ansi_escape_codes() + .flush_on_newline(true) + .build(), + ) + } + Err(err) => eprintln!("inputd: failed to create ANSI log: {}", err), + } + + logger.enable().unwrap(); + log::info!("inputd: enabled logger"); +} + +pub fn main() { + #[cfg(target_os = "redox")] + setup_logging(log::LevelFilter::Trace, "inputd"); + redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); +} diff --git a/ps2d/src/main.rs b/ps2d/src/main.rs index 96df72a2b0..f9cdba3b6b 100644 --- a/ps2d/src/main.rs +++ b/ps2d/src/main.rs @@ -45,8 +45,8 @@ fn daemon(daemon: redox_daemon::Daemon) -> ! { let input = OpenOptions::new() .write(true) - .open("display/vesa:input") - .expect("ps2d: failed to open display:input"); + .open("input:producer") + .expect("ps2d: failed to open input:producer"); let mut event_file = OpenOptions::new() .read(true) diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index 5bcc693f59..bc56de2149 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -252,7 +252,7 @@ impl<'a> Available<'a> { self.ring .elements .as_slice(self.queue_size) - .get(index % 256) + .get(index % self.queue_size) .expect("virtio-core::available: index out of bounds") } } @@ -318,7 +318,7 @@ impl<'a> Used<'a> { self.ring .elements .as_slice(self.queue_size) - .get(index % 256) + .get(index % self.queue_size) .expect("virtio-core::used: index out of bounds") } } From 07039e8321faaf3cbcea8db879bc14973ac1ce86 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 5 Jul 2023 11:14:06 +1000 Subject: [PATCH 05/11] drivers: run `fmt.sh` Signed-off-by: Anhad Singh --- inputd/src/main.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/inputd/src/main.rs b/inputd/src/main.rs index f349f6dec8..c16cf90325 100644 --- a/inputd/src/main.rs +++ b/inputd/src/main.rs @@ -1,12 +1,12 @@ //! `:input` -//! +//! //! A seperate scheme is required since all of the input from different input devices is required //! to be combined into a single stream which is later going to be processed by the "consumer" //! which usually is Orbital. -//! +//! //! ## Input Device ("producer") //! Write events to `input:producer`. -//! +//! //! ## Input Consumer ("consumer") //! Read events from `input:consumer`. Optionally, set the `EVENT_READ` flag to be notified when //! events are available. @@ -24,7 +24,7 @@ enum Handle { Consumer { events: EventFlags, pending: Vec, - notified: bool + notified: bool, }, } @@ -55,7 +55,7 @@ impl SchemeMut for InputScheme { "consumer" => Handle::Consumer { events: EventFlags::empty(), pending: Vec::new(), - notified: false + notified: false, }, _ => unreachable!("inputd: invalid path {path}"), @@ -97,11 +97,13 @@ impl SchemeMut for InputScheme { for handle in self.handles.values_mut() { match handle { Handle::Consumer { - ref mut pending, ref mut notified, .. + ref mut pending, + ref mut notified, + .. } => { pending.extend_from_slice(buf); *notified = false; - }, + } _ => continue, } } @@ -117,9 +119,14 @@ impl SchemeMut for InputScheme { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; match handle { - Handle::Consumer { ref mut events, ref mut notified, .. } => { + Handle::Consumer { + ref mut events, + ref mut notified, + .. + } => { *events = flags; - *notified = false;}, + *notified = false; + } _ => unreachable!(), } @@ -156,7 +163,12 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { } for (id, handle) in scheme.handles.iter_mut() { - if let Handle::Consumer { events, pending, ref mut notified } = handle { + if let Handle::Consumer { + events, + pending, + ref mut notified, + } = handle + { if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { continue; } @@ -167,7 +179,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { event_packet.b = *id; event_packet.c = EventFlags::EVENT_READ.bits(); // Specifies the number of bytes that can be read non-blocking. - event_packet.d = pending.len(); + event_packet.d = pending.len(); socket_file.write(&event_packet)?; *notified = true; From 30146fa81dccce7da5352373a02f34bdec001266 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 5 Jul 2023 13:44:35 +1000 Subject: [PATCH 06/11] virtio-gpu: update orbital patch Signed-off-by: Anhad Singh --- virtio-gpud/patches/01-orbital.patch | 45 ++++++++++++++++++++++++++++ virtio-gpud/src/scheme.rs | 1 - 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/virtio-gpud/patches/01-orbital.patch b/virtio-gpud/patches/01-orbital.patch index 493ec66735..0dc1b7c6dd 100644 --- a/virtio-gpud/patches/01-orbital.patch +++ b/virtio-gpud/patches/01-orbital.patch @@ -1,3 +1,48 @@ +diff --git a/src/core/mod.rs b/src/core/mod.rs +index 85337dd..a454579 100644 +--- a/src/core/mod.rs ++++ b/src/core/mod.rs +@@ -160,6 +160,9 @@ pub struct Orbital { + pub todo: Vec, + pub displays: Vec, + pub maps: BTreeMap, ++ ++ /// Handle to "input:consumer" to recieve input events. ++ pub input: File, + } + + impl Orbital { +@@ -259,6 +262,7 @@ impl Orbital { + todo: Vec::new(), + displays, + maps: BTreeMap::new(), ++ input: File::open("input:consumer")?, + }) + } + +@@ -294,6 +298,7 @@ impl Orbital { + + let scheme_fd = self.scheme.as_raw_fd(); + let display_fd = self.displays[0].file.as_raw_fd(); ++ let input_fd = self.input.as_raw_fd(); + + handler.handle_startup(&mut self)?; + +@@ -337,12 +342,12 @@ impl Orbital { + Ok(result) + })?; + +- event_queue.add(display_fd, move |_| -> io::Result> { ++ event_queue.add(input_fd, move |_| -> io::Result> { + let mut me = me2.borrow_mut(); + let me = &mut *me; + let mut events = [Event::new(); 16]; + loop { +- match read_to_slice(&mut me.orb.displays[0].file, &mut events)? { ++ match read_to_slice(&mut me.orb.input, &mut events)? { + 0 => break, + count => { + let events = &mut events[..count]; diff --git a/src/main.rs b/src/main.rs index 1cdf9cf..3859ac0 100644 --- a/src/main.rs diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index 0dbec3326f..f7d906cced 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -6,7 +6,6 @@ use std::{ }, }; -use orbclient::Event; use syscall::{Dma, Error as SysError, SchemeMut, EINVAL, EPERM}; use virtio_core::{ spec::{Buffer, ChainBuilder, DescriptorFlags}, From 320fedbd76e254b7c8c34e338a01020889b95b65 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 5 Jul 2023 17:49:08 +1000 Subject: [PATCH 07/11] virtio-gpud: get multiple displays to work Signed-off-by: Anhad Singh --- initfs.toml | 1 - virtio-gpud/src/main.rs | 13 ++- virtio-gpud/src/scheme.rs | 228 +++++++++++++++++++------------------- 3 files changed, 124 insertions(+), 118 deletions(-) diff --git a/initfs.toml b/initfs.toml index 631768911d..53e05f3432 100644 --- a/initfs.toml +++ b/initfs.toml @@ -45,7 +45,6 @@ use_channel = true [[drivers]] name = "virtio-gpu" class = 3 -subclass = 0 vendor = 6900 device = 4176 command = ["virtio-gpud"] diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index 15fd0509ea..ce3dc49a53 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -48,10 +48,17 @@ pub struct GpuConfig { /// Clears pending events in the device (write-to-clear). pub events_clear: VolatileCell, // write-only - pub min_scanouts: VolatileCell, + pub num_scanouts: VolatileCell, pub num_capsets: VolatileCell, } +impl GpuConfig { + #[inline] + pub fn num_scanouts(&self) -> u32 { + self.num_scanouts.get() + } +} + #[derive(Debug, Copy, Clone, PartialEq)] #[repr(u32)] pub enum CommandTy { @@ -346,6 +353,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { log::info!("virtio-gpu: initiating startup sequence :^)"); let device = virtio_core::probe_device(&mut pcid_handle)?; + let config = unsafe { &mut *(device.device_space as *mut GpuConfig) }; // Negotiate features. device.transport.finalize_features(); @@ -363,8 +371,9 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { device.transport.run_device(); deamon.ready().unwrap(); + let mut socket_file = File::create(":display/virtio-gpu")?; - let mut scheme = scheme::Display::new(control_queue, cursor_queue); + let mut scheme = scheme::Scheme::new(config, control_queue.clone(), cursor_queue.clone())?; loop { let mut packet = Packet::default(); diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index f7d906cced..fea65b1bf7 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -1,12 +1,12 @@ use std::{ collections::BTreeMap, sync::{ - atomic::{AtomicUsize, Ordering}, + atomic::{AtomicUsize, Ordering, AtomicU32}, Arc, }, }; -use syscall::{Dma, Error as SysError, SchemeMut, EINVAL, EPERM}; +use syscall::{Dma, Error as SysError, SchemeMut, EINVAL}; use virtio_core::{ spec::{Buffer, ChainBuilder, DescriptorFlags}, transport::{Error, Queue}, @@ -15,67 +15,41 @@ use virtio_core::{ use crate::*; -pub enum Handle { - Screen { id: usize }, -} +static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); pub struct Display<'a> { control_queue: Arc>, cursor_queue: Arc>, - display_id: usize, - handles: BTreeMap, - next_id: AtomicUsize, mapped: Option, + + width: u32, + height: u32, + + resource_id: u32, + id: usize, } impl<'a> Display<'a> { - pub fn new(control_queue: Arc>, cursor_queue: Arc>) -> Self { + pub fn new(control_queue: Arc>, cursor_queue: Arc>, display_info: & mut DisplayInfo, id: usize) -> Self { + display_info.enabled.set(1); + Self { control_queue, cursor_queue, - display_id: 0, - handles: BTreeMap::new(), - next_id: AtomicUsize::new(0), mapped: None, + + width: 1920, + height: 1080, + + id, + resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst) } } - async fn get_display_info(&self) -> Result, Error> { - let header = Dma::new(ControlHeader { - ty: VolatileCell::new(CommandTy::GetDisplayInfo), - ..Default::default() - })?; - - let response = Dma::new(GetDisplayInfo::default())?; - let command = ChainBuilder::new() - .chain(Buffer::new(&header)) - .chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY)) - .build(); - - self.control_queue.send(command).await; - assert!(response.header.ty.get() == CommandTy::RespOkDisplayInfo); - - Ok(response) - } - - async fn get_resolution(&self) -> Result<(u32, u32), Error> { - let display_info = self.get_display_info().await?; - - let width = display_info.display_info[self.display_id].rect.width(); - let height = display_info.display_info[self.display_id].rect.height(); - - Ok((width, height)) - } - - async fn get_fpath(&mut self, buffer: &mut [u8]) -> syscall::Result { - let display_info = self.get_display_info().await.unwrap(); - - let width = display_info.display_info[self.display_id].rect.width(); - let height = display_info.display_info[self.display_id].rect.height(); - - let path = format!("display:3.0/{}/{}", width, height); + async fn get_fpath(&mut self, buffer: &mut [u8]) -> Result { + let path = format!("display/virtio-gpu:3.0/{}/{}", self.width, self.height); // Copy the path into the target buffer. buffer[..path.len()].copy_from_slice(path.as_bytes()); @@ -101,14 +75,17 @@ impl<'a> Display<'a> { } async fn map_screen(&mut self, offset: usize) -> Result { + if let Some(mapped) = self.mapped { + return Ok(mapped + offset); + } + // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. - let (width, height) = self.get_resolution().await?; let mut request = Dma::new(ResourceCreate2d::default())?; - request.set_width(width); - request.set_height(height); + request.set_width(self.width); + request.set_height(self.height); request.set_format(ResourceFormat::Bgrx); - request.set_resource_id(1); // FIXME(andypython): dynamically allocate resource identifiers + request.set_resource_id(self.resource_id); self.send_request(request).await?; @@ -118,7 +95,7 @@ impl<'a> Display<'a> { // physical memory. let bpp = 32; let fb_size = - (width as usize * height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); + (self.width as usize * self.height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); let address = unsafe { syscall::physalloc(fb_size) }? as u64; let mapped = unsafe { syscall::physmap( @@ -138,7 +115,7 @@ impl<'a> Display<'a> { padding: 0, })?; - let attach_request = Dma::new(AttachBacking::new(1, 1))?; + let attach_request = Dma::new(AttachBacking::new(self.resource_id, 1))?; let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() .chain(Buffer::new(&attach_request)) @@ -154,50 +131,96 @@ impl<'a> Display<'a> { } async fn flush(&mut self) -> Result<(), Error> { - let (width, height) = self.get_resolution().await?; - - let rect = GpuRect::new(0, 0, width, height); - let scanout_request = Dma::new(SetScanout::new(0, 1, rect))?; + let rect = GpuRect::new(0, 0, self.width, self.height); + let scanout_request = Dma::new(SetScanout::new(self.id as u32, self.resource_id, rect))?; let header = self.send_request(scanout_request).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0, 0, width, height); - let req = Dma::new(XferToHost2d::new(1, rect))?; + let rect = GpuRect::new(0, 0, self.width, self.height); + let req = Dma::new(XferToHost2d::new(self.resource_id, rect))?; let header = self.send_request(req).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0, 0, width, height); - self.flush_resource(ResourceFlush::new(1, rect)).await?; + let rect = GpuRect::new(0, 0, self.width, self.height); + self.flush_resource(ResourceFlush::new(self.resource_id, rect)).await?; Ok(()) } } -impl<'a> SchemeMut for Display<'a> { - fn open(&mut self, path: &str, _flags: usize, uid: u32, _gid: u32) -> syscall::Result { - if path == "input" { - if uid != 0 { - return Err(SysError::new(EPERM)); +pub struct Scheme<'a> { + control_queue: Arc>, + cursor_queue: Arc>, + config: &'a mut GpuConfig, + /// File descriptor allocator. + next_id: AtomicUsize, + handles: BTreeMap> +} + +impl<'a> Scheme<'a> { + pub fn new(config: &'a mut GpuConfig, control_queue: Arc>, cursor_queue: Arc>) -> Result { + Ok( + Self { + control_queue, + cursor_queue, + config, + next_id: AtomicUsize::new(0), + handles: BTreeMap::new() } + ) + } - unimplemented!("input is only supported via `display/vesa:input`") - } else { - let mut parts = path.split('/'); - let mut screen = parts.next().unwrap_or("").split('.'); + async fn open_display(&self, id: usize) -> Result, Error> { + let mut display_info = self.get_display_info().await?; + let displays = &mut display_info.display_info[..self.config.num_scanouts() as usize]; - let vt_index = screen.next().unwrap_or("").parse::().unwrap_or(1); - let id = screen.next().unwrap_or("").parse::().unwrap_or(0); + let display = displays.get_mut (id).ok_or(SysError::new(syscall::ENOENT))?; - if id != self.display_id { - return Err(SysError::new(syscall::ENOENT)); - } - - dbg!(&vt_index, &id); - - let fd = self.next_id.fetch_add(1, Ordering::SeqCst); - self.handles.insert(fd, Handle::Screen { id }); - - Ok(fd) + log::info!("virtio-gpu: opening display ({}x{}px)", display.rect.width(), display.rect.height()); + let mut d = Display::new(self.control_queue.clone(), self.cursor_queue.clone(), display, id); + + if id != 0 { + d.map_screen(0).await?; } + + Ok( d) + } + + async fn get_display_info(&self) -> Result, Error> { + let header = Dma::new(ControlHeader { + ty: VolatileCell::new(CommandTy::GetDisplayInfo), + ..Default::default() + })?; + + let response = Dma::new(GetDisplayInfo::default())?; + let command = ChainBuilder::new() + .chain(Buffer::new(&header)) + .chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY)) + .build(); + + self.control_queue.send(command).await; + assert!(response.header.ty.get() == CommandTy::RespOkDisplayInfo); + + Ok(response) + } +} + +impl<'a> SchemeMut for Scheme<'a> { + fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result { + dbg!(&path); + + let mut parts = path.split('/'); + let mut screen = parts.next().unwrap_or("").split('.'); + + let vt_index = screen.next().unwrap_or("").parse::().unwrap_or(1); + let id = screen.next().unwrap_or("").parse::().unwrap_or(0); + + dbg!(&vt_index, &id); + + let fd = self.next_id.fetch_add(1, Ordering::SeqCst); + let display = futures::executor::block_on(self.open_display(id)).map_err(|_| SysError::new(syscall::ENOENT))?; + + self.handles.insert(fd, display); + Ok(fd) } fn dup(&mut self, _old_id: usize, _buf: &[u8]) -> syscall::Result { @@ -215,9 +238,7 @@ impl<'a> SchemeMut for Display<'a> { fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - let bytes_copied = match handle { - Handle::Screen { .. } => futures::executor::block_on(self.get_fpath(buf))?, - }; + let bytes_copied = futures::executor::block_on(handle.get_fpath(buf)).unwrap(); Ok(bytes_copied) } @@ -236,25 +257,12 @@ impl<'a> SchemeMut for Display<'a> { fn fmap(&mut self, id: usize, map: &syscall::Map) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - - let a = match handle { - Handle::Screen { .. } => { - if let Some(mapped) = self.mapped { - // already mapped - mapped + map.offset - } else { - // create the resource - futures::executor::block_on(self.map_screen(map.offset)).unwrap() - } - } - }; - Ok(a) + Ok(futures::executor::block_on(handle.map_screen(map.offset)).unwrap()) } fn fsync(&mut self, id: usize) -> syscall::Result { - let _handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - futures::executor::block_on(self.flush()).unwrap(); - + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + futures::executor::block_on(handle.flush()).unwrap(); Ok(0) } @@ -264,29 +272,19 @@ impl<'a> SchemeMut for Display<'a> { Ok(0) } - fn write(&mut self, _id: usize, buf: &[u8]) -> syscall::Result { - // let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - - // match handle { - // Handle::Screen { .. } => { - // let size = buf.len() / core::mem::size_of::(); - // let events = - // unsafe { core::slice::from_raw_parts(buf.as_ptr().cast::(), size) }; - - // dbg!(events); - // todo!() - // } - // } + fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; // SAFETY: lmao unsafe { core::ptr::copy_nonoverlapping( buf.as_ptr(), - self.mapped.unwrap() as *mut u8, + handle.mapped.unwrap() as *mut u8, buf.len(), ); - futures::executor::block_on(self.flush()).unwrap(); } + + futures::executor::block_on(handle.flush()).unwrap(); Ok(buf.len()) } From 7e83bd9adffbee6c97db55efc0ff17fc7677bfc5 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Wed, 5 Jul 2023 17:50:51 +1000 Subject: [PATCH 08/11] virtio-gpud: indicate the reservation of 0 resource_id Signed-off-by: Anhad Singh --- virtio-gpud/src/scheme.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index fea65b1bf7..2dd680fd48 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -15,7 +15,7 @@ use virtio_core::{ use crate::*; -static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); +static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`. pub struct Display<'a> { control_queue: Arc>, From 2cfd62d194f2a60c49020cb2db498368d20b9957 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 6 Jul 2023 17:38:44 +1000 Subject: [PATCH 09/11] remove patches from virtio-gpu/ Signed-off-by: Anhad Singh --- initfs.toml | 10 ---- virtio-gpud/patches/00-init.patch | 45 -------------- virtio-gpud/patches/01-orbital.patch | 58 ------------------- virtio-gpud/src/main.rs | 18 +++++- virtio-gpud/src/scheme.rs | 87 +++++++++++++++------------- virtio-netd/config.toml | 7 +++ 6 files changed, 71 insertions(+), 154 deletions(-) delete mode 100644 virtio-gpud/patches/00-init.patch delete mode 100644 virtio-gpud/patches/01-orbital.patch create mode 100644 virtio-netd/config.toml diff --git a/initfs.toml b/initfs.toml index 53e05f3432..d648d466ad 100644 --- a/initfs.toml +++ b/initfs.toml @@ -23,7 +23,6 @@ subclass = 8 command = ["nvmed"] use_channel = true -# -------------- virtio -------------- # [[drivers]] name = "virtio-blk" class = 1 @@ -33,15 +32,6 @@ device = 4097 command = ["virtio-blkd"] use_channel = true -[[drivers]] -name = "virtio-net" -class = 2 -subclass = 0 -vendor = 6900 -device = 4096 -command = ["virtio-netd"] -use_channel = true - [[drivers]] name = "virtio-gpu" class = 3 diff --git a/virtio-gpud/patches/00-init.patch b/virtio-gpud/patches/00-init.patch deleted file mode 100644 index dc20be1df6..0000000000 --- a/virtio-gpud/patches/00-init.patch +++ /dev/null @@ -1,45 +0,0 @@ -diff --git a/src/main.rs b/src/main.rs -index a99488a..ec97fdd 100644 ---- a/src/main.rs -+++ b/src/main.rs -@@ -78,6 +78,40 @@ pub fn run(file: &Path) -> Result<()> { - println!("init: failed to run: no argument"); - }, - "run.d" => if let Some(new_dir) = args.next() { -+ // On startup, the VESA display driver is started which basically makes use of the framebuffer -+ // provided by the firmware. The GPU device are latter started by `pcid` (such as `virtio-gpu`). -+ let mut devices = vec![]; -+ let schemes = std::fs::read_dir(":").unwrap(); -+ -+ for entry in schemes { -+ let path = entry.unwrap().path(); -+ let path_str = path -+ .into_os_string() -+ .into_string() -+ .expect("init: failed to convert path to string"); -+ -+ if path_str.contains("display") { -+ println!("init: found display scheme {}", path_str); -+ devices.push(path_str); -+ } -+ } -+ -+ let device = devices.iter().filter(|d| !d.contains("vesa")).collect::>(); -+ let device = if device.is_empty() { -+ // No GPU available, fallback to VESA display which *should* always be accessible via `display/vesa:`. -+ "vesa" -+ } else { -+ // Parts: -+ // :/display/virtio-gpu -+ // -+ // 1st: ":" -+ // 2nd: "display" -+ // 3rd: "virtio-gpu" (the one we want!) -+ device[0].split("/").nth(2).unwrap() -+ }; -+ -+ std::env::set_var("DISPLAY", &format!("display/{}:3/activate", device)); -+ - let mut entries = vec![]; - match read_dir(&new_dir) { - Ok(list) => for entry_res in list { diff --git a/virtio-gpud/patches/01-orbital.patch b/virtio-gpud/patches/01-orbital.patch deleted file mode 100644 index 0dc1b7c6dd..0000000000 --- a/virtio-gpud/patches/01-orbital.patch +++ /dev/null @@ -1,58 +0,0 @@ -diff --git a/src/core/mod.rs b/src/core/mod.rs -index 85337dd..a454579 100644 ---- a/src/core/mod.rs -+++ b/src/core/mod.rs -@@ -160,6 +160,9 @@ pub struct Orbital { - pub todo: Vec, - pub displays: Vec, - pub maps: BTreeMap, -+ -+ /// Handle to "input:consumer" to recieve input events. -+ pub input: File, - } - - impl Orbital { -@@ -259,6 +262,7 @@ impl Orbital { - todo: Vec::new(), - displays, - maps: BTreeMap::new(), -+ input: File::open("input:consumer")?, - }) - } - -@@ -294,6 +298,7 @@ impl Orbital { - - let scheme_fd = self.scheme.as_raw_fd(); - let display_fd = self.displays[0].file.as_raw_fd(); -+ let input_fd = self.input.as_raw_fd(); - - handler.handle_startup(&mut self)?; - -@@ -337,12 +342,12 @@ impl Orbital { - Ok(result) - })?; - -- event_queue.add(display_fd, move |_| -> io::Result> { -+ event_queue.add(input_fd, move |_| -> io::Result> { - let mut me = me2.borrow_mut(); - let me = &mut *me; - let mut events = [Event::new(); 16]; - loop { -- match read_to_slice(&mut me.orb.displays[0].file, &mut events)? { -+ match read_to_slice(&mut me.orb.input, &mut events)? { - 0 => break, - count => { - let events = &mut events[..count]; -diff --git a/src/main.rs b/src/main.rs -index 1cdf9cf..3859ac0 100644 ---- a/src/main.rs -+++ b/src/main.rs -@@ -63,7 +63,7 @@ fn orbital(daemon: Daemon) -> Result<(), String> { - .enable(); - - let mut args = env::args().skip(1); -- let display_path = args.next().ok_or("no display argument")?; -+ let display_path = env::var("DISPLAY").unwrap(); - let login_cmd = args.next().ok_or("no login manager argument")?; - - core::fix_env(&display_path).map_err(|_| "error setting env vars")?; diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index ce3dc49a53..f9671f2485 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -3,6 +3,23 @@ //! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support //! on the host machine. +// Notes for the future: +// +// `virtio-gpu` 2D acceleration is just blitting. 3D acceleration has 2 kinds: +// - virgl - OpenGL +// - venus - Vulkan +// +// The Venus driver requires support for the following from the `virtio-gpu` kernel driver: +// - VIRTGPU_PARAM_3D_FEATURES +// - VIRTGPU_PARAM_CAPSET_QUERY_FIX +// - VIRTGPU_PARAM_RESOURCE_BLOB +// - VIRTGPU_PARAM_HOST_VISIBLE +// - VIRTGPU_PARAM_CROSS_DEVICE +// - VIRTGPU_PARAM_CONTEXT_INIT +// +// cc https://docs.mesa3d.org/drivers/venus.html +// cc https://docs.mesa3d.org/drivers/virgl.html + #![feature(int_roundings)] use std::fs::File; @@ -371,7 +388,6 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { device.transport.run_device(); deamon.ready().unwrap(); - let mut socket_file = File::create(":display/virtio-gpu")?; let mut scheme = scheme::Scheme::new(config, control_queue.clone(), cursor_queue.clone())?; diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index 2dd680fd48..89c524a517 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -1,17 +1,13 @@ -use std::{ - collections::BTreeMap, - sync::{ - atomic::{AtomicUsize, Ordering, AtomicU32}, - Arc, - }, -}; +use std::collections::BTreeMap; + +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; +use std::sync::Arc; use syscall::{Dma, Error as SysError, SchemeMut, EINVAL}; -use virtio_core::{ - spec::{Buffer, ChainBuilder, DescriptorFlags}, - transport::{Error, Queue}, - utils::VolatileCell, -}; + +use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; +use virtio_core::transport::{Error, Queue}; +use virtio_core::utils::VolatileCell; use crate::*; @@ -31,9 +27,12 @@ pub struct Display<'a> { } impl<'a> Display<'a> { - pub fn new(control_queue: Arc>, cursor_queue: Arc>, display_info: & mut DisplayInfo, id: usize) -> Self { - display_info.enabled.set(1); - + pub fn new( + control_queue: Arc>, + cursor_queue: Arc>, + _display_info: &mut DisplayInfo, + id: usize, + ) -> Self { Self { control_queue, cursor_queue, @@ -44,7 +43,7 @@ impl<'a> Display<'a> { height: 1080, id, - resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst) + resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst), } } @@ -94,8 +93,8 @@ impl<'a> Display<'a> { // lists are supported, so the framebuffer doesn’t need to be contignous in guest // physical memory. let bpp = 32; - let fb_size = - (self.width as usize * self.height as usize * bpp / 8).next_multiple_of(syscall::PAGE_SIZE); + let fb_size = (self.width as usize * self.height as usize * bpp / 8) + .next_multiple_of(syscall::PAGE_SIZE); let address = unsafe { syscall::physalloc(fb_size) }? as u64; let mapped = unsafe { syscall::physmap( @@ -142,7 +141,8 @@ impl<'a> Display<'a> { assert_eq!(header.ty.get(), CommandTy::RespOkNodata); let rect = GpuRect::new(0, 0, self.width, self.height); - self.flush_resource(ResourceFlush::new(self.resource_id, rect)).await?; + self.flush_resource(ResourceFlush::new(self.resource_id, rect)) + .await?; Ok(()) } } @@ -153,36 +153,42 @@ pub struct Scheme<'a> { config: &'a mut GpuConfig, /// File descriptor allocator. next_id: AtomicUsize, - handles: BTreeMap> + handles: BTreeMap>, } impl<'a> Scheme<'a> { - pub fn new(config: &'a mut GpuConfig, control_queue: Arc>, cursor_queue: Arc>) -> Result { - Ok( - Self { - control_queue, - cursor_queue, - config, - next_id: AtomicUsize::new(0), - handles: BTreeMap::new() - } - ) + pub fn new( + config: &'a mut GpuConfig, + control_queue: Arc>, + cursor_queue: Arc>, + ) -> Result { + Ok(Self { + control_queue, + cursor_queue, + config, + next_id: AtomicUsize::new(0), + handles: BTreeMap::new(), + }) } async fn open_display(&self, id: usize) -> Result, Error> { let mut display_info = self.get_display_info().await?; let displays = &mut display_info.display_info[..self.config.num_scanouts() as usize]; - let display = displays.get_mut (id).ok_or(SysError::new(syscall::ENOENT))?; + let display = displays.get_mut(id).ok_or(SysError::new(syscall::ENOENT))?; - log::info!("virtio-gpu: opening display ({}x{}px)", display.rect.width(), display.rect.height()); - let mut d = Display::new(self.control_queue.clone(), self.cursor_queue.clone(), display, id); - - if id != 0 { - d.map_screen(0).await?; - } + log::info!( + "virtio-gpu: opening display ({}x{}px)", + display.rect.width(), + display.rect.height() + ); - Ok( d) + Ok(Display::new( + self.control_queue.clone(), + self.cursor_queue.clone(), + display, + id, + )) } async fn get_display_info(&self) -> Result, Error> { @@ -207,7 +213,7 @@ impl<'a> Scheme<'a> { impl<'a> SchemeMut for Scheme<'a> { fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result { dbg!(&path); - + let mut parts = path.split('/'); let mut screen = parts.next().unwrap_or("").split('.'); @@ -217,7 +223,8 @@ impl<'a> SchemeMut for Scheme<'a> { dbg!(&vt_index, &id); let fd = self.next_id.fetch_add(1, Ordering::SeqCst); - let display = futures::executor::block_on(self.open_display(id)).map_err(|_| SysError::new(syscall::ENOENT))?; + let display = futures::executor::block_on(self.open_display(id)) + .map_err(|_| SysError::new(syscall::ENOENT))?; self.handles.insert(fd, display); Ok(fd) diff --git a/virtio-netd/config.toml b/virtio-netd/config.toml new file mode 100644 index 0000000000..93949ce905 --- /dev/null +++ b/virtio-netd/config.toml @@ -0,0 +1,7 @@ +[[drivers]] +name = "virtio-net" +class = 2 +vendor = 6900 +device = 4096 +command = ["virtio-netd"] +use_channel = true From 5c4e7b36fad8d3f75ca2363873d8aca6eb3253b5 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Fri, 7 Jul 2023 16:51:27 +1000 Subject: [PATCH 10/11] make getty work Signed-off-by: Anhad Singh --- Cargo.lock | 4 + inputd/Cargo.toml | 1 + inputd/src/lib.rs | 25 ++++ inputd/src/main.rs | 116 +++++++++++++++++-- vesad/Cargo.toml | 1 + vesad/src/scheme.rs | 16 ++- virtio-core/src/transport.rs | 5 + virtio-gpud/Cargo.toml | 2 + virtio-gpud/src/main.rs | 101 +++++++++++----- virtio-gpud/src/scheme.rs | 215 ++++++++++++++++++++++++----------- 10 files changed, 375 insertions(+), 111 deletions(-) create mode 100644 inputd/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9960ef6c71..517d78f2bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,7 @@ version = "0.1.0" dependencies = [ "anyhow", "log", + "orbclient", "redox-daemon", "redox-log", "redox_syscall 0.3.5", @@ -1921,6 +1922,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vesad" version = "0.1.0" dependencies = [ + "inputd", "orbclient", "ransid", "redox-daemon", @@ -1968,12 +1970,14 @@ version = "0.1.0" dependencies = [ "anyhow", "futures", + "inputd", "log", "orbclient", "paste", "pcid", "redox-daemon", "redox_syscall 0.3.5", + "spin", "static_assertions", "virtio-core", ] diff --git a/inputd/Cargo.toml b/inputd/Cargo.toml index 75c1a048a5..dfc537236e 100644 --- a/inputd/Cargo.toml +++ b/inputd/Cargo.toml @@ -10,3 +10,4 @@ log = "0.4.19" redox-daemon = "0.1.0" redox-log = "0.1.1" redox_syscall = "0.3.5" +orbclient = "0.3.27" diff --git a/inputd/src/lib.rs b/inputd/src/lib.rs new file mode 100644 index 0000000000..9ad4ae5876 --- /dev/null +++ b/inputd/src/lib.rs @@ -0,0 +1,25 @@ +use std::fs::File; +use std::io::{Error, Read}; + +pub struct Handle(File); + +impl Handle { + pub fn new>(device_name: S) -> Result { + let path = format!("input:handle/{}", device_name.into()); + Ok(Self(File::open(path)?)) + } + + // The return value is the display identifier. It will be used to uniquely + // identify the display on activation events. + pub fn register(&mut self) -> Result { + Ok(dbg!(self.0.read(&mut [])?)) + } +} + +#[repr(packed)] +pub struct Damage { + pub x: i32, + pub y: i32, + pub width: i32, + pub height: i32, +} diff --git a/inputd/src/main.rs b/inputd/src/main.rs index c16cf90325..3f78339d2e 100644 --- a/inputd/src/main.rs +++ b/inputd/src/main.rs @@ -15,10 +15,11 @@ use std::collections::BTreeMap; use std::fs::File; use std::io::{Read, Write}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use orbclient::{Event, EventOption}; use syscall::{Error as SysError, EventFlags, Packet, SchemeMut, EINVAL}; -#[derive(Debug)] enum Handle { Producer, Consumer { @@ -26,6 +27,7 @@ enum Handle { pending: Vec, notified: bool, }, + Device(Arc), } impl Handle { @@ -36,27 +38,46 @@ impl Handle { struct InputScheme { handles: BTreeMap, + next_id: AtomicUsize, + next_vt_id: AtomicUsize, + + vts: BTreeMap>, + super_key: bool, + active_vt: Option<(Arc, File)>, } impl InputScheme { pub fn new() -> Self { Self { next_id: AtomicUsize::new(0), + next_vt_id: AtomicUsize::new(1), + handles: BTreeMap::new(), + vts: BTreeMap::new(), + super_key: false, + active_vt: None, } } } impl SchemeMut for InputScheme { fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result { - let handle_ty = match path { + let mut path_parts = path.split('/'); + + let command = path_parts.next().ok_or(SysError::new(EINVAL))?; + + let handle_ty = match command { "producer" => Handle::Producer, "consumer" => Handle::Consumer { events: EventFlags::empty(), pending: Vec::new(), notified: false, }, + "handle" => { + let value = path_parts.next().ok_or(SysError::new(EINVAL))?; + Handle::Device(Arc::new(value.to_string())) + } _ => unreachable!("inputd: invalid path {path}"), }; @@ -72,17 +93,24 @@ impl SchemeMut for InputScheme { fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - if let Handle::Consumer { pending, .. } = handle { - let copy = core::cmp::min(pending.len(), buf.len()); + match handle { + Handle::Consumer { pending, .. } => { + let copy = core::cmp::min(pending.len(), buf.len()); - for (i, byte) in pending.drain(..copy).enumerate() { - buf[i] = byte; + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + + Ok(copy) } - Ok(copy) - } else { - // A producer cannot read from the channel. - unreachable!() + Handle::Device(device) => { + let vt = self.next_vt_id.fetch_add(1, Ordering::SeqCst); + self.vts.insert(vt, device.clone()); + Ok(vt) + } + + _ => unreachable!(), } } @@ -91,6 +119,74 @@ impl SchemeMut for InputScheme { return Ok(1); } + let events = unsafe { + core::slice::from_raw_parts( + buf.as_ptr() as *const Event, + buf.len() / core::mem::size_of::(), + ) + }; + + for event in events.iter() { + let mut new_active_opt = None; + match event.to_option() { + EventOption::Key(key_event) => match key_event.scancode { + f @ 0x3B..=0x44 if self.super_key => { + // F1 through F10 + new_active_opt = Some((f - 0x3A) as usize); + } + + 0x57 if self.super_key => { + // F11 + new_active_opt = Some(11); + } + + 0x58 if self.super_key => { + // F12 + new_active_opt = Some(12); + } + + 0x5B => { + // Super + self.super_key = key_event.pressed; + } + + _ => (), + }, + + _ => continue, + } + + if let Some(new_active) = new_active_opt { + if let Some(vt) = self.vts.get(&new_active) { + // Result: display/virtio-gpu:3/acvtivate + let activate = format!("display/{vt}:{new_active}/activate"); + log::info!("inputd: switching to VT #{new_active} ({activate})"); + + let file = File::open(&activate).unwrap(); + // Drop the old active VT first. + // + // TODO(andypython): This can be drastically improved by introducting something + // like ioctl() to the display scheme. + for (vt, device) in self.vts.iter() { + if vt == &new_active { + continue; + } + + let deactivate = format!("display/{device}:{}/deactivate", vt); + let _ = File::open(&deactivate); + + if device.contains("virtio") { + break; + } + } + + self.active_vt = Some((vt.clone(), file)); + } else { + log::warn!("inputd: switch to non-existent VT #{new_active} was requested"); + } + } + } + let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; assert!(handle.is_producer()); diff --git a/vesad/Cargo.toml b/vesad/Cargo.toml index a77d65c2e6..9b247aabe2 100644 --- a/vesad/Cargo.toml +++ b/vesad/Cargo.toml @@ -9,6 +9,7 @@ ransid = "0.4" rusttype = { version = "0.2", optional = true } redox_syscall = "0.3" redox-daemon = "0.1" +inputd = { path = "../inputd" } [features] default = [] diff --git a/vesad/src/scheme.rs b/vesad/src/scheme.rs index d50c12902b..493eb7888e 100644 --- a/vesad/src/scheme.rs +++ b/vesad/src/scheme.rs @@ -38,10 +38,13 @@ pub struct DisplayScheme { next_id: usize, pub handles: BTreeMap, super_key: bool, + inputd_handle: inputd::Handle, } impl DisplayScheme { pub fn new(mut framebuffers: Vec, spec: &[bool]) -> DisplayScheme { + let mut inputd_handle = inputd::Handle::new("vesa").unwrap(); + let mut onscreens = Vec::new(); for fb in framebuffers.iter_mut() { onscreens.push(unsafe { @@ -51,7 +54,6 @@ impl DisplayScheme { let mut vts = BTreeMap::>>::new(); - let mut vt_i = 1; for &vt_type in spec.iter() { let mut screens = BTreeMap::>::new(); for fb_i in 0..framebuffers.len() { @@ -62,8 +64,7 @@ impl DisplayScheme { Box::new(TextScreen::new(Display::new(fb.width, fb.height))) }); } - vts.insert(VtIndex(vt_i), screens); - vt_i += 1; + vts.insert(VtIndex(inputd_handle.register().unwrap()), screens); } DisplayScheme { @@ -74,6 +75,7 @@ impl DisplayScheme { next_id: 0, handles: BTreeMap::new(), super_key: false, + inputd_handle } } @@ -168,11 +170,15 @@ impl SchemeMut for DisplayScheme { let screen_i = ScreenIndex( vt_screen.next().unwrap_or("").parse::().unwrap_or(0) ); - if let Some(screens) = self.vts.get(&vt_i) { - if screens.contains_key(&screen_i) { + if let Some(screens) = self.vts.get_mut(&vt_i) { + if let Some(screen) = screens.get_mut(&screen_i) { for cmd in parts { if cmd == "activate" { self.active = vt_i; + screen.redraw( + self.onscreens[screen_i.0], + self.framebuffers[screen_i.0].stride + ); } } diff --git a/virtio-core/src/transport.rs b/virtio-core/src/transport.rs index bc56de2149..a1a913d3fd 100644 --- a/virtio-core/src/transport.rs +++ b/virtio-core/src/transport.rs @@ -380,6 +380,11 @@ impl<'a> StandardTransport<'a> { }) } + pub fn reset(&self) { + let mut common = self.common.lock().unwrap(); + common.device_status.set(DeviceStatusFlags::empty()); + } + pub fn check_device_feature(&self, feature: u32) -> bool { let mut common = self.common.lock().unwrap(); diff --git a/virtio-gpud/Cargo.toml b/virtio-gpud/Cargo.toml index 02249c294c..c06c18d87e 100644 --- a/virtio-gpud/Cargo.toml +++ b/virtio-gpud/Cargo.toml @@ -13,7 +13,9 @@ paste = "1.0.13" virtio-core = { path = "../virtio-core" } pcid = { path = "../pcid" } +inputd = { path = "../inputd" } redox-daemon = "0.1" redox_syscall = "0.3" orbclient = "0.3.27" +spin = "0.9.8" diff --git a/virtio-gpud/src/main.rs b/virtio-gpud/src/main.rs index f9671f2485..bbe5675688 100644 --- a/virtio-gpud/src/main.rs +++ b/virtio-gpud/src/main.rs @@ -22,6 +22,7 @@ #![feature(int_roundings)] +use std::cell::UnsafeCell; use std::fs::File; use std::io::{Read, Write}; @@ -144,6 +145,15 @@ pub struct ControlHeader { padding: [u8; 3], } +impl ControlHeader { + pub fn with_ty(ty: CommandTy) -> Self { + Self { + ty: VolatileCell::new(ty), + ..Default::default() + } + } +} + impl Default for ControlHeader { fn default() -> Self { Self { @@ -157,44 +167,41 @@ impl Default for ControlHeader { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[repr(C)] pub struct GpuRect { - pub x: VolatileCell, - pub y: VolatileCell, - pub width: VolatileCell, - pub height: VolatileCell, + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, } impl GpuRect { pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self { Self { - x: VolatileCell::new(x), - y: VolatileCell::new(y), - width: VolatileCell::new(width), - height: VolatileCell::new(height), + x, + y, + width, + height, } } - - #[inline] - pub fn width(&self) -> u32 { - self.width.get() - } - - #[inline] - pub fn height(&self) -> u32 { - self.height.get() - } } #[derive(Debug)] #[repr(C)] pub struct DisplayInfo { - pub rect: GpuRect, + rect: UnsafeCell, pub enabled: VolatileCell, pub flags: VolatileCell, } +impl DisplayInfo { + pub fn rect(&self) -> &GpuRect { + // SAFETY: We never give out mutable references to `self.rect`. + unsafe { &*self.rect.get() } + } +} + #[derive(Debug)] #[repr(C)] pub struct GetDisplayInfo { @@ -274,16 +281,31 @@ pub struct AttachBacking { impl AttachBacking { pub fn new(resource_id: u32, num_entries: u32) -> Self { Self { - header: ControlHeader { - ty: VolatileCell::new(CommandTy::ResourceAttachBacking), - ..Default::default() - }, + header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking), resource_id, num_entries, } } } +#[derive(Debug)] +#[repr(C)] +pub struct DetachBacking { + pub header: ControlHeader, + pub resource_id: u32, + pub padding: u32, +} + +impl DetachBacking { + pub fn new(resource_id: u32) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking), + resource_id, + padding: 0, + } + } +} + #[derive(Debug)] #[repr(C)] pub struct ResourceFlush { @@ -296,11 +318,7 @@ pub struct ResourceFlush { impl ResourceFlush { pub fn new(resource_id: u32, rect: GpuRect) -> Self { Self { - header: ControlHeader { - ty: VolatileCell::new(CommandTy::ResourceFlush), - ..Default::default() - }, - + header: ControlHeader::with_ty(CommandTy::ResourceFlush), rect, resource_id, padding: 0, @@ -308,6 +326,24 @@ impl ResourceFlush { } } +#[derive(Debug)] +#[repr(C)] +pub struct ResourceUnref { + pub header: ControlHeader, + pub resource_id: u32, + pub padding: u32, +} + +impl ResourceUnref { + pub fn new(resource_id: u32) -> Self { + Self { + header: ControlHeader::with_ty(CommandTy::ResourceUnref), + resource_id, + padding: 0, + } + } +} + #[repr(C)] #[derive(Debug)] pub struct SetScanout { @@ -389,7 +425,12 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { deamon.ready().unwrap(); let mut socket_file = File::create(":display/virtio-gpu")?; - let mut scheme = scheme::Scheme::new(config, control_queue.clone(), cursor_queue.clone())?; + let mut scheme = futures::executor::block_on(scheme::Scheme::new( + config, + control_queue.clone(), + cursor_queue.clone(), + device.transport.clone(), + ))?; loop { let mut packet = Packet::default(); diff --git a/virtio-gpud/src/scheme.rs b/virtio-gpud/src/scheme.rs index 89c524a517..b0490c8cce 100644 --- a/virtio-gpud/src/scheme.rs +++ b/virtio-gpud/src/scheme.rs @@ -1,23 +1,38 @@ use std::collections::BTreeMap; -use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; +use inputd::Damage; use syscall::{Dma, Error as SysError, SchemeMut, EINVAL}; use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; -use virtio_core::transport::{Error, Queue}; +use virtio_core::transport::{Error, Queue, StandardTransport}; use virtio_core::utils::VolatileCell; use crate::*; static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`. +impl Into for &Damage { + fn into(self) -> GpuRect { + GpuRect { + x: self.x as u32, + y: self.y as u32, + width: self.width as u32, + height: self.height as u32, + } + } +} + pub struct Display<'a> { control_queue: Arc>, cursor_queue: Arc>, + transport: Arc>, - mapped: Option, + // TODO(andypython): Remove the need for the spin crate after the `once_cell` + // API is stabilized. + mapped: spin::Once, width: u32, height: u32, @@ -30,24 +45,25 @@ impl<'a> Display<'a> { pub fn new( control_queue: Arc>, cursor_queue: Arc>, - _display_info: &mut DisplayInfo, + transport: Arc>, id: usize, ) -> Self { Self { control_queue, cursor_queue, - mapped: None, + mapped: spin::Once::new(), width: 1920, height: 1080, + transport, id, resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst), } } - async fn get_fpath(&mut self, buffer: &mut [u8]) -> Result { + async fn get_fpath(&self, buffer: &mut [u8]) -> Result { let path = format!("display/virtio-gpu:3.0/{}/{}", self.width, self.height); // Copy the path into the target buffer. @@ -55,7 +71,7 @@ impl<'a> Display<'a> { Ok(path.len()) } - async fn send_request(&mut self, request: Dma) -> Result, Error> { + async fn send_request(&self, request: Dma) -> Result, Error> { let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() .chain(Buffer::new(&request)) @@ -66,15 +82,15 @@ impl<'a> Display<'a> { Ok(header) } - async fn flush_resource(&mut self, flush: ResourceFlush) -> Result<(), Error> { + async fn flush_resource(&self, flush: ResourceFlush) -> Result<(), Error> { let header = self.send_request(Dma::new(flush)?).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); Ok(()) } - async fn map_screen(&mut self, offset: usize) -> Result { - if let Some(mapped) = self.mapped { + async fn map_screen(&self, offset: usize) -> Result { + if let Some(mapped) = self.mapped.get() { return Ok(mapped + offset); } @@ -124,74 +140,128 @@ impl<'a> Display<'a> { self.control_queue.send(command).await; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - self.flush().await?; - self.mapped = Some(mapped); - Ok(mapped + offset) - } - async fn flush(&mut self) -> Result<(), Error> { - let rect = GpuRect::new(0, 0, self.width, self.height); - let scanout_request = Dma::new(SetScanout::new(self.id as u32, self.resource_id, rect))?; + let scanout_request = Dma::new(SetScanout::new( + self.id as u32, + self.resource_id, + GpuRect::new(0, 0, self.width, self.height), + ))?; let header = self.send_request(scanout_request).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0, 0, self.width, self.height); - let req = Dma::new(XferToHost2d::new(self.resource_id, rect))?; + self.flush(None).await?; + self.mapped.call_once(|| mapped); + + Ok(mapped + offset) + } + + /// If `damage` is `None`, the entire screen is flushed. + async fn flush(&self, damage: Option<&Damage>) -> Result<(), Error> { + let damage = if let Some(damage) = damage { + damage.into() + } else { + GpuRect { + x: 0, + y: 0, + width: self.width, + height: self.height, + } + }; + + let req = Dma::new(XferToHost2d::new( + self.resource_id, + GpuRect { + x: 0, + y: 0, + width: self.width, + height: self.height, + }, + ))?; let header = self.send_request(req).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - let rect = GpuRect::new(0, 0, self.width, self.height); - self.flush_resource(ResourceFlush::new(self.resource_id, rect)) + self.flush_resource(ResourceFlush::new(self.resource_id, damage.clone())) .await?; Ok(()) } + + /// This detaches any backing pages from the display and unrefs the resource. Also resets the + /// device, which is required to go back to legacy mode. + async fn detach(&self) -> Result<(), Error> { + let request = Dma::new(DetachBacking::new(self.resource_id))?; + let header = self.send_request(request).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + let request = Dma::new(ResourceUnref::new(self.resource_id))?; + let header = self.send_request(request).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + + // Go back to legacy mode. + self.transport.reset(); + Ok(()) + } } pub struct Scheme<'a> { - control_queue: Arc>, - cursor_queue: Arc>, - config: &'a mut GpuConfig, - /// File descriptor allocator. - next_id: AtomicUsize, - handles: BTreeMap>, + handles: BTreeMap>>, + inputd_handle: inputd::Handle, + displays: Vec>>, } impl<'a> Scheme<'a> { - pub fn new( + pub async fn new( config: &'a mut GpuConfig, control_queue: Arc>, cursor_queue: Arc>, - ) -> Result { - Ok(Self { - control_queue, - cursor_queue, + transport: Arc>, + ) -> Result, Error> { + let displays = Self::probe( + control_queue.clone(), + cursor_queue.clone(), + transport.clone(), config, - next_id: AtomicUsize::new(0), + ) + .await?; + + Ok(Self { handles: BTreeMap::new(), + inputd_handle: inputd::Handle::new("virtio-gpu").unwrap(), + displays, }) } - async fn open_display(&self, id: usize) -> Result, Error> { - let mut display_info = self.get_display_info().await?; - let displays = &mut display_info.display_info[..self.config.num_scanouts() as usize]; + async fn probe( + control_queue: Arc>, + cursor_queue: Arc>, + transport: Arc>, + config: &GpuConfig, + ) -> Result>>, Error> { + let mut display_info = Self::get_display_info(control_queue.clone()).await?; + let displays = &mut display_info.display_info[..config.num_scanouts() as usize]; - let display = displays.get_mut(id).ok_or(SysError::new(syscall::ENOENT))?; + let mut result = vec![]; - log::info!( - "virtio-gpu: opening display ({}x{}px)", - display.rect.width(), - display.rect.height() - ); + for (id, info) in displays.iter().enumerate() { + log::info!( + "virtio-gpu: opening display ({}x{}px)", + info.rect().width, + info.rect().height + ); - Ok(Display::new( - self.control_queue.clone(), - self.cursor_queue.clone(), - display, - id, - )) + let display = Display::new( + control_queue.clone(), + cursor_queue.clone(), + transport.clone(), + id, + ); + + result.push(Arc::new(display)); + } + + Ok(result) } - async fn get_display_info(&self) -> Result, Error> { + async fn get_display_info(control_queue: Arc>) -> Result, Error> { let header = Dma::new(ControlHeader { ty: VolatileCell::new(CommandTy::GetDisplayInfo), ..Default::default() @@ -203,7 +273,7 @@ impl<'a> Scheme<'a> { .chain(Buffer::new(&response).flags(DescriptorFlags::WRITE_ONLY)) .build(); - self.control_queue.send(command).await; + control_queue.send(command).await; assert!(response.header.ty.get() == CommandTy::RespOkDisplayInfo); Ok(response) @@ -217,16 +287,30 @@ impl<'a> SchemeMut for Scheme<'a> { let mut parts = path.split('/'); let mut screen = parts.next().unwrap_or("").split('.'); - let vt_index = screen.next().unwrap_or("").parse::().unwrap_or(1); + static YES: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); + if YES.load(Ordering::SeqCst) { + YES.store(false, Ordering::SeqCst); + } + + let fd = screen.next().unwrap_or("").parse::().unwrap(); + + if path.contains("deactivate") { + log::info!("virtio-gpu: deactivating display at file #{}", fd); + + let handle = self.handles.get(&fd).unwrap(); + futures::executor::block_on(handle.detach()).unwrap(); + + return Ok(0); + } + let id = screen.next().unwrap_or("").parse::().unwrap_or(0); - dbg!(&vt_index, &id); + dbg!(&id); - let fd = self.next_id.fetch_add(1, Ordering::SeqCst); - let display = futures::executor::block_on(self.open_display(id)) - .map_err(|_| SysError::new(syscall::ENOENT))?; + let display = self.displays.get(id).ok_or(SysError::new(EINVAL))?; - self.handles.insert(fd, display); + let fd = self.inputd_handle.register().unwrap(); + self.handles.insert(fd, display.clone()); Ok(fd) } @@ -269,7 +353,7 @@ impl<'a> SchemeMut for Scheme<'a> { fn fsync(&mut self, id: usize) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; - futures::executor::block_on(handle.flush()).unwrap(); + futures::executor::block_on(handle.flush(None)).unwrap(); Ok(0) } @@ -281,17 +365,16 @@ impl<'a> SchemeMut for Scheme<'a> { fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; + let damages = unsafe { + core::slice::from_raw_parts( + buf.as_ptr() as *const Damage, + buf.len() / core::mem::size_of::(), + ) + }; - // SAFETY: lmao - unsafe { - core::ptr::copy_nonoverlapping( - buf.as_ptr(), - handle.mapped.unwrap() as *mut u8, - buf.len(), - ); + for damage in damages { + futures::executor::block_on(handle.flush(Some(damage))).unwrap(); } - - futures::executor::block_on(handle.flush()).unwrap(); Ok(buf.len()) } From c5729befe5e8afa58ce853f22d060028ad6bddc7 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Fri, 7 Jul 2023 17:03:14 +1000 Subject: [PATCH 11/11] inputd: do not reswitch if the same Signed-off-by: Anhad Singh --- inputd/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/inputd/src/main.rs b/inputd/src/main.rs index 3f78339d2e..a963789c06 100644 --- a/inputd/src/main.rs +++ b/inputd/src/main.rs @@ -158,11 +158,17 @@ impl SchemeMut for InputScheme { if let Some(new_active) = new_active_opt { if let Some(vt) = self.vts.get(&new_active) { + // If the VT is already active, don't do anything. + if let Some(current) = self.active_vt.as_ref() { + if vt == ¤t.0 { + continue; + } + } + // Result: display/virtio-gpu:3/acvtivate let activate = format!("display/{vt}:{new_active}/activate"); log::info!("inputd: switching to VT #{new_active} ({activate})"); - let file = File::open(&activate).unwrap(); // Drop the old active VT first. // // TODO(andypython): This can be drastically improved by introducting something @@ -180,6 +186,7 @@ impl SchemeMut for InputScheme { } } + let file = File::open(&activate).unwrap(); self.active_vt = Some((vt.clone(), file)); } else { log::warn!("inputd: switch to non-existent VT #{new_active} was requested");