diff --git a/graphics/virtio-gpud/src/main.rs b/graphics/virtio-gpud/src/main.rs index 6ac65e3c1b..fa26bbc036 100644 --- a/graphics/virtio-gpud/src/main.rs +++ b/graphics/virtio-gpud/src/main.rs @@ -24,6 +24,7 @@ use std::cell::UnsafeCell; use std::os::fd::AsRawFd; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use event::{user_data, EventQueue}; @@ -225,6 +226,18 @@ impl Default for GetDisplayInfo { } } +static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`. + +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct ResourceId(u32); + +impl ResourceId { + fn alloc() -> Self { + ResourceId(RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst)) + } +} + #[derive(Debug, Copy, Clone)] #[repr(u32)] pub enum ResourceFormat { @@ -239,14 +252,16 @@ pub enum ResourceFormat { pub struct ResourceCreate2d { pub header: ControlHeader, - resource_id: VolatileCell, + // FIXME we can likely use regular loads and stores as the ring buffer should provide the + // necessary synchronization. + resource_id: VolatileCell, format: VolatileCell, width: VolatileCell, height: VolatileCell, } impl ResourceCreate2d { - make_getter_setter!(resource_id: u32, format: ResourceFormat, width: u32, height: u32); + make_getter_setter!(resource_id: ResourceId, format: ResourceFormat, width: u32, height: u32); } impl Default for ResourceCreate2d { @@ -257,7 +272,7 @@ impl Default for ResourceCreate2d { ..Default::default() }, - resource_id: VolatileCell::new(0), + resource_id: VolatileCell::new(ResourceId(0)), format: VolatileCell::new(ResourceFormat::Unknown), width: VolatileCell::new(0), height: VolatileCell::new(0), @@ -277,12 +292,12 @@ pub struct MemEntry { #[repr(C)] pub struct AttachBacking { pub header: ControlHeader, - pub resource_id: u32, + pub resource_id: ResourceId, pub num_entries: u32, } impl AttachBacking { - pub fn new(resource_id: u32, num_entries: u32) -> Self { + pub fn new(resource_id: ResourceId, num_entries: u32) -> Self { Self { header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking), resource_id, @@ -295,12 +310,12 @@ impl AttachBacking { #[repr(C)] pub struct DetachBacking { pub header: ControlHeader, - pub resource_id: u32, + pub resource_id: ResourceId, pub padding: u32, } impl DetachBacking { - pub fn new(resource_id: u32) -> Self { + pub fn new(resource_id: ResourceId) -> Self { Self { header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking), resource_id, @@ -314,12 +329,12 @@ impl DetachBacking { pub struct ResourceFlush { pub header: ControlHeader, pub rect: GpuRect, - pub resource_id: u32, + pub resource_id: ResourceId, pub padding: u32, } impl ResourceFlush { - pub fn new(resource_id: u32, rect: GpuRect) -> Self { + pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self { Self { header: ControlHeader::with_ty(CommandTy::ResourceFlush), rect, @@ -333,12 +348,12 @@ impl ResourceFlush { #[repr(C)] pub struct ResourceUnref { pub header: ControlHeader, - pub resource_id: u32, + pub resource_id: ResourceId, pub padding: u32, } impl ResourceUnref { - pub fn new(resource_id: u32) -> Self { + pub fn new(resource_id: ResourceId) -> Self { Self { header: ControlHeader::with_ty(CommandTy::ResourceUnref), resource_id, @@ -353,11 +368,11 @@ pub struct SetScanout { pub header: ControlHeader, pub rect: GpuRect, pub scanout_id: u32, - pub resource_id: u32, + pub resource_id: ResourceId, } impl SetScanout { - pub fn new(scanout_id: u32, resource_id: u32, rect: GpuRect) -> Self { + pub fn new(scanout_id: u32, resource_id: ResourceId, rect: GpuRect) -> Self { Self { header: ControlHeader::with_ty(CommandTy::SetScanout), @@ -374,12 +389,12 @@ pub struct XferToHost2d { pub header: ControlHeader, pub rect: GpuRect, pub offset: u64, - pub resource_id: u32, + pub resource_id: ResourceId, pub padding: u32, } impl XferToHost2d { - pub fn new(resource_id: u32, rect: GpuRect) -> Self { + pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self { Self { header: ControlHeader { ty: VolatileCell::new(CommandTy::TransferToHost2d), @@ -455,7 +470,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { } let event_queue: EventQueue = - EventQueue::new().expect("vesad: failed to create event queue"); + EventQueue::new().expect("virtio-gpud: failed to create event queue"); event_queue .subscribe( scheme.inputd_handle.inner().as_raw_fd() as usize, @@ -477,14 +492,14 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { let all = [Source::Input, Source::Scheme]; for event in all .into_iter() - .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data)) + .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) { match event { Source::Input => { while let Some(vt_event) = scheme .inputd_handle .read_vt_event() - .expect("vesad: failed to read display handle") + .expect("virtio-gpud: failed to read display handle") { scheme.handle_vt_event(vt_event); } @@ -508,7 +523,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { call_request.handle_scheme_mut(&mut scheme), SignalBehavior::Restart, ) - .expect("vesad: failed to write display scheme"); + .expect("virtio-gpud: failed to write display scheme"); } RequestKind::Cancellation(_cancellation_request) => { // FIXME handle this @@ -522,23 +537,7 @@ fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { } } - loop { - let Some(request) = socket.next_request(SignalBehavior::Restart)? else { - // Scheme likely got unmounted - return Ok(()); - }; - - match request.kind() { - RequestKind::Call(call_request) => { - socket.write_response( - call_request.handle_scheme_mut(&mut scheme), - SignalBehavior::Restart, - )?; - } - RequestKind::Cancellation(_cancellation_request) => {} - RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => unreachable!(), - } - } + std::process::exit(0); } fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { diff --git a/graphics/virtio-gpud/src/scheme.rs b/graphics/virtio-gpud/src/scheme.rs index 8c8df4de2a..ba8c70b938 100644 --- a/graphics/virtio-gpud/src/scheme.rs +++ b/graphics/virtio-gpud/src/scheme.rs @@ -1,14 +1,14 @@ -use std::cell::OnceCell; -use std::collections::BTreeMap; +use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use common::{dma::Dma, sgl}; use inputd::{Damage, VtEvent, VtEventKind}; use redox_scheme::SchemeMut; -use syscall::{Error as SysError, MapFlags, EAGAIN, EINVAL, PAGE_SIZE}; +use syscall::{Error as SysError, MapFlags, EINVAL, PAGE_SIZE}; use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; use virtio_core::transport::{Error, Queue, Transport}; @@ -16,8 +16,6 @@ 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 { @@ -34,12 +32,13 @@ pub struct Display<'a> { cursor_queue: Arc>, transport: Arc, - mapped: OnceCell, + active_vt: RefCell, + vts_map: RefCell>, + vts_res: RefCell>, width: u32, height: u32, - resource_id: u32, id: usize, is_reseted: AtomicBool, @@ -56,22 +55,24 @@ impl<'a> Display<'a> { control_queue, cursor_queue, - mapped: OnceCell::new(), + active_vt: RefCell::new(0), + vts_map: RefCell::new(HashMap::new()), + vts_res: RefCell::new(HashMap::new()), width: 1920, height: 1080, transport, id, - resource_id: RESOURCE_ALLOC.fetch_add(1, Ordering::SeqCst), is_reseted: AtomicBool::new(false), } } - async fn init(&self) -> Result<(), Error> { + async fn init(&self, vt: usize) -> Result<(), Error> { if !self.is_reseted.load(Ordering::SeqCst) { // The device is already initialized. + self.set_scanout(vt).await?; return Ok(()); } @@ -80,7 +81,7 @@ impl<'a> Display<'a> { log::info!("virtio-gpu: initializing GPU after a reset"); crate::reinit(self.control_queue.clone(), self.cursor_queue.clone())?; - self.remap_screen().await?; + self.set_scanout(vt).await?; Ok(()) } @@ -111,20 +112,9 @@ impl<'a> Display<'a> { Ok(()) } - // TODO: Is this a no-op? - async fn remap_screen(&self) -> Result<*mut u8, Error> { - let bpp = 32; - - let fb_size = self.width as usize * self.height as usize * bpp / 8; - - let mapped = self.mapped.get().unwrap(); - self.map_screen_with(0, fb_size, mapped.as_ptr(), mapped.chunks()) - .await - } - - async fn map_screen(&self, offset: usize) -> Result<*mut u8, Error> { - if let Some(mapped) = self.mapped.get() { - return Ok(mapped.as_ptr().wrapping_add(offset)); + async fn mmap_screen(&self, vt: usize, offset: usize) -> Result<*mut u8, Error> { + if let Some(sgl) = self.vts_map.borrow().get(&vt) { + return Ok(sgl.as_ptr().wrapping_add(offset)); } let bpp = 32; @@ -134,35 +124,40 @@ impl<'a> Display<'a> { unsafe { core::ptr::write_bytes(mapped.as_ptr() as *mut u8, 255, fb_size); } - let _ = self.mapped.set(mapped); - let mapped = self.mapped.get().unwrap(); - self.map_screen_with(offset, fb_size, mapped.as_ptr(), mapped.chunks()) - .await + let mut mapped_vts = self.vts_map.borrow_mut(); + let sgl = mapped_vts.entry(vt).or_insert(mapped); + Ok(sgl.as_ptr().wrapping_add(offset)) } - async fn map_screen_with( - &self, - offset: usize, - _size: usize, - virt: *mut u8, - chunks: &[sgl::Chunk], - ) -> Result<*mut u8, Error> { + async fn create_res_for_screen(&self, vt: usize) -> Result { + if let Some(&res_id) = self.vts_res.borrow().get(&vt) { + return Ok(res_id); + } + + self.mmap_screen(vt, 0).await?; + + let vts_map = self.vts_map.borrow(); + let mapped = &vts_map.get(&vt).unwrap(); + + let res_id = ResourceId::alloc(); + // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. let mut request = Dma::new(ResourceCreate2d::default())?; request.set_width(self.width); request.set_height(self.height); request.set_format(ResourceFormat::Bgrx); - request.set_resource_id(self.resource_id); + request.set_resource_id(res_id); - self.send_request(request).await?; + let header = self.send_request(request).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); // Use the allocated framebuffer from tthe guest ram, and attach it as backing // storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. - let mut mem_entries = unsafe { Dma::zeroed_slice(chunks.len())?.assume_init() }; - for (entry, chunk) in mem_entries.iter_mut().zip(chunks.iter()) { + let mut mem_entries = unsafe { Dma::zeroed_slice(mapped.chunks().len())?.assume_init() }; + for (entry, chunk) in mem_entries.iter_mut().zip(mapped.chunks().iter()) { *entry = MemEntry { address: chunk.phys as u64, length: chunk.length.next_multiple_of(PAGE_SIZE) as u32, @@ -170,10 +165,7 @@ impl<'a> Display<'a> { }; } - let attach_request = Dma::new(AttachBacking::new( - self.resource_id, - mem_entries.len() as u32, - ))?; + let attach_request = Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32))?; let header = Dma::new(ControlHeader::default())?; let command = ChainBuilder::new() .chain(Buffer::new(&attach_request)) @@ -184,21 +176,33 @@ impl<'a> Display<'a> { self.control_queue.send(command).await; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + let mut mapped_vts = self.vts_res.borrow_mut(); + mapped_vts.insert(vt, res_id); + Ok(res_id) + } + + async fn set_scanout(&self, vt: usize) -> Result<(), Error> { + let res_id = self.create_res_for_screen(vt).await?; + let scanout_request = Dma::new(SetScanout::new( self.id as u32, - self.resource_id, + res_id, GpuRect::new(0, 0, self.width, self.height), ))?; let header = self.send_request(scanout_request).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - self.flush(None).await?; + self.flush(vt, None).await?; - Ok(virt.wrapping_add(offset)) + Ok(()) } /// If `damage` is `None`, the entire screen is flushed. - async fn flush(&self, damage: Option<&Damage>) -> Result<(), Error> { + async fn flush(&self, vt: usize, damage: Option<&Damage>) -> Result<(), Error> { + if vt != *self.active_vt.borrow() { + return Ok(()); + } + let damage = if let Some(damage) = damage { damage.into() } else { @@ -211,7 +215,7 @@ impl<'a> Display<'a> { }; let req = Dma::new(XferToHost2d::new( - self.resource_id, + self.vts_res.borrow()[&vt], GpuRect { x: 0, y: 0, @@ -222,21 +226,26 @@ impl<'a> Display<'a> { let header = self.send_request(req).await?; assert_eq!(header.ty.get(), CommandTy::RespOkNodata); - self.flush_resource(ResourceFlush::new(self.resource_id, damage.clone())) - .await?; + self.flush_resource(ResourceFlush::new( + self.vts_res.borrow()[&vt], + 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); + for (_vt, res_id) in self.vts_res.borrow_mut().drain() { + let request = Dma::new(DetachBacking::new(res_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); + let request = Dma::new(ResourceUnref::new(res_id))?; + let header = self.send_request(request).await?; + assert_eq!(header.ty.get(), CommandTy::RespOkNodata); + } // Go back to legacy mode. self.transport.reset(); @@ -310,6 +319,11 @@ impl<'a> Scheme<'a> { transport.clone(), id, ); + // FIXME this is a hack to avoid breaking things while we need to co-exist with vesad + // Somehow necessary to ensure that creating a resource on the first reinitialization + // after this detach doesn't fail. + display.init(1).await?; + display.detach().await?; result.push(Arc::new(display)); } @@ -340,14 +354,12 @@ impl<'a> Scheme<'a> { VtEventKind::Activate => { log::info!("activate {}", vt_event.vt); - for handle in self.handles.values() { - if handle.vt != vt_event.vt { - continue; - } - + for display in &self.displays { log::warn!("virtio-gpu: activating"); - futures::executor::block_on(handle.display.init()).unwrap(); + futures::executor::block_on(display.init(vt_event.vt)).unwrap(); + + *display.active_vt.borrow_mut() = vt_event.vt; } } @@ -408,7 +420,7 @@ impl<'a> SchemeMut for Scheme<'a> { fn fsync(&mut self, id: usize) -> syscall::Result { let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?; - futures::executor::block_on(handle.display.flush(None)).unwrap(); + futures::executor::block_on(handle.display.flush(handle.vt, None)).unwrap(); Ok(0) } @@ -433,10 +445,11 @@ impl<'a> SchemeMut for Scheme<'a> { ) -> syscall::Result { let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?; - // The VT is not active and the device is reseted. Ask them to try - // again later. + // The VT is not active and the device is reseted. Ignore the damage. We will recreate the + // backing storage from scratch next time we initialize, which is equivalent to damaging the + // entire buffer. if handle.display.is_reseted.load(Ordering::SeqCst) { - return Err(SysError::new(EAGAIN)); + return Ok(buf.len()); } let damages = unsafe { @@ -447,7 +460,7 @@ impl<'a> SchemeMut for Scheme<'a> { }; for damage in damages { - futures::executor::block_on(handle.display.flush(Some(damage))).unwrap(); + futures::executor::block_on(handle.display.flush(handle.vt, Some(damage))).unwrap(); } Ok(buf.len()) @@ -465,7 +478,9 @@ impl<'a> SchemeMut for Scheme<'a> { ) -> syscall::Result { log::info!("KSMSG MMAP {} {:?} {} {}", id, flags, offset, size); let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?; - let ptr = futures::executor::block_on(handle.display.map_screen(offset as usize)).unwrap(); + let ptr = + futures::executor::block_on(handle.display.mmap_screen(handle.vt, offset as usize)) + .unwrap(); Ok(ptr as usize) } }