blob: 1fa21326342128c3c67de0d9aa42017cbb071bd4 [file] [log] [blame]
//! Driver for VirtIO GPU devices.
use crate::hal::{Dma, Hal};
use crate::queue::VirtQueue;
use crate::transport::Transport;
use crate::volatile::{volread, ReadOnly, Volatile, WriteOnly};
use crate::{pages, Error, Result, PAGE_SIZE};
use bitflags::bitflags;
use log::info;
/// 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>,
}
/// 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,
};