use super::*; use crate::queue::VirtQueue; use crate::transport::Transport; use crate::volatile::{volread, ReadOnly, Volatile, WriteOnly}; use bitflags::*; use core::fmt; use log::*; /// A virtio based graphics adapter. /// /// It can operate in 2D mode and in 3D (virgl) mode. /// 3D mode will offload rendering ops to the host gpu and therefore requires /// a gpu with 3D support on the host machine. /// In 2D mode the virtio-gpu device provides support for ARGB Hardware cursors /// and multiple scanouts (aka heads). pub struct VirtIOGpu<'a, H: Hal, T: Transport> { transport: T, rect: Option, /// DMA area of frame buffer. frame_buffer_dma: Option>, /// DMA area of cursor image buffer. cursor_buffer_dma: Option>, /// Queue for sending control commands. control_queue: VirtQueue, /// Queue for sending cursor commands. cursor_queue: VirtQueue, /// Queue buffer DMA queue_buf_dma: Dma, /// Send buffer for queue. queue_buf_send: &'a mut [u8], /// Recv buffer for queue. queue_buf_recv: &'a mut [u8], } impl VirtIOGpu<'_, H, T> { /// Create a new VirtIO-Gpu driver. pub fn new(mut transport: T) -> Result { transport.begin_init(|features| { let features = Features::from_bits_truncate(features); info!("Device features {:?}", features); let supported_features = Features::empty(); (features & supported_features).bits() }); // read configuration space let config_space = transport.config_space::()?; unsafe { let events_read = volread!(config_space, events_read); let num_scanouts = volread!(config_space, num_scanouts); info!( "events_read: {:#x}, num_scanouts: {:#x}", events_read, num_scanouts ); } let control_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT, 2)?; let cursor_queue = VirtQueue::new(&mut transport, QUEUE_CURSOR, 2)?; let queue_buf_dma = Dma::new(2)?; let queue_buf_send = unsafe { &mut queue_buf_dma.as_buf()[..PAGE_SIZE] }; let queue_buf_recv = unsafe { &mut queue_buf_dma.as_buf()[PAGE_SIZE..] }; transport.finish_init(); Ok(VirtIOGpu { transport, frame_buffer_dma: None, cursor_buffer_dma: None, rect: None, control_queue, cursor_queue, queue_buf_dma, queue_buf_send, queue_buf_recv, }) } /// Acknowledge interrupt. pub fn ack_interrupt(&mut self) -> bool { self.transport.ack_interrupt() } /// Get the resolution (width, height). pub fn resolution(&mut self) -> Result<(u32, u32)> { let display_info = self.get_display_info()?; Ok((display_info.rect.width, display_info.rect.height)) } /// Setup framebuffer pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> { // get display info let display_info = self.get_display_info()?; info!("=> {:?}", display_info); self.rect = Some(display_info.rect); // create resource 2d self.resource_create_2d( RESOURCE_ID_FB, display_info.rect.width, display_info.rect.height, )?; // alloc continuous pages for the frame buffer let size = display_info.rect.width * display_info.rect.height * 4; let frame_buffer_dma = Dma::new(pages(size as usize))?; // resource_attach_backing self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?; // map frame buffer to screen self.set_scanout(display_info.rect, SCANOUT_ID, RESOURCE_ID_FB)?; let buf = unsafe { frame_buffer_dma.as_buf() }; self.frame_buffer_dma = Some(frame_buffer_dma); Ok(buf) } /// Flush framebuffer to screen. pub fn flush(&mut self) -> Result { let rect = self.rect.ok_or(Error::NotReady)?; // copy data from guest to host self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?; // flush data to screen self.resource_flush(rect, RESOURCE_ID_FB)?; Ok(()) } /// Set the pointer shape and position. pub fn setup_cursor( &mut self, cursor_image: &[u8], pos_x: u32, pos_y: u32, hot_x: u32, hot_y: u32, ) -> Result { let size = CURSOR_RECT.width * CURSOR_RECT.height * 4; if cursor_image.len() != size as usize { return Err(Error::InvalidParam); } let cursor_buffer_dma = Dma::new(pages(size as usize))?; let buf = unsafe { cursor_buffer_dma.as_buf() }; buf.copy_from_slice(cursor_image); self.resource_create_2d(RESOURCE_ID_CURSOR, CURSOR_RECT.width, CURSOR_RECT.height)?; self.resource_attach_backing(RESOURCE_ID_CURSOR, cursor_buffer_dma.paddr() as u64, size)?; self.transfer_to_host_2d(CURSOR_RECT, 0, RESOURCE_ID_CURSOR)?; self.update_cursor( RESOURCE_ID_CURSOR, SCANOUT_ID, pos_x, pos_y, hot_x, hot_y, false, )?; self.cursor_buffer_dma = Some(cursor_buffer_dma); Ok(()) } /// Move the pointer without updating the shape. pub fn move_cursor(&mut self, pos_x: u32, pos_y: u32) -> Result { self.update_cursor(RESOURCE_ID_CURSOR, SCANOUT_ID, pos_x, pos_y, 0, 0, true)?; Ok(()) } /// Send a request to the device and block for a response. fn request(&mut self, req: Req) -> Result { unsafe { (self.queue_buf_send.as_mut_ptr() as *mut Req).write(req); } self.control_queue.add_notify_wait_pop( &[self.queue_buf_send], &[self.queue_buf_recv], &mut self.transport, )?; Ok(unsafe { (self.queue_buf_recv.as_ptr() as *const Rsp).read() }) } /// Send a mouse cursor operation request to the device and block for a response. fn cursor_request(&mut self, req: Req) -> Result { unsafe { (self.queue_buf_send.as_mut_ptr() as *mut Req).write(req); } self.cursor_queue .add_notify_wait_pop(&[self.queue_buf_send], &[], &mut self.transport)?; Ok(()) } fn get_display_info(&mut self) -> Result { let info: RespDisplayInfo = self.request(CtrlHeader::with_type(Command::GetDisplayInfo))?; info.header.check_type(Command::OkDisplayInfo)?; Ok(info) } fn resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result { let rsp: CtrlHeader = self.request(ResourceCreate2D { header: CtrlHeader::with_type(Command::ResourceCreate2d), resource_id, format: Format::B8G8R8A8UNORM, width, height, })?; rsp.check_type(Command::OkNodata) } fn set_scanout(&mut self, rect: Rect, scanout_id: u32, resource_id: u32) -> Result { let rsp: CtrlHeader = self.request(SetScanout { header: CtrlHeader::with_type(Command::SetScanout), rect, scanout_id, resource_id, })?; rsp.check_type(Command::OkNodata) } fn resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result { let rsp: CtrlHeader = self.request(ResourceFlush { header: CtrlHeader::with_type(Command::ResourceFlush), rect, resource_id, _padding: 0, })?; rsp.check_type(Command::OkNodata) } fn transfer_to_host_2d(&mut self, rect: Rect, offset: u64, resource_id: u32) -> Result { let rsp: CtrlHeader = self.request(TransferToHost2D { header: CtrlHeader::with_type(Command::TransferToHost2d), rect, offset, resource_id, _padding: 0, })?; rsp.check_type(Command::OkNodata) } fn resource_attach_backing(&mut self, resource_id: u32, paddr: u64, length: u32) -> Result { let rsp: CtrlHeader = self.request(ResourceAttachBacking { header: CtrlHeader::with_type(Command::ResourceAttachBacking), resource_id, nr_entries: 1, addr: paddr, length, _padding: 0, })?; rsp.check_type(Command::OkNodata) } fn update_cursor( &mut self, resource_id: u32, scanout_id: u32, pos_x: u32, pos_y: u32, hot_x: u32, hot_y: u32, is_move: bool, ) -> Result { self.cursor_request(UpdateCursor { header: if is_move { CtrlHeader::with_type(Command::MoveCursor) } else { CtrlHeader::with_type(Command::UpdateCursor) }, pos: CursorPos { scanout_id, x: pos_x, y: pos_y, _padding: 0, }, resource_id, hot_x, hot_y, _padding: 0, }) } } impl Drop for VirtIOGpu<'_, H, T> { fn drop(&mut self) { // Clear any pointers pointing to DMA regions, so the device doesn't try to access them // after they have been freed. self.transport.queue_unset(QUEUE_TRANSMIT); self.transport.queue_unset(QUEUE_CURSOR); } } #[repr(C)] struct Config { /// Signals pending events to the driver。 events_read: ReadOnly, /// Clears pending events in the device. events_clear: WriteOnly, /// Specifies the maximum number of scanouts supported by the device. /// /// Minimum value is 1, maximum value is 16. num_scanouts: Volatile, } impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Config") .field("events_read", &self.events_read) .field("num_scanouts", &self.num_scanouts) .finish() } } /// Display configuration has changed. const EVENT_DISPLAY: u32 = 1 << 0; bitflags! { struct Features: u64 { /// virgl 3D mode is supported. const VIRGL = 1 << 0; /// EDID is supported. const EDID = 1 << 1; // device independent const NOTIFY_ON_EMPTY = 1 << 24; // legacy const ANY_LAYOUT = 1 << 27; // legacy const RING_INDIRECT_DESC = 1 << 28; const RING_EVENT_IDX = 1 << 29; const UNUSED = 1 << 30; // legacy const VERSION_1 = 1 << 32; // detect legacy // since virtio v1.1 const ACCESS_PLATFORM = 1 << 33; const RING_PACKED = 1 << 34; const IN_ORDER = 1 << 35; const ORDER_PLATFORM = 1 << 36; const SR_IOV = 1 << 37; const NOTIFICATION_DATA = 1 << 38; } } #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Command { GetDisplayInfo = 0x100, ResourceCreate2d = 0x101, ResourceUnref = 0x102, SetScanout = 0x103, ResourceFlush = 0x104, TransferToHost2d = 0x105, ResourceAttachBacking = 0x106, ResourceDetachBacking = 0x107, GetCapsetInfo = 0x108, GetCapset = 0x109, GetEdid = 0x10a, UpdateCursor = 0x300, MoveCursor = 0x301, OkNodata = 0x1100, OkDisplayInfo = 0x1101, OkCapsetInfo = 0x1102, OkCapset = 0x1103, OkEdid = 0x1104, ErrUnspec = 0x1200, ErrOutOfMemory = 0x1201, ErrInvalidScanoutId = 0x1202, } const GPU_FLAG_FENCE: u32 = 1 << 0; #[repr(C)] #[derive(Debug, Clone, Copy)] struct CtrlHeader { hdr_type: Command, flags: u32, fence_id: u64, ctx_id: u32, _padding: u32, } impl CtrlHeader { fn with_type(hdr_type: Command) -> CtrlHeader { CtrlHeader { hdr_type, flags: 0, fence_id: 0, ctx_id: 0, _padding: 0, } } /// Return error if the type is not same as expected. fn check_type(&self, expected: Command) -> Result { if self.hdr_type == expected { Ok(()) } else { Err(Error::IoError) } } } #[repr(C)] #[derive(Debug, Copy, Clone, Default)] struct Rect { x: u32, y: u32, width: u32, height: u32, } #[repr(C)] #[derive(Debug)] struct RespDisplayInfo { header: CtrlHeader, rect: Rect, enabled: u32, flags: u32, } #[repr(C)] #[derive(Debug)] struct ResourceCreate2D { header: CtrlHeader, resource_id: u32, format: Format, width: u32, height: u32, } #[repr(u32)] #[derive(Debug)] enum Format { B8G8R8A8UNORM = 1, } #[repr(C)] #[derive(Debug)] struct ResourceAttachBacking { header: CtrlHeader, resource_id: u32, nr_entries: u32, // always 1 addr: u64, length: u32, _padding: u32, } #[repr(C)] #[derive(Debug)] struct SetScanout { header: CtrlHeader, rect: Rect, scanout_id: u32, resource_id: u32, } #[repr(C)] #[derive(Debug)] struct TransferToHost2D { header: CtrlHeader, rect: Rect, offset: u64, resource_id: u32, _padding: u32, } #[repr(C)] #[derive(Debug)] struct ResourceFlush { header: CtrlHeader, rect: Rect, resource_id: u32, _padding: u32, } #[repr(C)] #[derive(Debug, Clone, Copy)] struct CursorPos { scanout_id: u32, x: u32, y: u32, _padding: u32, } #[repr(C)] #[derive(Debug, Clone, Copy)] struct UpdateCursor { header: CtrlHeader, pos: CursorPos, resource_id: u32, hot_x: u32, hot_y: u32, _padding: u32, } const QUEUE_TRANSMIT: u16 = 0; const QUEUE_CURSOR: u16 = 1; const SCANOUT_ID: u32 = 0; const RESOURCE_ID_FB: u32 = 0xbabe; const RESOURCE_ID_CURSOR: u32 = 0xdade; const CURSOR_RECT: Rect = Rect { x: 0, y: 0, width: 64, height: 64, };