mod edid;
pub use self::edid::Edid;
use crate::config::{ReadOnly, WriteOnly, read_config};
use crate::hal::{BufferDirection, Dma, Hal};
use crate::queue::VirtQueue;
use crate::transport::{InterruptStatus, Transport};
use crate::{Error, PAGE_SIZE, Result, pages};
use alloc::boxed::Box;
use alloc::vec::Vec;
use bitflags::bitflags;
use log::info;
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
const QUEUE_SIZE: u16 = 2;
const SUPPORTED_FEATURES: Features = Features::RING_EVENT_IDX
.union(Features::RING_INDIRECT_DESC)
.union(Features::VERSION_1)
.union(Features::EDID);
pub struct VirtIOGpu<H: Hal, T: Transport> {
transport: T,
rect: Option<Rect>,
frame_buffer_dma: Option<Dma<H>>,
cursor_buffer_dma: Option<Dma<H>>,
control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
queue_buf_send: Box<[u8]>,
queue_buf_recv: Box<[u8]>,
has_edid: bool,
}
impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
pub fn new(mut transport: T) -> Result<Self> {
let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
let events_read = read_config!(transport, Config, events_read)?;
let num_scanouts = read_config!(transport, Config, num_scanouts)?;
info!(
"events_read: {:#x}, num_scanouts: {:#x}",
events_read, num_scanouts
);
let control_queue = VirtQueue::new(
&mut transport,
QUEUE_TRANSMIT,
negotiated_features.contains(Features::RING_INDIRECT_DESC),
negotiated_features.contains(Features::RING_EVENT_IDX),
)?;
let cursor_queue = VirtQueue::new(
&mut transport,
QUEUE_CURSOR,
negotiated_features.contains(Features::RING_INDIRECT_DESC),
negotiated_features.contains(Features::RING_EVENT_IDX),
)?;
let queue_buf_send = FromZeros::new_box_zeroed_with_elems(PAGE_SIZE).unwrap();
let queue_buf_recv = FromZeros::new_box_zeroed_with_elems(PAGE_SIZE).unwrap();
transport.finish_init();
let has_edid = negotiated_features.contains(Features::EDID);
Ok(VirtIOGpu {
transport,
frame_buffer_dma: None,
cursor_buffer_dma: None,
rect: None,
control_queue,
cursor_queue,
queue_buf_send,
queue_buf_recv,
has_edid,
})
}
pub fn ack_interrupt(&mut self) -> InterruptStatus {
self.transport.ack_interrupt()
}
pub fn resolution(&mut self) -> Result<(u32, u32)> {
let display_info = self.get_display_info()?;
Ok((display_info.rect.width, display_info.rect.height))
}
pub fn get_edid(&mut self, scanout: u32) -> Result<Edid> {
if !self.has_edid {
return Err(Error::Unsupported);
}
let rsp: RespEdid = self.request(CmdGetEdid {
header: CtrlHeader::with_type(Command::GET_EDID),
scanout,
_padding: 0,
})?;
rsp.header.check_type(Command::OK_EDID)?;
Ok(Edid {
data: rsp.edid,
size: rsp.size,
})
}
pub fn edid_preferred_resolution(&mut self) -> Result<(u32, u32)> {
let edid = self.get_edid(SCANOUT_ID)?;
edid.preferred_resolution()
}
pub fn edid_supported_resolutions(&mut self) -> Result<Vec<(u32, u32)>> {
let edid = self.get_edid(SCANOUT_ID)?;
Ok(edid.standard_timings())
}
pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
let display_info = self.get_display_info()?;
info!("=> {:?}", display_info);
self.change_resolution(display_info.rect.width, display_info.rect.height)
}
pub fn change_resolution(&mut self, width: u32, height: u32) -> Result<&mut [u8]> {
let rect = Rect {
x: 0,
y: 0,
width,
height,
};
if self.frame_buffer_dma.is_some() {
self.set_scanout(Rect::default(), SCANOUT_ID, 0)?;
self.resource_detach_backing(RESOURCE_ID_FB)?;
self.resource_unref(RESOURCE_ID_FB)?;
self.frame_buffer_dma = None;
}
self.rect = Some(rect);
self.resource_create_2d(RESOURCE_ID_FB, width, height)?;
let size = width * height * 4;
let frame_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
self.set_scanout(rect, SCANOUT_ID, RESOURCE_ID_FB)?;
let buf = unsafe { frame_buffer_dma.raw_slice().as_mut() };
self.frame_buffer_dma = Some(frame_buffer_dma);
Ok(buf)
}
pub fn flush(&mut self) -> Result {
let rect = self.rect.ok_or(Error::NotReady)?;
self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
self.resource_flush(rect, RESOURCE_ID_FB)?;
Ok(())
}
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), BufferDirection::DriverToDevice)?;
let buf = unsafe { cursor_buffer_dma.raw_slice().as_mut() };
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(())
}
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(())
}
fn request<Req: IntoBytes + Immutable, Rsp: FromBytes>(&mut self, req: Req) -> Result<Rsp> {
req.write_to_prefix(&mut self.queue_buf_send).unwrap();
self.control_queue.add_notify_wait_pop(
&[&self.queue_buf_send],
&mut [&mut self.queue_buf_recv],
&mut self.transport,
)?;
Ok(Rsp::read_from_prefix(&self.queue_buf_recv).unwrap().0)
}
fn cursor_request<Req: IntoBytes + Immutable>(&mut self, req: Req) -> Result {
req.write_to_prefix(&mut self.queue_buf_send).unwrap();
self.cursor_queue.add_notify_wait_pop(
&[&self.queue_buf_send],
&mut [],
&mut self.transport,
)?;
Ok(())
}
fn get_display_info(&mut self) -> Result<RespDisplayInfo> {
let info: RespDisplayInfo =
self.request(CtrlHeader::with_type(Command::GET_DISPLAY_INFO))?;
info.header.check_type(Command::OK_DISPLAY_INFO)?;
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::RESOURCE_CREATE_2D),
resource_id,
format: Format::B8G8R8A8UNORM,
width,
height,
})?;
rsp.check_type(Command::OK_NODATA)
}
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::SET_SCANOUT),
rect,
scanout_id,
resource_id,
})?;
rsp.check_type(Command::OK_NODATA)
}
fn resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result {
let rsp: CtrlHeader = self.request(ResourceFlush {
header: CtrlHeader::with_type(Command::RESOURCE_FLUSH),
rect,
resource_id,
_padding: 0,
})?;
rsp.check_type(Command::OK_NODATA)
}
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::TRANSFER_TO_HOST_2D),
rect,
offset,
resource_id,
_padding: 0,
})?;
rsp.check_type(Command::OK_NODATA)
}
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::RESOURCE_ATTACH_BACKING),
resource_id,
nr_entries: 1,
addr: paddr,
length,
_padding: 0,
})?;
rsp.check_type(Command::OK_NODATA)
}
fn resource_detach_backing(&mut self, resource_id: u32) -> Result {
let rsp: CtrlHeader = self.request(ResourceDetachBacking {
header: CtrlHeader::with_type(Command::RESOURCE_DETACH_BACKING),
resource_id,
_padding: 0,
})?;
rsp.check_type(Command::OK_NODATA)
}
fn resource_unref(&mut self, resource_id: u32) -> Result {
let rsp: CtrlHeader = self.request(ResourceUnref {
header: CtrlHeader::with_type(Command::RESOURCE_UNREF),
resource_id,
_padding: 0,
})?;
rsp.check_type(Command::OK_NODATA)
}
#[allow(clippy::too_many_arguments)]
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::MOVE_CURSOR)
} else {
CtrlHeader::with_type(Command::UPDATE_CURSOR)
},
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) {
self.transport.queue_unset(QUEUE_TRANSMIT);
self.transport.queue_unset(QUEUE_CURSOR);
}
}
#[repr(C)]
struct Config {
events_read: ReadOnly<u32>,
events_clear: WriteOnly<u32>,
num_scanouts: ReadOnly<u32>,
}
const EVENT_DISPLAY: u32 = 1 << 0;
bitflags! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct Features: u64 {
const VIRGL = 1 << 0;
const EDID = 1 << 1;
const NOTIFY_ON_EMPTY = 1 << 24; const ANY_LAYOUT = 1 << 27; const RING_INDIRECT_DESC = 1 << 28;
const RING_EVENT_IDX = 1 << 29;
const UNUSED = 1 << 30; const VERSION_1 = 1 << 32;
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(transparent)]
#[derive(Clone, Copy, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
struct Command(u32);
impl Command {
const GET_DISPLAY_INFO: Command = Command(0x100);
const RESOURCE_CREATE_2D: Command = Command(0x101);
const RESOURCE_UNREF: Command = Command(0x102);
const SET_SCANOUT: Command = Command(0x103);
const RESOURCE_FLUSH: Command = Command(0x104);
const TRANSFER_TO_HOST_2D: Command = Command(0x105);
const RESOURCE_ATTACH_BACKING: Command = Command(0x106);
const RESOURCE_DETACH_BACKING: Command = Command(0x107);
const GET_CAPSET_INFO: Command = Command(0x108);
const GET_CAPSET: Command = Command(0x109);
const GET_EDID: Command = Command(0x10a);
const UPDATE_CURSOR: Command = Command(0x300);
const MOVE_CURSOR: Command = Command(0x301);
const OK_NODATA: Command = Command(0x1100);
const OK_DISPLAY_INFO: Command = Command(0x1101);
const OK_CAPSET_INFO: Command = Command(0x1102);
const OK_CAPSET: Command = Command(0x1103);
const OK_EDID: Command = Command(0x1104);
const ERR_UNSPEC: Command = Command(0x1200);
const ERR_OUT_OF_MEMORY: Command = Command(0x1201);
const ERR_INVALID_SCANOUT_ID: Command = Command(0x1202);
}
const GPU_FLAG_FENCE: u32 = 1 << 0;
#[repr(C)]
#[derive(Debug, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
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,
}
}
fn check_type(&self, expected: Command) -> Result {
if self.hdr_type == expected {
Ok(())
} else {
Err(Error::IoError)
}
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
struct Rect {
x: u32,
y: u32,
width: u32,
height: u32,
}
#[repr(C)]
#[derive(Debug, FromBytes, Immutable, KnownLayout)]
struct RespDisplayInfo {
header: CtrlHeader,
rect: Rect,
enabled: u32,
flags: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct CmdGetEdid {
header: CtrlHeader,
scanout: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Debug, FromBytes, Immutable, KnownLayout)]
struct RespEdid {
header: CtrlHeader,
size: u32,
_padding: u32,
edid: [u8; 1024],
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct ResourceCreate2D {
header: CtrlHeader,
resource_id: u32,
format: Format,
width: u32,
height: u32,
}
#[repr(u32)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
enum Format {
B8G8R8A8UNORM = 1,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct ResourceAttachBacking {
header: CtrlHeader,
resource_id: u32,
nr_entries: u32, addr: u64,
length: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct ResourceDetachBacking {
header: CtrlHeader,
resource_id: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct ResourceUnref {
header: CtrlHeader,
resource_id: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct SetScanout {
header: CtrlHeader,
rect: Rect,
scanout_id: u32,
resource_id: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct TransferToHost2D {
header: CtrlHeader,
rect: Rect,
offset: u64,
resource_id: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
struct ResourceFlush {
header: CtrlHeader,
rect: Rect,
resource_id: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Immutable, IntoBytes, KnownLayout)]
struct CursorPos {
scanout_id: u32,
x: u32,
y: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Immutable, IntoBytes, KnownLayout)]
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,
};