123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- 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<Rect>,
- /// DMA area of frame buffer.
- frame_buffer_dma: Option<Dma<H>>,
- /// DMA area of cursor image buffer.
- cursor_buffer_dma: Option<Dma<H>>,
- /// Queue for sending control commands.
- control_queue: VirtQueue<H>,
- /// Queue for sending cursor commands.
- cursor_queue: VirtQueue<H>,
- /// Queue buffer DMA
- queue_buf_dma: Dma<H>,
- /// Send buffer for queue.
- queue_buf_send: &'a mut [u8],
- /// Recv buffer for queue.
- queue_buf_recv: &'a mut [u8],
- }
- impl<H: Hal, T: Transport> VirtIOGpu<'_, H, T> {
- /// Create a new VirtIO-Gpu driver.
- pub fn new(mut transport: T) -> Result<Self> {
- 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::<Config>()?;
- 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<Req, Rsp>(&mut self, req: Req) -> Result<Rsp> {
- 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<Req>(&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<RespDisplayInfo> {
- 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<H: Hal, T: Transport> 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<u32>,
- /// Clears pending events in the device.
- events_clear: WriteOnly<u32>,
- /// Specifies the maximum number of scanouts supported by the device.
- ///
- /// Minimum value is 1, maximum value is 16.
- num_scanouts: Volatile<u32>,
- }
- 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,
- };
|