3abe41d258
This handles daemonization and getting the PciFunctionHandle.
560 lines
13 KiB
Rust
560 lines
13 KiB
Rust
//! `virtio-gpu` is a virtio based graphics adapter. It can operate in 2D mode and in 3D mode.
|
|
//!
|
|
//! XXX: 3D mode will offload rendering ops to the host gpu and therefore requires a GPU with 3D support
|
|
//! on the host machine.
|
|
|
|
// Notes for the future:
|
|
//
|
|
// `virtio-gpu` 2D acceleration is just blitting. 3D acceleration has 2 kinds:
|
|
// - virgl - OpenGL
|
|
// - venus - Vulkan
|
|
//
|
|
// The Venus driver requires support for the following from the `virtio-gpu` kernel driver:
|
|
// - VIRTGPU_PARAM_3D_FEATURES
|
|
// - VIRTGPU_PARAM_CAPSET_QUERY_FIX
|
|
// - VIRTGPU_PARAM_RESOURCE_BLOB
|
|
// - VIRTGPU_PARAM_HOST_VISIBLE
|
|
// - VIRTGPU_PARAM_CROSS_DEVICE
|
|
// - VIRTGPU_PARAM_CONTEXT_INIT
|
|
//
|
|
// cc https://docs.mesa3d.org/drivers/venus.html
|
|
// cc https://docs.mesa3d.org/drivers/virgl.html
|
|
|
|
use std::os::fd::AsRawFd;
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
use event::{user_data, EventQueue};
|
|
use pcid_interface::PciFunctionHandle;
|
|
|
|
use virtio_core::utils::VolatileCell;
|
|
use virtio_core::MSIX_PRIMARY_VECTOR;
|
|
|
|
mod scheme;
|
|
|
|
const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
|
|
const VIRTIO_GPU_MAX_SCANOUTS: usize = 16;
|
|
|
|
#[repr(C)]
|
|
pub struct GpuConfig {
|
|
/// Signals pending events to the driver.
|
|
pub events_read: VolatileCell<u32>, // read-only
|
|
/// Clears pending events in the device (write-to-clear).
|
|
pub events_clear: VolatileCell<u32>, // write-only
|
|
|
|
pub num_scanouts: VolatileCell<u32>,
|
|
pub num_capsets: VolatileCell<u32>,
|
|
}
|
|
|
|
impl GpuConfig {
|
|
#[inline]
|
|
pub fn num_scanouts(&self) -> u32 {
|
|
self.num_scanouts.get()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
#[repr(u32)]
|
|
pub enum CommandTy {
|
|
Undefined = 0,
|
|
|
|
// 2D commands
|
|
GetDisplayInfo = 0x0100,
|
|
ResourceCreate2d,
|
|
ResourceUnref,
|
|
SetScanout,
|
|
ResourceFlush,
|
|
TransferToHost2d,
|
|
ResourceAttachBacking,
|
|
ResourceDetachBacking,
|
|
GetCapsetInfo,
|
|
GetCapset,
|
|
GetEdid,
|
|
ResourceAssignUuid,
|
|
ResourceCreateBlob,
|
|
SetScanoutBlob,
|
|
|
|
// 3D commands
|
|
CtxCreate = 0x0200,
|
|
CtxDestroy,
|
|
CtxAttachResource,
|
|
CtxDetachResource,
|
|
ResourceCreate3d,
|
|
TransferToHost3d,
|
|
TransferFromHost3d,
|
|
Submit3d,
|
|
ResourceMapBlob,
|
|
ResourceUnmapBlob,
|
|
|
|
// cursor commands
|
|
UpdateCursor = 0x0300,
|
|
MoveCursor,
|
|
|
|
// success responses
|
|
RespOkNodata = 0x1100,
|
|
RespOkDisplayInfo,
|
|
RespOkCapsetInfo,
|
|
RespOkCapset,
|
|
RespOkEdid,
|
|
RespOkResourceUuid,
|
|
RespOkMapInfo,
|
|
|
|
// error responses
|
|
RespErrUnspec = 0x1200,
|
|
RespErrOutOfMemory,
|
|
RespErrInvalidScanoutId,
|
|
RespErrInvalidResourceId,
|
|
RespErrInvalidContextId,
|
|
RespErrInvalidParameter,
|
|
}
|
|
|
|
static_assertions::const_assert_eq!(core::mem::size_of::<CommandTy>(), 4);
|
|
|
|
const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0;
|
|
//const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1;
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct ControlHeader {
|
|
pub ty: CommandTy,
|
|
pub flags: u32,
|
|
pub fence_id: u64,
|
|
pub ctx_id: u32,
|
|
pub ring_index: u8,
|
|
padding: [u8; 3],
|
|
}
|
|
|
|
impl ControlHeader {
|
|
pub fn with_ty(ty: CommandTy) -> Self {
|
|
Self {
|
|
ty,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for ControlHeader {
|
|
fn default() -> Self {
|
|
Self {
|
|
ty: CommandTy::Undefined,
|
|
flags: 0,
|
|
fence_id: 0,
|
|
ctx_id: 0,
|
|
ring_index: 0,
|
|
padding: [0; 3],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[repr(C)]
|
|
pub struct GpuRect {
|
|
pub x: u32,
|
|
pub y: u32,
|
|
pub width: u32,
|
|
pub height: u32,
|
|
}
|
|
|
|
impl GpuRect {
|
|
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
|
|
Self {
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct DisplayInfo {
|
|
rect: GpuRect,
|
|
pub enabled: u32,
|
|
pub flags: u32,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct GetDisplayInfo {
|
|
pub header: ControlHeader,
|
|
pub display_info: [DisplayInfo; VIRTIO_GPU_MAX_SCANOUTS],
|
|
}
|
|
|
|
impl Default for GetDisplayInfo {
|
|
fn default() -> Self {
|
|
Self {
|
|
header: ControlHeader {
|
|
ty: CommandTy::GetDisplayInfo,
|
|
..Default::default()
|
|
},
|
|
|
|
display_info: unsafe { core::mem::zeroed() },
|
|
}
|
|
}
|
|
}
|
|
|
|
static RESOURCE_ALLOC: AtomicU32 = AtomicU32::new(1); // XXX: 0 is reserved for whatever that takes `resource_id`.
|
|
|
|
#[derive(PartialEq, Eq, 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 {
|
|
Unknown = 0,
|
|
|
|
Bgrx = 2,
|
|
Xrgb = 4,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct ResourceCreate2d {
|
|
pub header: ControlHeader,
|
|
resource_id: ResourceId,
|
|
format: ResourceFormat,
|
|
width: u32,
|
|
height: u32,
|
|
}
|
|
|
|
impl ResourceCreate2d {
|
|
fn new(resource_id: ResourceId, format: ResourceFormat, width: u32, height: u32) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::ResourceCreate2d),
|
|
resource_id,
|
|
format,
|
|
width,
|
|
height,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct MemEntry {
|
|
pub address: u64,
|
|
pub length: u32,
|
|
pub padding: u32,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct AttachBacking {
|
|
pub header: ControlHeader,
|
|
pub resource_id: ResourceId,
|
|
pub num_entries: u32,
|
|
}
|
|
|
|
impl AttachBacking {
|
|
pub fn new(resource_id: ResourceId, num_entries: u32) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::ResourceAttachBacking),
|
|
resource_id,
|
|
num_entries,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct DetachBacking {
|
|
pub header: ControlHeader,
|
|
pub resource_id: ResourceId,
|
|
pub padding: u32,
|
|
}
|
|
|
|
impl DetachBacking {
|
|
pub fn new(resource_id: ResourceId) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::ResourceDetachBacking),
|
|
resource_id,
|
|
padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct ResourceFlush {
|
|
pub header: ControlHeader,
|
|
pub rect: GpuRect,
|
|
pub resource_id: ResourceId,
|
|
pub padding: u32,
|
|
}
|
|
|
|
impl ResourceFlush {
|
|
pub fn new(resource_id: ResourceId, rect: GpuRect) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::ResourceFlush),
|
|
rect,
|
|
resource_id,
|
|
padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct ResourceUnref {
|
|
pub header: ControlHeader,
|
|
pub resource_id: ResourceId,
|
|
pub padding: u32,
|
|
}
|
|
|
|
impl ResourceUnref {
|
|
pub fn new(resource_id: ResourceId) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::ResourceUnref),
|
|
resource_id,
|
|
padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
pub struct SetScanout {
|
|
pub header: ControlHeader,
|
|
pub rect: GpuRect,
|
|
pub scanout_id: u32,
|
|
pub resource_id: ResourceId,
|
|
}
|
|
|
|
impl SetScanout {
|
|
pub fn new(scanout_id: u32, resource_id: ResourceId, rect: GpuRect) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::SetScanout),
|
|
|
|
rect,
|
|
scanout_id,
|
|
resource_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct XferToHost2d {
|
|
pub header: ControlHeader,
|
|
pub rect: GpuRect,
|
|
pub offset: u64,
|
|
pub resource_id: ResourceId,
|
|
pub padding: u32,
|
|
}
|
|
|
|
impl XferToHost2d {
|
|
pub fn new(resource_id: ResourceId, rect: GpuRect, offset: u64) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::TransferToHost2d),
|
|
rect,
|
|
offset,
|
|
resource_id,
|
|
padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct CursorPos {
|
|
pub scanout_id: u32,
|
|
pub x: i32,
|
|
pub y: i32,
|
|
_padding: u32,
|
|
}
|
|
|
|
impl CursorPos {
|
|
pub fn new(scanout_id: u32, x: i32, y: i32) -> Self {
|
|
Self {
|
|
scanout_id,
|
|
x,
|
|
y,
|
|
_padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */
|
|
#[derive(Debug)]
|
|
#[repr(C)]
|
|
pub struct UpdateCursor {
|
|
pub header: ControlHeader,
|
|
pub pos: CursorPos,
|
|
pub resource_id: ResourceId,
|
|
pub hot_x: i32,
|
|
pub hot_y: i32,
|
|
_padding: u32,
|
|
}
|
|
|
|
impl UpdateCursor {
|
|
pub fn update_cursor(x: i32, y: i32, hot_x: i32, hot_y: i32, resource_id: ResourceId) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::UpdateCursor),
|
|
pos: CursorPos::new(0, x, y),
|
|
resource_id,
|
|
hot_x,
|
|
hot_y,
|
|
_padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct MoveCursor {
|
|
pub header: ControlHeader,
|
|
pub pos: CursorPos,
|
|
pub resource_id: ResourceId,
|
|
pub hot_x: i32,
|
|
pub hot_y: i32,
|
|
_padding: u32,
|
|
}
|
|
|
|
impl MoveCursor {
|
|
pub fn move_cursor(x: i32, y: i32) -> Self {
|
|
Self {
|
|
header: ControlHeader::with_ty(CommandTy::MoveCursor),
|
|
pos: CursorPos::new(0, x, y),
|
|
resource_id: ResourceId(0),
|
|
hot_x: 0,
|
|
hot_y: 0,
|
|
_padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
static DEVICE: spin::Once<virtio_core::Device> = spin::Once::new();
|
|
|
|
fn main() {
|
|
pcid_interface::pci_daemon(daemon_runner);
|
|
}
|
|
|
|
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
|
deamon(daemon, pcid_handle).unwrap();
|
|
unreachable!();
|
|
}
|
|
|
|
fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> {
|
|
common::setup_logging(
|
|
"graphics",
|
|
"pci",
|
|
"virtio-gpud",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
// Double check that we have the right device.
|
|
//
|
|
// 0x1050 - virtio-gpu
|
|
let pci_config = pcid_handle.config();
|
|
|
|
assert_eq!(pci_config.func.full_device_id.device_id, 0x1050);
|
|
log::info!("virtio-gpu: initiating startup sequence :^)");
|
|
|
|
let device = DEVICE.try_call_once(|| virtio_core::probe_device(&mut pcid_handle))?;
|
|
let config = unsafe { &mut *(device.device_space as *mut GpuConfig) };
|
|
|
|
// Negotiate features.
|
|
device.transport.finalize_features();
|
|
|
|
// Queue for sending control commands.
|
|
let control_queue = device
|
|
.transport
|
|
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
|
|
|
// Queue for sending cursor updates.
|
|
let cursor_queue = device
|
|
.transport
|
|
.setup_queue(MSIX_PRIMARY_VECTOR, &device.irq_handle)?;
|
|
|
|
device.transport.setup_config_notify(MSIX_PRIMARY_VECTOR);
|
|
|
|
device.transport.run_device();
|
|
deamon.ready();
|
|
|
|
let (mut scheme, mut inputd_handle) = futures::executor::block_on(scheme::GpuScheme::new(
|
|
config,
|
|
control_queue.clone(),
|
|
cursor_queue.clone(),
|
|
device.transport.clone(),
|
|
))?;
|
|
|
|
user_data! {
|
|
enum Source {
|
|
Input,
|
|
Scheme,
|
|
Interrupt,
|
|
}
|
|
}
|
|
|
|
let event_queue: EventQueue<Source> =
|
|
EventQueue::new().expect("virtio-gpud: failed to create event queue");
|
|
event_queue
|
|
.subscribe(
|
|
inputd_handle.inner().as_raw_fd() as usize,
|
|
Source::Input,
|
|
event::EventFlags::READ,
|
|
)
|
|
.unwrap();
|
|
event_queue
|
|
.subscribe(
|
|
scheme.event_handle().raw(),
|
|
Source::Scheme,
|
|
event::EventFlags::READ,
|
|
)
|
|
.unwrap();
|
|
event_queue
|
|
.subscribe(
|
|
device.irq_handle.as_raw_fd() as usize,
|
|
Source::Interrupt,
|
|
event::EventFlags::READ,
|
|
)
|
|
.unwrap();
|
|
|
|
let all = [Source::Input, Source::Scheme, Source::Interrupt];
|
|
for event in all
|
|
.into_iter()
|
|
.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) = inputd_handle
|
|
.read_vt_event()
|
|
.expect("virtio-gpud: failed to read display handle")
|
|
{
|
|
scheme.handle_vt_event(vt_event);
|
|
}
|
|
}
|
|
Source::Scheme => {
|
|
scheme
|
|
.tick()
|
|
.expect("virtio-gpud: failed to process scheme events");
|
|
}
|
|
Source::Interrupt => loop {
|
|
let before_gen = device.transport.config_generation();
|
|
|
|
let events = config.events_read.get();
|
|
|
|
if events & VIRTIO_GPU_EVENT_DISPLAY != 0 {
|
|
futures::executor::block_on(scheme.adapter_mut().update_displays(config))
|
|
.unwrap();
|
|
scheme.notify_displays_changed();
|
|
config.events_clear.set(VIRTIO_GPU_EVENT_DISPLAY);
|
|
}
|
|
|
|
let after_gen = device.transport.config_generation();
|
|
if before_gen == after_gen {
|
|
break;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
std::process::exit(0);
|
|
}
|