From 7cc2f4eff7bb94d457e215a8b028d69d9e29cb5e Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Mon, 3 Jul 2023 17:12:59 +1000 Subject: [PATCH] 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!() + } + } + } +}