mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-26 22:48:17 +00:00
Use String::from_utf8_lossy() instead of CStr::from_bytes_with_nul() since the virtio-gpu driver sends debug_name without null terminator. Signed-off-by: Matej Hrica <mhrica@redhat.com>
1447 lines
50 KiB
Rust
1447 lines
50 KiB
Rust
// Copyright 2024 Red Hat Inc
|
|
// Copyright 2019 The ChromiumOS Authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
|
|
|
|
#![allow(non_camel_case_types)]
|
|
|
|
use std::{
|
|
borrow::Cow,
|
|
cmp::min,
|
|
convert::From,
|
|
fmt::{self, Display},
|
|
io::{self, Read, Write},
|
|
marker::PhantomData,
|
|
mem::{size_of, size_of_val},
|
|
};
|
|
|
|
use log::trace;
|
|
use rutabaga_gfx::RutabagaError;
|
|
use thiserror::Error;
|
|
pub use virtio_bindings::virtio_gpu::{
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE as VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_CTX_CREATE as VIRTIO_GPU_CMD_CTX_CREATE,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_CTX_DESTROY as VIRTIO_GPU_CMD_CTX_DESTROY,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE as VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_GET_CAPSET as VIRTIO_GPU_CMD_GET_CAPSET,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_GET_CAPSET_INFO as VIRTIO_GPU_CMD_GET_CAPSET_INFO,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_GET_DISPLAY_INFO as VIRTIO_GPU_CMD_GET_DISPLAY_INFO,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_GET_EDID as VIRTIO_GPU_CMD_GET_EDID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_MOVE_CURSOR as VIRTIO_GPU_CMD_MOVE_CURSOR,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID as VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING as VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_CREATE_2D as VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_CREATE_3D as VIRTIO_GPU_CMD_RESOURCE_CREATE_3D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB as VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING as VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_FLUSH as VIRTIO_GPU_CMD_RESOURCE_FLUSH,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB as VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB as VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_RESOURCE_UNREF as VIRTIO_GPU_CMD_RESOURCE_UNREF,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_SET_SCANOUT as VIRTIO_GPU_CMD_SET_SCANOUT,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_SET_SCANOUT_BLOB as VIRTIO_GPU_CMD_SET_SCANOUT_BLOB,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_SUBMIT_3D as VIRTIO_GPU_CMD_SUBMIT_3D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D as VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D as VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D as VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_CMD_UPDATE_CURSOR as VIRTIO_GPU_CMD_UPDATE_CURSOR,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID as VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER as VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID as VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID as VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY as VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_ERR_UNSPEC as VIRTIO_GPU_RESP_ERR_UNSPEC,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_CAPSET as VIRTIO_GPU_RESP_OK_CAPSET,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_CAPSET_INFO as VIRTIO_GPU_RESP_OK_CAPSET_INFO,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_DISPLAY_INFO as VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_EDID as VIRTIO_GPU_RESP_OK_EDID,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_MAP_INFO as VIRTIO_GPU_RESP_OK_MAP_INFO,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_NODATA as VIRTIO_GPU_RESP_OK_NODATA,
|
|
virtio_gpu_ctrl_type_VIRTIO_GPU_RESP_OK_RESOURCE_UUID as VIRTIO_GPU_RESP_OK_RESOURCE_UUID,
|
|
};
|
|
use virtio_queue::{Reader, Writer};
|
|
use vm_memory::{ByteValued, GuestAddress, Le32, Le64};
|
|
|
|
use crate::device::{self, Error};
|
|
|
|
pub const QUEUE_SIZE: usize = 1024;
|
|
pub const NUM_QUEUES: usize = 2;
|
|
|
|
pub const CONTROL_QUEUE: u16 = 0;
|
|
pub const CURSOR_QUEUE: u16 = 1;
|
|
pub const POLL_EVENT: u16 = 3;
|
|
|
|
/// 3D resource creation parameters. Also used to create 2D resource.
|
|
///
|
|
/// Constants based on Mesa's (internal) Gallium interface. Not in the
|
|
/// virtio-gpu spec, but should be since dumb resources can't work with
|
|
/// gfxstream/virglrenderer without this.
|
|
pub const VIRTIO_GPU_TEXTURE_2D: u32 = 2;
|
|
pub const VIRTIO_GPU_BIND_RENDER_TARGET: u32 = 2;
|
|
|
|
pub const VIRTIO_GPU_MAX_SCANOUTS: u32 = 16;
|
|
|
|
/// `CHROMIUM(b/277982577)` success responses
|
|
pub const VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO: u32 = 0x11FF;
|
|
|
|
/// Create a OS-specific handle from guest memory (not upstreamed).
|
|
pub const VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE: u32 = 0x0008;
|
|
|
|
pub const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0;
|
|
pub const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1;
|
|
|
|
/// Virtio Gpu Configuration
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct VirtioGpuConfig {
|
|
/// Signals pending events to the driver
|
|
pub events_read: Le32,
|
|
/// Clears pending events in the device
|
|
pub events_clear: Le32,
|
|
/// Maximum number of scanouts supported by the device
|
|
pub num_scanouts: Le32,
|
|
/// Maximum number of capability sets supported by the device
|
|
pub num_capsets: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for VirtioGpuConfig {}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct InvalidCommandType(u32);
|
|
|
|
impl std::fmt::Display for InvalidCommandType {
|
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(fmt, "Invalid command type {}", self.0)
|
|
}
|
|
}
|
|
|
|
impl From<InvalidCommandType> for crate::device::Error {
|
|
fn from(val: InvalidCommandType) -> Self {
|
|
Self::InvalidCommandType(val.0)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for InvalidCommandType {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_ctrl_hdr {
|
|
pub type_: Le32,
|
|
pub flags: Le32,
|
|
pub fence_id: Le64,
|
|
pub ctx_id: Le32,
|
|
pub ring_idx: u8,
|
|
pub padding: [u8; 3],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_ctrl_hdr {}
|
|
|
|
/// Data passed in the cursor `vq`
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_cursor_pos {
|
|
pub scanout_id: Le32,
|
|
pub x: Le32,
|
|
pub y: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_cursor_pos {}
|
|
|
|
// VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_update_cursor {
|
|
/// update & move
|
|
pub pos: virtio_gpu_cursor_pos,
|
|
/// update only
|
|
pub resource_id: Le32,
|
|
/// update only
|
|
pub hot_x: Le32,
|
|
/// update only
|
|
pub hot_y: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_update_cursor {}
|
|
|
|
/// Data passed in the control `vq`, 2d related
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_rect {
|
|
pub x: Le32,
|
|
pub y: Le32,
|
|
pub width: Le32,
|
|
pub height: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_rect {}
|
|
|
|
// VIRTIO_GPU_CMD_GET_EDID
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_get_edid {
|
|
pub scanout: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_get_edid {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_UNREF
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_unref {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_unref {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: create a 2d resource with a format
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_create_2d {
|
|
pub resource_id: Le32,
|
|
pub format: Le32,
|
|
pub width: Le32,
|
|
pub height: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_create_2d {}
|
|
|
|
// VIRTIO_GPU_CMD_SET_SCANOUT
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_set_scanout {
|
|
pub r: virtio_gpu_rect,
|
|
pub scanout_id: Le32,
|
|
pub resource_id: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_set_scanout {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_FLUSH
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_flush {
|
|
pub r: virtio_gpu_rect,
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_flush {}
|
|
|
|
// VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: simple transfer to_host
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_transfer_to_host_2d {
|
|
pub r: virtio_gpu_rect,
|
|
pub offset: Le64,
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_transfer_to_host_2d {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_mem_entry {
|
|
pub addr: Le64,
|
|
pub length: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_mem_entry {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_attach_backing {
|
|
pub resource_id: Le32,
|
|
pub nr_entries: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_attach_backing {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_detach_backing {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_detach_backing {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_display_one {
|
|
pub r: virtio_gpu_rect,
|
|
pub enabled: Le32,
|
|
pub flags: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_display_one {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_display_info {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub pmodes: [virtio_gpu_display_one; VIRTIO_GPU_MAX_SCANOUTS as usize],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_display_info {}
|
|
|
|
const EDID_BLOB_MAX_SIZE: usize = 1024;
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_edid {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub size: Le32,
|
|
pub padding: Le32,
|
|
pub edid: [u8; EDID_BLOB_MAX_SIZE],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_edid {}
|
|
|
|
// data passed in the control vq, 3d related
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_box {
|
|
pub x: Le32,
|
|
pub y: Le32,
|
|
pub z: Le32,
|
|
pub w: Le32,
|
|
pub h: Le32,
|
|
pub d: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_box {}
|
|
|
|
// VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_transfer_host_3d {
|
|
pub box_: virtio_gpu_box,
|
|
pub offset: Le64,
|
|
pub resource_id: Le32,
|
|
pub level: Le32,
|
|
pub stride: Le32,
|
|
pub layer_stride: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_transfer_host_3d {}
|
|
|
|
// VIRTIO_GPU_CMD_RESOURCE_CREATE_3D
|
|
pub const VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP: u32 = 1 << 0;
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_create_3d {
|
|
pub resource_id: Le32,
|
|
pub target: Le32,
|
|
pub format: Le32,
|
|
pub bind: Le32,
|
|
pub width: Le32,
|
|
pub height: Le32,
|
|
pub depth: Le32,
|
|
pub array_size: Le32,
|
|
pub last_level: Le32,
|
|
pub nr_samples: Le32,
|
|
pub flags: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
impl From<virtio_gpu_resource_create_2d> for virtio_gpu_resource_create_3d {
|
|
fn from(args: virtio_gpu_resource_create_2d) -> Self {
|
|
Self {
|
|
resource_id: args.resource_id,
|
|
target: VIRTIO_GPU_TEXTURE_2D.into(),
|
|
format: args.format,
|
|
bind: VIRTIO_GPU_BIND_RENDER_TARGET.into(),
|
|
width: args.width,
|
|
height: args.height,
|
|
depth: 1.into(), // default for 2D
|
|
array_size: 1.into(), // default for 2D
|
|
last_level: 0.into(), // default mipmap
|
|
nr_samples: 0.into(), // default sample count
|
|
flags: 0.into(),
|
|
padding: 0.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_create_3d {}
|
|
|
|
// VIRTIO_GPU_CMD_CTX_CREATE
|
|
pub const VIRTIO_GPU_CONTEXT_INIT_CAPSET_ID_MASK: u32 = 1 << 0;
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_ctx_create {
|
|
pub nlen: Le32,
|
|
pub context_init: Le32,
|
|
pub debug_name: [u8; 64],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_ctx_create {}
|
|
|
|
impl Default for virtio_gpu_ctx_create {
|
|
fn default() -> Self {
|
|
Self {
|
|
nlen: 0.into(),
|
|
context_init: 0.into(),
|
|
debug_name: [0; 64],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl virtio_gpu_ctx_create {
|
|
pub fn get_debug_name(&self) -> Cow<'_, str> {
|
|
let len = min(64, <Le32 as Into<u32>>::into(self.nlen) as usize);
|
|
String::from_utf8_lossy(&self.debug_name[..len])
|
|
}
|
|
}
|
|
impl fmt::Debug for virtio_gpu_ctx_create {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct("virtio_gpu_ctx_create")
|
|
.field("debug_name", &self.get_debug_name())
|
|
.field("context_init", &self.context_init)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
// VIRTIO_GPU_CMD_CTX_DESTROY
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_ctx_destroy {}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_ctx_destroy {}
|
|
|
|
// VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_ctx_resource {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_ctx_resource {}
|
|
|
|
// VIRTIO_GPU_CMD_SUBMIT_3D
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_cmd_submit {
|
|
pub size: Le32,
|
|
|
|
// The in-fence IDs are prepended to the cmd_buf and memory layout
|
|
// of the VIRTIO_GPU_CMD_SUBMIT_3D buffer looks like this:
|
|
// _________________
|
|
// | CMD_SUBMIT_3D |
|
|
// -----------------
|
|
// | header |
|
|
// | in-fence IDs |
|
|
// | cmd_buf |
|
|
// -----------------
|
|
//
|
|
// This makes in-fence IDs naturally aligned to the sizeof(u64) inside
|
|
// of the virtio buffer.
|
|
pub num_in_fences: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_cmd_submit {}
|
|
|
|
// VIRTIO_GPU_CMD_GET_CAPSET_INFO
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_get_capset_info {
|
|
pub capset_index: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_get_capset_info {}
|
|
|
|
// VIRTIO_GPU_RESP_OK_CAPSET_INFO
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_capset_info {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub capset_id: Le32,
|
|
pub capset_max_version: Le32,
|
|
pub capset_max_size: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_capset_info {}
|
|
|
|
// VIRTIO_GPU_CMD_GET_CAPSET
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_get_capset {
|
|
pub capset_id: Le32,
|
|
pub capset_version: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_get_capset {}
|
|
|
|
// VIRTIO_GPU_RESP_OK_CAPSET
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_capset {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub capset_data: PhantomData<[u8]>,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_capset {}
|
|
|
|
// VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_resource_plane_info {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub count: Le32,
|
|
pub padding: Le32,
|
|
pub format_modifier: Le64,
|
|
pub strides: [Le32; 4],
|
|
pub offsets: [Le32; 4],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_resource_plane_info {}
|
|
|
|
pub const PLANE_INFO_MAX_COUNT: usize = 4;
|
|
|
|
pub const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_create_blob {
|
|
pub resource_id: Le32,
|
|
pub blob_mem: Le32,
|
|
pub blob_flags: Le32,
|
|
pub nr_entries: Le32,
|
|
pub blob_id: Le64,
|
|
pub size: Le64,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_create_blob {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_map_blob {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
pub offset: Le64,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_map_blob {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_unmap_blob {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_unmap_blob {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_map_info {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub map_info: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_map_info {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resource_assign_uuid {
|
|
pub resource_id: Le32,
|
|
pub padding: Le32,
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resource_assign_uuid {}
|
|
|
|
#[derive(Copy, Clone, Debug, Default)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_resp_resource_uuid {
|
|
pub hdr: virtio_gpu_ctrl_hdr,
|
|
pub uuid: [u8; 16],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_resp_resource_uuid {}
|
|
|
|
// VIRTIO_GPU_CMD_SET_SCANOUT_BLOB
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct virtio_gpu_set_scanout_blob {
|
|
pub r: virtio_gpu_rect,
|
|
pub scanout_id: Le32,
|
|
pub resource_id: Le32,
|
|
pub width: Le32,
|
|
pub height: Le32,
|
|
pub format: Le32,
|
|
pub padding: Le32,
|
|
pub strides: [Le32; 4],
|
|
pub offsets: [Le32; 4],
|
|
}
|
|
|
|
// SAFETY: The layout of the structure is fixed and can be initialized by
|
|
// reading its content from byte array.
|
|
unsafe impl ByteValued for virtio_gpu_set_scanout_blob {}
|
|
|
|
// simple formats for fbcon/X use
|
|
pub const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;
|
|
pub const VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: u32 = 2;
|
|
pub const VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: u32 = 3;
|
|
pub const VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: u32 = 4;
|
|
pub const VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: u32 = 67;
|
|
pub const VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: u32 = 68;
|
|
pub const VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: u32 = 121;
|
|
pub const VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: u32 = 134;
|
|
|
|
/// A virtio gpu command and associated metadata specific to each command.
|
|
#[derive(Clone, PartialEq, Eq)]
|
|
pub enum GpuCommand {
|
|
GetDisplayInfo,
|
|
GetEdid(virtio_gpu_get_edid),
|
|
ResourceCreate2d(virtio_gpu_resource_create_2d),
|
|
ResourceUnref(virtio_gpu_resource_unref),
|
|
SetScanout(virtio_gpu_set_scanout),
|
|
SetScanoutBlob(virtio_gpu_set_scanout_blob),
|
|
ResourceFlush(virtio_gpu_resource_flush),
|
|
TransferToHost2d(virtio_gpu_transfer_to_host_2d),
|
|
ResourceAttachBacking(
|
|
virtio_gpu_resource_attach_backing,
|
|
Vec<(GuestAddress, usize)>,
|
|
),
|
|
ResourceDetachBacking(virtio_gpu_resource_detach_backing),
|
|
GetCapsetInfo(virtio_gpu_get_capset_info),
|
|
GetCapset(virtio_gpu_get_capset),
|
|
CtxCreate(virtio_gpu_ctx_create),
|
|
CtxDestroy(virtio_gpu_ctx_destroy),
|
|
CtxAttachResource(virtio_gpu_ctx_resource),
|
|
CtxDetachResource(virtio_gpu_ctx_resource),
|
|
ResourceCreate3d(virtio_gpu_resource_create_3d),
|
|
TransferToHost3d(virtio_gpu_transfer_host_3d),
|
|
TransferFromHost3d(virtio_gpu_transfer_host_3d),
|
|
CmdSubmit3d {
|
|
cmd_data: Vec<u8>,
|
|
fence_ids: Vec<u64>,
|
|
},
|
|
ResourceCreateBlob(virtio_gpu_resource_create_blob, Vec<(GuestAddress, usize)>),
|
|
ResourceMapBlob(virtio_gpu_resource_map_blob),
|
|
ResourceUnmapBlob(virtio_gpu_resource_unmap_blob),
|
|
UpdateCursor(virtio_gpu_update_cursor),
|
|
MoveCursor(virtio_gpu_update_cursor),
|
|
ResourceAssignUuid(virtio_gpu_resource_assign_uuid),
|
|
}
|
|
|
|
/// An error indicating something went wrong decoding a `GpuCommand`. These
|
|
/// correspond to `VIRTIO_GPU_CMD_*`.
|
|
#[derive(Error, Debug)]
|
|
pub enum GpuCommandDecodeError {
|
|
/// The type of the command was invalid.
|
|
#[error("invalid command type ({0})")]
|
|
InvalidType(u32),
|
|
/// An I/O error occurred.
|
|
#[error("an I/O error occurred: {0}")]
|
|
IO(io::Error),
|
|
#[error("Descriptor read failed")]
|
|
DescriptorReadFailed,
|
|
}
|
|
|
|
impl From<io::Error> for GpuCommandDecodeError {
|
|
fn from(e: io::Error) -> Self {
|
|
Self::IO(e)
|
|
}
|
|
}
|
|
|
|
impl From<device::Error> for GpuCommandDecodeError {
|
|
fn from(_: device::Error) -> Self {
|
|
Self::DescriptorReadFailed
|
|
}
|
|
}
|
|
|
|
impl From<device::Error> for GpuResponseEncodeError {
|
|
fn from(_: device::Error) -> Self {
|
|
Self::DescriptorWriteFailed
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for GpuCommand {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.debug_struct(self.command_name()).finish()
|
|
}
|
|
}
|
|
|
|
fn read_mem_entries(
|
|
reader: &mut Reader,
|
|
num_entries: u32,
|
|
) -> Result<Vec<(GuestAddress, usize)>, GpuCommandDecodeError> {
|
|
let mut entries = Vec::with_capacity(num_entries as usize);
|
|
|
|
for _ in 0..num_entries {
|
|
let entry: virtio_gpu_mem_entry =
|
|
reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?;
|
|
entries.push((
|
|
GuestAddress(entry.addr.into()),
|
|
entry.length.to_native() as usize,
|
|
))
|
|
}
|
|
Ok(entries)
|
|
}
|
|
|
|
impl GpuCommand {
|
|
pub const fn command_name(&self) -> &'static str {
|
|
use GpuCommand::*;
|
|
match self {
|
|
GetDisplayInfo => "GetDisplayInfo",
|
|
GetEdid(_info) => "GetEdid",
|
|
ResourceCreate2d(_info) => "ResourceCreate2d",
|
|
ResourceUnref(_info) => "ResourceUnref",
|
|
SetScanout(_info) => "SetScanout",
|
|
SetScanoutBlob(_info) => "SetScanoutBlob",
|
|
ResourceFlush(_info) => "ResourceFlush",
|
|
TransferToHost2d(_info) => "TransferToHost2d",
|
|
ResourceAttachBacking(_info, _vecs) => "ResourceAttachBacking",
|
|
ResourceDetachBacking(_info) => "ResourceDetachBacking",
|
|
GetCapsetInfo(_info) => "GetCapsetInfo",
|
|
GetCapset(_info) => "GetCapset",
|
|
CtxCreate(_info) => "CtxCreate",
|
|
CtxDestroy(_info) => "CtxDestroy",
|
|
CtxAttachResource(_info) => "CtxAttachResource",
|
|
CtxDetachResource(_info) => "CtxDetachResource",
|
|
ResourceCreate3d(_info) => "ResourceCreate3d",
|
|
TransferToHost3d(_info) => "TransferToHost3d",
|
|
TransferFromHost3d(_info) => "TransferFromHost3d",
|
|
CmdSubmit3d { .. } => "CmdSubmit3d",
|
|
ResourceCreateBlob(_info, _) => "ResourceCreateBlob",
|
|
ResourceMapBlob(_info) => "ResourceMapBlob",
|
|
ResourceUnmapBlob(_info) => "ResourceUnmapBlob",
|
|
UpdateCursor(_info) => "UpdateCursor",
|
|
MoveCursor(_info) => "MoveCursor",
|
|
ResourceAssignUuid(_info) => "ResourceAssignUuid",
|
|
}
|
|
}
|
|
|
|
/// Decodes a command from the given chunk of memory.
|
|
pub fn decode(
|
|
reader: &mut Reader,
|
|
) -> Result<(virtio_gpu_ctrl_hdr, Self), GpuCommandDecodeError> {
|
|
use self::GpuCommand::*;
|
|
let hdr = reader
|
|
.read_obj::<virtio_gpu_ctrl_hdr>()
|
|
.map_err(|_| Error::DescriptorReadFailed)?;
|
|
trace!(
|
|
"Decoding GpuCommand 0x{:0x}",
|
|
<Le32 as Into<u32>>::into(hdr.type_)
|
|
);
|
|
let cmd = match hdr.type_.into() {
|
|
VIRTIO_GPU_CMD_GET_DISPLAY_INFO => GetDisplayInfo,
|
|
VIRTIO_GPU_CMD_GET_EDID => {
|
|
GetEdid(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D => {
|
|
ResourceCreate2d(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_UNREF => {
|
|
ResourceUnref(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_SET_SCANOUT => {
|
|
SetScanout(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_SET_SCANOUT_BLOB => {
|
|
SetScanoutBlob(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_FLUSH => {
|
|
ResourceFlush(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D => {
|
|
TransferToHost2d(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING => {
|
|
let info: virtio_gpu_resource_attach_backing =
|
|
reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?;
|
|
let entries = read_mem_entries(reader, info.nr_entries.into())?;
|
|
ResourceAttachBacking(info, entries)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING => {
|
|
ResourceDetachBacking(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_GET_CAPSET_INFO => {
|
|
GetCapsetInfo(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_GET_CAPSET => {
|
|
GetCapset(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_CTX_CREATE => {
|
|
CtxCreate(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_CTX_DESTROY => {
|
|
CtxDestroy(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE => {
|
|
CtxAttachResource(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE => {
|
|
CtxDetachResource(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_CREATE_3D => {
|
|
ResourceCreate3d(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D => {
|
|
TransferToHost3d(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D => {
|
|
TransferFromHost3d(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_SUBMIT_3D => {
|
|
let info: virtio_gpu_cmd_submit =
|
|
reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?;
|
|
|
|
let mut cmd_data = vec![0; <Le32 as Into<u32>>::into(info.size) as usize];
|
|
let mut fence_ids: Vec<u64> =
|
|
Vec::with_capacity(<Le32 as Into<u32>>::into(info.num_in_fences) as usize);
|
|
|
|
for _ in 0..info.num_in_fences.into() {
|
|
let fence_id = reader
|
|
.read_obj::<u64>()
|
|
.map_err(|_| Error::DescriptorReadFailed)?;
|
|
fence_ids.push(fence_id);
|
|
}
|
|
|
|
reader
|
|
.read_exact(&mut cmd_data[..])
|
|
.map_err(|_| Error::DescriptorReadFailed)?;
|
|
|
|
CmdSubmit3d {
|
|
cmd_data,
|
|
fence_ids,
|
|
}
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB => {
|
|
let info: virtio_gpu_resource_create_blob =
|
|
reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?;
|
|
|
|
let entries = read_mem_entries(reader, info.nr_entries.into())?;
|
|
ResourceCreateBlob(info, entries)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB => {
|
|
ResourceMapBlob(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB => {
|
|
ResourceUnmapBlob(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_UPDATE_CURSOR => {
|
|
UpdateCursor(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_MOVE_CURSOR => {
|
|
MoveCursor(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID => {
|
|
ResourceAssignUuid(reader.read_obj().map_err(|_| Error::DescriptorReadFailed)?)
|
|
}
|
|
_ => return Err(GpuCommandDecodeError::InvalidType(hdr.type_.into())),
|
|
};
|
|
|
|
Ok((hdr, cmd))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct GpuResponsePlaneInfo {
|
|
pub stride: u32,
|
|
pub offset: u32,
|
|
}
|
|
|
|
/// A response to a `GpuCommand`. These correspond to `VIRTIO_GPU_RESP_*`.
|
|
#[derive(Debug)]
|
|
pub enum GpuResponse {
|
|
OkNoData,
|
|
OkDisplayInfo(Vec<(u32, u32, bool)>),
|
|
OkEdid {
|
|
/// The EDID display data blob (as specified by VESA)
|
|
blob: Box<[u8]>,
|
|
},
|
|
OkCapsetInfo {
|
|
capset_id: u32,
|
|
version: u32,
|
|
size: u32,
|
|
},
|
|
OkCapset(Vec<u8>),
|
|
OkResourcePlaneInfo {
|
|
format_modifier: u64,
|
|
plane_info: Vec<GpuResponsePlaneInfo>,
|
|
},
|
|
OkResourceUuid {
|
|
uuid: [u8; 16],
|
|
},
|
|
OkMapInfo {
|
|
map_info: u32,
|
|
},
|
|
ErrUnspec,
|
|
ErrRutabaga(RutabagaError),
|
|
ErrScanout {
|
|
num_scanouts: u32,
|
|
},
|
|
ErrOutOfMemory,
|
|
ErrInvalidScanoutId,
|
|
ErrInvalidResourceId,
|
|
ErrInvalidContextId,
|
|
ErrInvalidParameter,
|
|
}
|
|
|
|
impl From<RutabagaError> for GpuResponse {
|
|
fn from(e: RutabagaError) -> Self {
|
|
Self::ErrRutabaga(e)
|
|
}
|
|
}
|
|
|
|
impl Display for GpuResponse {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
use self::GpuResponse::{ErrRutabaga, ErrScanout};
|
|
match self {
|
|
ErrRutabaga(e) => write!(f, "renderer error: {e}"),
|
|
ErrScanout { num_scanouts } => write!(f, "non-zero scanout: {num_scanouts}"),
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error indicating something went wrong decoding a `GpuCommand`.
|
|
#[derive(Error, Debug)]
|
|
pub enum GpuResponseEncodeError {
|
|
/// An I/O error occurred.
|
|
#[error("an I/O error occurred: {0}")]
|
|
IO(io::Error),
|
|
/// Size conversion failed
|
|
#[error("Size conversion failed")]
|
|
SizeOverflow,
|
|
/// More displays than are valid were in a `OkDisplayInfo`.
|
|
#[error("{0} is more displays than are valid")]
|
|
TooManyDisplays(usize),
|
|
/// More planes than are valid were in a `OkResourcePlaneInfo`.
|
|
#[error("{0} is more planes than are valid")]
|
|
TooManyPlanes(usize),
|
|
#[error("Descriptor write failed")]
|
|
DescriptorWriteFailed,
|
|
}
|
|
|
|
impl From<io::Error> for GpuResponseEncodeError {
|
|
fn from(e: io::Error) -> Self {
|
|
Self::IO(e)
|
|
}
|
|
}
|
|
|
|
pub type VirtioGpuResult = std::result::Result<GpuResponse, GpuResponse>;
|
|
|
|
impl GpuResponse {
|
|
/// Encodes a this `GpuResponse` into `resp` and the given set of metadata.
|
|
pub fn encode(
|
|
&self,
|
|
flags: u32,
|
|
fence_id: u64,
|
|
ctx_id: u32,
|
|
ring_idx: u8,
|
|
writer: &mut Writer,
|
|
) -> Result<u32, GpuResponseEncodeError> {
|
|
let hdr = virtio_gpu_ctrl_hdr {
|
|
type_: self.get_type().into(),
|
|
flags: flags.into(),
|
|
fence_id: fence_id.into(),
|
|
ctx_id: ctx_id.into(),
|
|
ring_idx,
|
|
padding: Default::default(),
|
|
};
|
|
let len = match *self {
|
|
Self::OkDisplayInfo(ref info) => {
|
|
if info.len() > VIRTIO_GPU_MAX_SCANOUTS as usize {
|
|
return Err(GpuResponseEncodeError::TooManyDisplays(info.len()));
|
|
}
|
|
let mut disp_info = virtio_gpu_resp_display_info {
|
|
hdr,
|
|
pmodes: Default::default(),
|
|
};
|
|
for (disp_mode, &(width, height, enabled)) in disp_info.pmodes.iter_mut().zip(info)
|
|
{
|
|
disp_mode.r.width = width.into();
|
|
disp_mode.r.height = height.into();
|
|
disp_mode.enabled = u32::from(enabled).into();
|
|
}
|
|
writer
|
|
.write_obj(disp_info)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&disp_info)
|
|
}
|
|
Self::OkEdid { ref blob } => {
|
|
let Ok(size) = u32::try_from(blob.len()) else {
|
|
return Err(GpuResponseEncodeError::SizeOverflow);
|
|
};
|
|
let mut edid_info = virtio_gpu_resp_edid {
|
|
hdr,
|
|
size: size.into(),
|
|
edid: [0; EDID_BLOB_MAX_SIZE],
|
|
padding: Le32::default(),
|
|
};
|
|
edid_info.edid.copy_from_slice(blob);
|
|
writer
|
|
.write_obj(edid_info)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&edid_info)
|
|
}
|
|
Self::OkCapsetInfo {
|
|
capset_id,
|
|
version,
|
|
size,
|
|
} => {
|
|
writer
|
|
.write_obj(virtio_gpu_resp_capset_info {
|
|
hdr,
|
|
capset_id: capset_id.into(),
|
|
capset_max_version: version.into(),
|
|
capset_max_size: size.into(),
|
|
padding: 0u32.into(),
|
|
})
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of::<virtio_gpu_resp_capset_info>()
|
|
}
|
|
Self::OkCapset(ref data) => {
|
|
writer
|
|
.write_obj(hdr)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
writer
|
|
.write(data)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&hdr) + data.len()
|
|
}
|
|
Self::OkResourcePlaneInfo {
|
|
format_modifier,
|
|
ref plane_info,
|
|
} => {
|
|
if plane_info.len() > PLANE_INFO_MAX_COUNT {
|
|
return Err(GpuResponseEncodeError::TooManyPlanes(plane_info.len()));
|
|
}
|
|
let mut strides = [Le32::default(); PLANE_INFO_MAX_COUNT];
|
|
let mut offsets = [Le32::default(); PLANE_INFO_MAX_COUNT];
|
|
for (plane_index, plane) in plane_info.iter().enumerate() {
|
|
strides[plane_index] = plane.stride.into();
|
|
offsets[plane_index] = plane.offset.into();
|
|
}
|
|
let Ok(count) = u32::try_from(plane_info.len()) else {
|
|
return Err(GpuResponseEncodeError::SizeOverflow);
|
|
};
|
|
let plane_info = virtio_gpu_resp_resource_plane_info {
|
|
hdr,
|
|
count: count.into(),
|
|
padding: 0u32.into(),
|
|
format_modifier: format_modifier.into(),
|
|
strides,
|
|
offsets,
|
|
};
|
|
if writer.available_bytes() >= size_of_val(&plane_info) {
|
|
size_of_val(&plane_info)
|
|
} else {
|
|
// In case there is too little room in the response slice to store the
|
|
// entire virtio_gpu_resp_resource_plane_info, convert response to a regular
|
|
// VIRTIO_GPU_RESP_OK_NODATA and attempt to return that.
|
|
writer
|
|
.write_obj(virtio_gpu_ctrl_hdr {
|
|
type_: Le32::from(VIRTIO_GPU_RESP_OK_NODATA),
|
|
..hdr
|
|
})
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&hdr)
|
|
}
|
|
}
|
|
Self::OkResourceUuid { uuid } => {
|
|
let resp_info = virtio_gpu_resp_resource_uuid { hdr, uuid };
|
|
|
|
writer
|
|
.write_obj(resp_info)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&resp_info)
|
|
}
|
|
Self::OkMapInfo { map_info } => {
|
|
let resp_info = virtio_gpu_resp_map_info {
|
|
hdr,
|
|
map_info: map_info.into(),
|
|
padding: Le32::default(),
|
|
};
|
|
|
|
writer
|
|
.write_obj(resp_info)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&resp_info)
|
|
}
|
|
_ => {
|
|
writer
|
|
.write_obj(hdr)
|
|
.map_err(|_| Error::DescriptorWriteFailed)?;
|
|
size_of_val(&hdr)
|
|
}
|
|
};
|
|
let len = u32::try_from(len).map_err(|_| GpuResponseEncodeError::SizeOverflow)?;
|
|
|
|
Ok(len)
|
|
}
|
|
|
|
/// Gets the `VIRTIO_GPU_*` enum value that corresponds to this variant.
|
|
pub const fn get_type(&self) -> u32 {
|
|
match self {
|
|
Self::OkNoData => VIRTIO_GPU_RESP_OK_NODATA,
|
|
Self::OkDisplayInfo(_) => VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
|
|
Self::OkEdid { .. } => VIRTIO_GPU_RESP_OK_EDID,
|
|
Self::OkCapsetInfo { .. } => VIRTIO_GPU_RESP_OK_CAPSET_INFO,
|
|
Self::OkCapset(_) => VIRTIO_GPU_RESP_OK_CAPSET,
|
|
Self::OkResourcePlaneInfo { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_PLANE_INFO,
|
|
Self::OkResourceUuid { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_UUID,
|
|
Self::OkMapInfo { .. } => VIRTIO_GPU_RESP_OK_MAP_INFO,
|
|
Self::ErrUnspec | Self::ErrRutabaga(_) | Self::ErrScanout { .. } => {
|
|
VIRTIO_GPU_RESP_ERR_UNSPEC
|
|
}
|
|
Self::ErrOutOfMemory => VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
|
|
Self::ErrInvalidScanoutId => VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
|
|
Self::ErrInvalidResourceId => VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
|
|
Self::ErrInvalidContextId => VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
|
|
Self::ErrInvalidParameter => VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use virtio_bindings::virtio_ring::VRING_DESC_F_WRITE;
|
|
use virtio_queue::{
|
|
desc::{split::Descriptor as SplitDescriptor, RawDescriptor},
|
|
mock::MockSplitQueue,
|
|
};
|
|
use vm_memory::GuestMemoryMmap;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_virtio_gpu_config() {
|
|
// Test VirtioGpuConfig size
|
|
assert_eq!(std::mem::size_of::<VirtioGpuConfig>(), 16);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_command_type_display() {
|
|
let error = InvalidCommandType(42);
|
|
assert_eq!(format!("{error}"), "Invalid command type 42");
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_response_display() {
|
|
let err_rutabaga = GpuResponse::ErrRutabaga(RutabagaError::InvalidContextId);
|
|
assert_eq!(
|
|
format!("{err_rutabaga}"),
|
|
"renderer error: invalid context id"
|
|
);
|
|
|
|
let err_scanout = GpuResponse::ErrScanout { num_scanouts: 3 };
|
|
assert_eq!(format!("{err_scanout}"), "non-zero scanout: 3");
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_type_error() {
|
|
let error = GpuCommandDecodeError::InvalidType(42);
|
|
assert_eq!(format!("{error}"), "invalid command type (42)");
|
|
}
|
|
|
|
// Test io_error conversion to gpu command decode error
|
|
#[test]
|
|
fn test_io_error() {
|
|
let io_error = io::Error::other("Test IO error");
|
|
let gpu_error: GpuCommandDecodeError = io_error.into();
|
|
match gpu_error {
|
|
GpuCommandDecodeError::IO(_) => (),
|
|
_ => panic!("Expected IO error"),
|
|
}
|
|
}
|
|
|
|
//Test vhu_error conversion to gpu command decode/encode error
|
|
#[test]
|
|
fn test_device_error() {
|
|
let device_error = device::Error::DescriptorReadFailed;
|
|
let gpu_error: GpuCommandDecodeError = device_error.into();
|
|
match gpu_error {
|
|
GpuCommandDecodeError::DescriptorReadFailed => (),
|
|
_ => panic!("Expected DescriptorReadFailed error"),
|
|
}
|
|
let device_error = device::Error::DescriptorWriteFailed;
|
|
let gpu_error: GpuResponseEncodeError = device_error.into();
|
|
match gpu_error {
|
|
GpuResponseEncodeError::DescriptorWriteFailed => (),
|
|
_ => panic!("Expected DescriptorWriteFailed error"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_command_debug() {
|
|
use GpuCommand::*;
|
|
|
|
let test_cases = vec![
|
|
(GetDisplayInfo, "GetDisplayInfo"),
|
|
(GetEdid(virtio_gpu_get_edid::default()), "GetEdid"),
|
|
(
|
|
ResourceCreate2d(virtio_gpu_resource_create_2d::default()),
|
|
"ResourceCreate2d",
|
|
),
|
|
(
|
|
ResourceUnref(virtio_gpu_resource_unref::default()),
|
|
"ResourceUnref",
|
|
),
|
|
(SetScanout(virtio_gpu_set_scanout::default()), "SetScanout"),
|
|
(
|
|
SetScanoutBlob(virtio_gpu_set_scanout_blob::default()),
|
|
"SetScanoutBlob",
|
|
),
|
|
(
|
|
ResourceFlush(virtio_gpu_resource_flush::default()),
|
|
"ResourceFlush",
|
|
),
|
|
(
|
|
TransferToHost2d(virtio_gpu_transfer_to_host_2d::default()),
|
|
"TransferToHost2d",
|
|
),
|
|
(
|
|
ResourceDetachBacking(virtio_gpu_resource_detach_backing::default()),
|
|
"ResourceDetachBacking",
|
|
),
|
|
(
|
|
GetCapsetInfo(virtio_gpu_get_capset_info::default()),
|
|
"GetCapsetInfo",
|
|
),
|
|
(GetCapset(virtio_gpu_get_capset::default()), "GetCapset"),
|
|
(CtxCreate(virtio_gpu_ctx_create::default()), "CtxCreate"),
|
|
(CtxDestroy(virtio_gpu_ctx_destroy::default()), "CtxDestroy"),
|
|
(
|
|
CtxAttachResource(virtio_gpu_ctx_resource::default()),
|
|
"CtxAttachResource",
|
|
),
|
|
(
|
|
CtxDetachResource(virtio_gpu_ctx_resource::default()),
|
|
"CtxDetachResource",
|
|
),
|
|
(
|
|
ResourceCreate3d(virtio_gpu_resource_create_3d::default()),
|
|
"ResourceCreate3d",
|
|
),
|
|
(
|
|
TransferToHost3d(virtio_gpu_transfer_host_3d::default()),
|
|
"TransferToHost3d",
|
|
),
|
|
(
|
|
TransferFromHost3d(virtio_gpu_transfer_host_3d::default()),
|
|
"TransferFromHost3d",
|
|
),
|
|
(
|
|
CmdSubmit3d {
|
|
cmd_data: Vec::new(),
|
|
fence_ids: Vec::new(),
|
|
},
|
|
"CmdSubmit3d",
|
|
),
|
|
(
|
|
ResourceCreateBlob(virtio_gpu_resource_create_blob::default(), Vec::new()),
|
|
"ResourceCreateBlob",
|
|
),
|
|
(
|
|
ResourceMapBlob(virtio_gpu_resource_map_blob::default()),
|
|
"ResourceMapBlob",
|
|
),
|
|
(
|
|
ResourceUnmapBlob(virtio_gpu_resource_unmap_blob::default()),
|
|
"ResourceUnmapBlob",
|
|
),
|
|
(
|
|
UpdateCursor(virtio_gpu_update_cursor::default()),
|
|
"UpdateCursor",
|
|
),
|
|
(
|
|
MoveCursor(virtio_gpu_update_cursor::default()),
|
|
"MoveCursor",
|
|
),
|
|
(
|
|
ResourceAssignUuid(virtio_gpu_resource_assign_uuid::default()),
|
|
"ResourceAssignUuid",
|
|
),
|
|
];
|
|
|
|
for (command, expected) in test_cases {
|
|
assert_eq!(format!("{command:?}"), expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_virtio_gpu_ctx_create_debug() {
|
|
// Test without null terminator (typical case)
|
|
let bytes = b"test_debug";
|
|
let ctx = virtio_gpu_ctx_create {
|
|
debug_name: {
|
|
let mut debug_name = [0; 64];
|
|
debug_name[..bytes.len()].copy_from_slice(bytes);
|
|
debug_name
|
|
},
|
|
context_init: 0.into(),
|
|
nlen: (bytes.len() as u32).into(),
|
|
};
|
|
assert_eq!(
|
|
format!("{ctx:?}"),
|
|
"virtio_gpu_ctx_create { debug_name: \"test_debug\", context_init: Le32(0), .. }"
|
|
);
|
|
|
|
// Test with null terminator included in nlen (edge case - should preserve it)
|
|
let bytes_with_null = b"test_debug\0";
|
|
let ctx_with_null = virtio_gpu_ctx_create {
|
|
debug_name: {
|
|
let mut debug_name = [0; 64];
|
|
debug_name[..bytes_with_null.len()].copy_from_slice(bytes_with_null);
|
|
debug_name
|
|
},
|
|
context_init: 0.into(),
|
|
nlen: (bytes_with_null.len() as u32).into(),
|
|
};
|
|
assert_eq!(
|
|
format!("{ctx_with_null:?}"),
|
|
"virtio_gpu_ctx_create { debug_name: \"test_debug\\0\", context_init: Le32(0), .. }"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_response_encode() {
|
|
let mem = GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 16384)]).unwrap();
|
|
|
|
let vq = MockSplitQueue::new(&mem, 8);
|
|
let desc_chain = vq
|
|
.build_desc_chain(&[RawDescriptor::from(SplitDescriptor::new(
|
|
0x1000,
|
|
8192,
|
|
VRING_DESC_F_WRITE as u16,
|
|
0,
|
|
))])
|
|
.unwrap();
|
|
|
|
let mut writer = desc_chain
|
|
.clone()
|
|
.writer(&mem)
|
|
.map_err(Error::CreateWriter)
|
|
.unwrap();
|
|
|
|
let resp = GpuResponse::OkNoData;
|
|
let resp_ok_nodata = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_ok_nodata, 24);
|
|
|
|
let resp = GpuResponse::OkDisplayInfo(vec![(0, 0, false)]);
|
|
let resp_display_info = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_display_info, 408);
|
|
|
|
let edid_data: Box<[u8]> = Box::new([0u8; 1024]);
|
|
let resp = GpuResponse::OkEdid { blob: edid_data };
|
|
let resp_edid = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_edid, 1056);
|
|
|
|
let resp = GpuResponse::OkCapset(vec![]);
|
|
let resp_capset = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_capset, 24);
|
|
|
|
let resp = GpuResponse::OkCapsetInfo {
|
|
capset_id: 0,
|
|
version: 0,
|
|
size: 0,
|
|
};
|
|
let resp_capset = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_capset, 40);
|
|
|
|
let resp = GpuResponse::OkResourcePlaneInfo {
|
|
format_modifier: 0,
|
|
plane_info: vec![],
|
|
};
|
|
let resp_resource_planeinfo = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_resource_planeinfo, 72);
|
|
|
|
let resp = GpuResponse::OkResourceUuid { uuid: [0u8; 16] };
|
|
let resp_resource_uuid = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_resource_uuid, 40);
|
|
|
|
let resp = GpuResponse::OkMapInfo { map_info: 0 };
|
|
let resp_map_info = GpuResponse::encode(&resp, 0, 0, 0, 0, &mut writer).unwrap();
|
|
assert_eq!(resp_map_info, 32);
|
|
}
|
|
}
|