From c0950d06867f4451a6fd59153049303857ff57bf Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Tue, 4 Jul 2023 13:17:58 +1000 Subject: [PATCH] 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) };