use std::mem::size_of;
use std::os::unix::io::{AsRawFd, RawFd};
use std::rc::Rc;
use std::slice::{from_raw_parts, from_raw_parts_mut};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::{ptr, vec};
use anyhow::{anyhow, bail, Context, Result};
use clap::{ArgAction, Parser};
use log::{error, info, warn};
use vmm_sys_util::{epoll::EventSet, eventfd::EventFd};
use crate::{
check_config_space_rw, gpa_hva_iovec_map, iov_discard_front, iov_read_object,
read_config_default, ElemIovec, Element, Queue, VirtioBase, VirtioDevice, VirtioDeviceQuirk,
VirtioError, VirtioInterrupt, VirtioInterruptType, VIRTIO_F_RING_EVENT_IDX,
VIRTIO_F_RING_INDIRECT_DESC, VIRTIO_F_VERSION_1, VIRTIO_GPU_CMD_GET_DISPLAY_INFO,
VIRTIO_GPU_CMD_GET_EDID, VIRTIO_GPU_CMD_MOVE_CURSOR, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D, VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
VIRTIO_GPU_CMD_RESOURCE_FLUSH, VIRTIO_GPU_CMD_RESOURCE_UNREF, VIRTIO_GPU_CMD_SET_SCANOUT,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D, VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_FLAG_FENCE,
VIRTIO_GPU_F_EDID, VIRTIO_GPU_F_MONOCHROME, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, VIRTIO_GPU_RESP_ERR_UNSPEC, VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
VIRTIO_GPU_RESP_OK_EDID, VIRTIO_GPU_RESP_OK_NODATA, VIRTIO_TYPE_GPU,
};
use address_space::{AddressSpace, FileBackend, GuestAddress};
use machine_manager::config::{get_pci_df, valid_id, DEFAULT_VIRTQUEUE_SIZE};
use machine_manager::event_loop::{register_event_helper, unregister_event_helper};
use migration::{
DeviceStateDesc, MigrationHook, MigrationManager, MigrationStatus, StateTransfer,
MAX_LARGE_DEVICE_STATE_SIZE,
};
use migration_derive::{ByteCode, DescSerde};
use serde::{Deserialize, Serialize};
use ui::console::{
console_close, console_init, display_cursor_define, display_graphic_update,
display_replace_surface, display_set_major_screen, get_run_stage, set_run_stage, ConsoleType,
DisplayConsole, DisplayMouse, DisplaySurface, HardWareOperations, VmRunningStage,
DEFAULT_CURSOR_BPP, DEFAULT_CURSOR_HEIGHT, DEFAULT_CURSOR_WIDTH,
};
use ui::pixman::{
create_pixman_image, get_image_data, get_image_format, get_image_height, get_image_stride,
get_image_width, ref_pixman_image, unref_pixman_image,
};
use util::aio::{
iov_from_buf_direct, iov_to_buf_direct, wait_io_done, IoRef, Iovec, DEFAULT_IO_TIMEOUT,
};
use util::byte_code::ByteCode;
use util::edid::EdidInfo;
use util::gen_base_func;
use util::loop_context::{
read_fd, EventNotifier, EventNotifierHelper, NotifierCallback, NotifierOperation,
};
use util::pixman::{
pixman_format_bpp, pixman_format_code_t, pixman_image_set_destroy_function, pixman_image_t,
pixman_region16_t, pixman_region_extents, pixman_region_fini, pixman_region_init,
pixman_region_init_rect, pixman_region_intersect, pixman_region_translate,
virtio_gpu_unref_resource_callback,
};
const QUEUE_NUM_GPU: usize = 2;
const VIRTIO_GPU_EVENT_DISPLAY: u32 = 1 << 0;
const VIRTIO_GPU_RES_WIN_FRAMEBUF: u32 = 0x80000000;
const VIRTIO_GPU_RES_EFI_FRAMEBUF: u32 = 0x40000000;
const VIRTIO_GPU_RES_FRAMEBUF: u32 = VIRTIO_GPU_RES_WIN_FRAMEBUF | VIRTIO_GPU_RES_EFI_FRAMEBUF;
pub const VIRTIO_GPU_MAX_OUTPUTS: usize = 16;
const VIRTIO_GPU_DEFAULT_MAX_HOSTMEM: u64 = 0x10000000;
#[derive(Parser, Clone, Debug, Default)]
#[command(no_binary_name(true))]
pub struct GpuDevConfig {
#[arg(long, value_parser = ["virtio-gpu-pci"])]
pub classtype: String,
#[arg(long, value_parser = valid_id)]
pub id: String,
#[arg(long)]
pub bus: String,
#[arg(long, value_parser = get_pci_df)]
pub addr: (u8, u8),
#[arg(long, alias = "max_outputs", default_value="1", value_parser = clap::value_parser!(u32).range(1..=VIRTIO_GPU_MAX_OUTPUTS as i64))]
pub max_outputs: u32,
#[arg(long, default_value="true", action = ArgAction::Append)]
pub edid: bool,
#[arg(long, default_value = "1024")]
pub xres: u32,
#[arg(long, default_value = "768")]
pub yres: u32,
#[arg(long, alias = "max_hostmem", default_value="268435456", value_parser = clap::value_parser!(u64).range(1..))]
pub max_hostmem: u64,
#[arg(long, alias = "enable_bar0", default_value="false", action = ArgAction::Append)]
pub enable_bar0: bool,
#[arg(long, alias = "cursor_size", default_value = "128")]
pub cursor_size: u32,
}
impl GpuDevConfig {
pub fn check(&self) {
if self.max_hostmem < VIRTIO_GPU_DEFAULT_MAX_HOSTMEM {
warn!(
"max_hostmem should >= {}, allocating less than it may cause \
the GPU to fail to start or refresh.",
VIRTIO_GPU_DEFAULT_MAX_HOSTMEM
);
}
}
}
#[derive(Debug)]
struct GpuResource {
resource_id: u32,
width: u32,
height: u32,
format: u32,
iov: Vec<Iovec>,
backing_iov: Vec<ElemIovec>,
scanouts_bitmask: u32,
host_mem: u64,
pixman_image: *mut pixman_image_t,
monochrome_cursor: Vec<u8>,
}
impl Default for GpuResource {
fn default() -> Self {
GpuResource {
resource_id: 0,
width: 0,
height: 0,
format: 0,
iov: Vec::new(),
backing_iov: Vec::new(),
scanouts_bitmask: 0,
host_mem: 0,
pixman_image: ptr::null_mut(),
monochrome_cursor: Vec::new(),
}
}
}
unsafe impl Send for GpuResource {}
#[allow(unused)]
#[derive(Default, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct VirtioGpuOutputState {
pub con_id: usize,
pub width: u32,
pub height: u32,
x_coord: i32,
y_coord: i32,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
struct GpuBlobRange {
offset: u64,
len: u64,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct GpuBackingState {
gpa: u64,
len: u32,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct GpuResourceState {
resource_id: u32,
width: u32,
height: u32,
format: u32,
scanouts_bitmask: u32,
host_mem: u64,
backing_iov: Vec<GpuBackingState>,
image: Option<GpuBlobRange>,
monochrome_cursor: Option<GpuBlobRange>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct GpuCursorState {
width: u32,
height: u32,
hot_x: u32,
hot_y: u32,
data: GpuBlobRange,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct GpuScanoutState {
width: u32,
height: u32,
x: u32,
y: u32,
resource_id: u32,
cursor_visible: bool,
cursor: Option<GpuCursorState>,
}
#[derive(Clone, Debug, Default, DescSerde, Serialize, Deserialize)]
#[desc_version(current_version = "1.0.0")]
pub struct GpuState {
device_features: u64,
driver_features: u64,
broken: bool,
config_space: VirtioGpuConfig,
output_states: Vec<VirtioGpuOutputState>,
resources: Vec<GpuResourceState>,
scanouts: Vec<GpuScanoutState>,
enable_output_bitmask: u32,
used_hostmem: u64,
}
#[repr(C)]
#[derive(Clone, Copy, ByteCode)]
struct GpuVmstateHeader {
magic: u32,
version: u32,
json_len: u64,
blob_len: u64,
}
const GPU_VMSTATE_MAGIC: u32 = u32::from_le_bytes(*b"GVMS");
const GPU_VMSTATE_VERSION: u32 = 1;
trait CtrlHdr {
fn mut_ctrl_hdr(&mut self) -> &mut VirtioGpuCtrlHdr;
}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
struct VirtioGpuCtrlHdr {
hdr_type: u32,
flags: u32,
fence_id: u64,
ctx_id: u32,
padding: u32,
}
impl ByteCode for VirtioGpuCtrlHdr {}
impl CtrlHdr for VirtioGpuCtrlHdr {
fn mut_ctrl_hdr(&mut self) -> &mut VirtioGpuCtrlHdr {
self
}
}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
struct VirtioGpuRect {
x_coord: u32,
y_coord: u32,
width: u32,
height: u32,
}
impl ByteCode for VirtioGpuRect {}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
struct VirtioGpuDisplayOne {
rect: VirtioGpuRect,
enabled: u32,
flags: u32,
}
impl ByteCode for VirtioGpuDisplayOne {}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug)]
struct VirtioGpuDisplayInfo {
header: VirtioGpuCtrlHdr,
pmodes: [VirtioGpuDisplayOne; VIRTIO_GPU_MAX_OUTPUTS],
}
impl ByteCode for VirtioGpuDisplayInfo {}
impl CtrlHdr for VirtioGpuDisplayInfo {
fn mut_ctrl_hdr(&mut self) -> &mut VirtioGpuCtrlHdr {
&mut self.header
}
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuGetEdid {
scanouts: u32,
padding: u32,
}
impl ByteCode for VirtioGpuGetEdid {}
#[repr(C)]
#[derive(Clone, Copy)]
struct VirtioGpuRespEdid {
header: VirtioGpuCtrlHdr,
size: u32,
padding: u32,
edid: [u8; 1024],
}
impl ByteCode for VirtioGpuRespEdid {}
impl CtrlHdr for VirtioGpuRespEdid {
fn mut_ctrl_hdr(&mut self) -> &mut VirtioGpuCtrlHdr {
&mut self.header
}
}
impl Default for VirtioGpuRespEdid {
fn default() -> Self {
VirtioGpuRespEdid {
header: VirtioGpuCtrlHdr::default(),
size: 0,
padding: 0,
edid: [0; 1024],
}
}
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuResourceCreate2d {
resource_id: u32,
format: u32,
width: u32,
height: u32,
}
impl ByteCode for VirtioGpuResourceCreate2d {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuResourceUnref {
resource_id: u32,
padding: u32,
}
impl ByteCode for VirtioGpuResourceUnref {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuSetScanout {
rect: VirtioGpuRect,
scanout_id: u32,
resource_id: u32,
}
impl ByteCode for VirtioGpuSetScanout {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuResourceFlush {
rect: VirtioGpuRect,
resource_id: u32,
padding: u32,
}
impl ByteCode for VirtioGpuResourceFlush {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuTransferToHost2d {
rect: VirtioGpuRect,
offset: u64,
resource_id: u32,
padding: u32,
}
impl ByteCode for VirtioGpuTransferToHost2d {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuResourceAttachBacking {
resource_id: u32,
nr_entries: u32,
}
impl ByteCode for VirtioGpuResourceAttachBacking {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuMemEntry {
addr: u64,
length: u32,
padding: u32,
}
impl ByteCode for VirtioGpuMemEntry {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuResourceDetachBacking {
resource_id: u32,
padding: u32,
}
impl ByteCode for VirtioGpuResourceDetachBacking {}
pub struct GpuOpts {
pub output_states: Arc<Mutex<[VirtioGpuOutputState; VIRTIO_GPU_MAX_OUTPUTS]>>,
pub config_space: Arc<Mutex<VirtioGpuConfig>>,
pub interrupt_cb: Option<Arc<VirtioInterrupt>>,
pub enable_bar0: bool,
}
impl HardWareOperations for GpuOpts {
fn hw_update(&self, con: Arc<Mutex<DisplayConsole>>) {
if !self.enable_bar0 || get_run_stage() != VmRunningStage::Bios {
return;
}
let locked_con = con.lock().unwrap();
if locked_con.surface.is_none() {
return;
}
let width = locked_con.width;
let height = locked_con.height;
trace::virtio_gpu_console_hw_update(locked_con.con_id, width, height);
drop(locked_con);
display_graphic_update(&Some(Arc::downgrade(&con)), 0, 0, width, height)
.unwrap_or_else(|e| error!("Error occurs during graphic updating: {:?}", e));
}
fn hw_ui_info(&self, con: Arc<Mutex<DisplayConsole>>, width: u32, height: u32) {
let con_id = con.lock().unwrap().con_id;
for output_state in self.output_states.lock().unwrap().iter_mut() {
if output_state.con_id == con_id {
output_state.width = width;
output_state.height = height;
break;
}
}
let mut config_space = self.config_space.lock().unwrap();
config_space.events_read |= VIRTIO_GPU_EVENT_DISPLAY;
if self.interrupt_cb.is_none() {
return;
}
info!(
"virtio-gpu receive resize request, con {} will be resize to {} {}.",
con_id, width, height
);
let interrupt_cb = self.interrupt_cb.as_ref().unwrap();
if let Err(e) = (interrupt_cb)(&VirtioInterruptType::Config, None, false) {
error!(
"{:?}. {:?}",
VirtioError::InterruptTrigger("gpu", VirtioInterruptType::Config),
e
);
}
}
}
#[derive(Default, Clone)]
struct VirtioGpuRequest {
header: VirtioGpuCtrlHdr,
index: u16,
out_iovec: Vec<Iovec>,
out_len: u32,
in_iovec: Vec<Iovec>,
_in_len: u32,
}
impl VirtioGpuRequest {
fn new(mem_space: &Arc<AddressSpace>, elem: &mut Element) -> Result<Self> {
if elem.out_iovec.is_empty() {
bail!(
"Missed header for gpu request: out {} in {} desc num {}.",
elem.out_iovec.len(),
elem.in_iovec.len(),
elem.desc_num
);
}
let header = iov_read_object::<VirtioGpuCtrlHdr>(mem_space, &elem.out_iovec, &None)?;
let data_iovec =
iov_discard_front(&mut elem.out_iovec, size_of::<VirtioGpuCtrlHdr>() as u64)
.unwrap_or_default();
let (out_len, out_iovec) = gpa_hva_iovec_map(data_iovec, mem_space, &None)?;
let (in_len, in_iovec) = gpa_hva_iovec_map(&elem.in_iovec, mem_space, &None)?;
Ok(VirtioGpuRequest {
header,
index: elem.index,
out_iovec,
out_len: out_len as u32,
in_iovec,
_in_len: in_len as u32,
})
}
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuCursorPos {
scanout_id: u32,
x_coord: u32,
y_coord: u32,
padding: u32,
}
impl ByteCode for VirtioGpuCursorPos {}
#[repr(C)]
#[derive(Default, Clone, Copy)]
struct VirtioGpuUpdateCursor {
pos: VirtioGpuCursorPos,
resource_id: u32,
hot_x: u32,
hot_y: u32,
padding: u32,
}
impl ByteCode for VirtioGpuUpdateCursor {}
#[derive(Default)]
struct GpuScanout {
con: Option<Weak<Mutex<DisplayConsole>>>,
surface: Option<DisplaySurface>,
mouse: Option<DisplayMouse>,
width: u32,
height: u32,
x: u32,
y: u32,
resource_id: u32,
cursor_visible: bool,
}
impl GpuScanout {
fn clear(&mut self) {
self.resource_id = 0;
self.surface = None;
self.width = 0;
self.height = 0;
self.cursor_visible = false;
}
}
#[derive(Default)]
struct GpuRuntimeState {
resources_list: Vec<GpuResource>,
enable_output_bitmask: u32,
scanouts: Vec<GpuScanout>,
used_hostmem: u64,
}
impl GpuRuntimeState {
fn new(consoles: &[Option<Weak<Mutex<DisplayConsole>>>]) -> Self {
let scanouts = consoles
.iter()
.map(|con| GpuScanout {
con: con.clone(),
..Default::default()
})
.collect();
Self {
resources_list: Vec::new(),
enable_output_bitmask: 1,
scanouts,
used_hostmem: 0,
}
}
fn get_resource_idx(&self, resource_id: u32) -> Option<usize> {
self.resources_list
.iter()
.position(|x| x.resource_id == resource_id)
}
fn get_backed_resource_idx(&self, res_id: u32, caller: &str) -> (Option<usize>, u32) {
match self.get_resource_idx(res_id) {
None => {
error!(
"GuestError: The resource_id {} in {} request does not existed",
res_id, caller,
);
(None, VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID)
}
Some(res_idx) => {
let res = &self.resources_list[res_idx];
if res.iov.is_empty()
|| (res.pixman_image.is_null() && res.monochrome_cursor.is_empty())
{
error!(
"GuestError: The resource_id {} in {} request has no backing storage.",
res_id, caller,
);
(None, VIRTIO_GPU_RESP_ERR_UNSPEC)
} else {
(Some(res_idx), 0)
}
}
}
}
fn disable_scanout(&mut self, scanout_id: usize) {
let resource_id = self.scanouts[scanout_id].resource_id;
if resource_id == 0 {
return;
}
if let Some(res_idx) = self.get_resource_idx(resource_id) {
let res = &mut self.resources_list[res_idx];
res.scanouts_bitmask &= !(1 << scanout_id);
}
let scanout = &mut self.scanouts[scanout_id];
display_replace_surface(&scanout.con, None)
.unwrap_or_else(|e| error!("Error occurs during surface switching: {:?}", e));
let mouse = DisplayMouse {
height: DEFAULT_CURSOR_WIDTH as u32,
width: DEFAULT_CURSOR_HEIGHT as u32,
hot_x: 0,
hot_y: 0,
data: vec![0_u8; DEFAULT_CURSOR_WIDTH * DEFAULT_CURSOR_HEIGHT * DEFAULT_CURSOR_BPP],
};
display_cursor_define(&scanout.con, &mouse)
.unwrap_or_else(|e| error!("Error occurs during display_cursor_define: {:?}", e));
scanout.clear();
}
fn resource_destroy(&mut self, res_index: usize) {
let scanouts_bitmask = self.resources_list[res_index].scanouts_bitmask;
if scanouts_bitmask != 0 {
for i in 0..self.scanouts.len() {
if (scanouts_bitmask & (1 << i)) != 0 {
self.disable_scanout(i);
}
}
}
let res = &mut self.resources_list[res_index];
unref_pixman_image(res.pixman_image);
self.used_hostmem -= res.host_mem;
self.resources_list.remove(res_index);
}
}
impl Drop for GpuRuntimeState {
fn drop(&mut self) {
while !self.resources_list.is_empty() {
self.resource_destroy(0);
}
}
}
unsafe impl Send for GpuRuntimeState {}
struct GpuIoHandler {
ctrl_queue: Arc<Mutex<Queue>>,
cursor_queue: Arc<Mutex<Queue>>,
mem_space: Arc<AddressSpace>,
ctrl_queue_evt: Arc<EventFd>,
cursor_queue_evt: Arc<EventFd>,
interrupt_cb: Arc<VirtioInterrupt>,
driver_features: u64,
runtime: Arc<Mutex<GpuRuntimeState>>,
migrating: Arc<AtomicBool>,
io_inflight: IoRef,
num_scanouts: u32,
output_states: Arc<Mutex<[VirtioGpuOutputState; VIRTIO_GPU_MAX_OUTPUTS]>>,
max_hostmem: u64,
cursor_size: u32,
}
unsafe impl Sync for GpuIoHandler {}
unsafe impl Send for GpuIoHandler {}
fn create_surface(
scanout: &mut GpuScanout,
info_set_scanout: VirtioGpuSetScanout,
res: &GpuResource,
pixman_format: pixman_format_code_t,
pixman_stride: libc::c_int,
res_data_offset: *mut u32,
) -> DisplaySurface {
let mut surface = DisplaySurface::default();
let rect = create_pixman_image(
pixman_format,
info_set_scanout.rect.width as i32,
info_set_scanout.rect.height as i32,
res_data_offset,
pixman_stride,
);
ref_pixman_image(res.pixman_image);
unsafe {
pixman_image_set_destroy_function(
rect,
Some(virtio_gpu_unref_resource_callback),
res.pixman_image.cast(),
);
}
surface.format = pixman_format;
surface.image = ref_pixman_image(rect);
if !surface.image.is_null() {
scanout.surface = Some(surface);
unref_pixman_image(rect);
display_replace_surface(&scanout.con, scanout.surface)
.unwrap_or_else(|e| error!("Error occurs during surface switching: {:?}", e));
}
surface
}
const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;
const VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM: u32 = 2;
const VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM: u32 = 3;
const VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM: u32 = 4;
const VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM: u32 = 67;
const VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM: u32 = 68;
const VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM: u32 = 121;
const VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM: u32 = 134;
const VIRTIO_GPU_FORMAT_MONOCHROME: u32 = 500;
pub const VIRTIO_GPU_FORMAT_INVALID_UNORM: u32 = 135;
pub fn get_pixman_format(format: u32) -> Result<pixman_format_code_t> {
match format {
VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM => Ok(pixman_format_code_t::PIXMAN_a8r8g8b8),
VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM => Ok(pixman_format_code_t::PIXMAN_x8r8g8b8),
VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM => Ok(pixman_format_code_t::PIXMAN_b8g8r8a8),
VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM => Ok(pixman_format_code_t::PIXMAN_b8g8r8x8),
VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM => Ok(pixman_format_code_t::PIXMAN_a8b8g8r8),
VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM => Ok(pixman_format_code_t::PIXMAN_r8g8b8x8),
VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM => Ok(pixman_format_code_t::PIXMAN_r8g8b8a8),
VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM => Ok(pixman_format_code_t::PIXMAN_x8b8g8r8),
_ => {
bail!("Unsupported pixman format")
}
}
}
pub fn set_monochrome_cursor(mse: &mut DisplayMouse, source: &[u8], width: usize, height: usize) {
let pixels_num = width * height;
let mask_value_size = pixels_num / 8;
let and_mask_value = &source[0..mask_value_size];
let xor_mask_value = &source[mask_value_size..mask_value_size * 2];
let bpl = (mse.width as usize) / 8;
let bpp = 4;
let cursor = &mut mse.data;
for row in 0..mse.height as usize {
for col in 0..bpl {
for i in 0..8 {
let cursor_index = (row * mse.width as usize + col * 8 + i) * bpp;
if row >= height || col * bpl >= width {
cursor[cursor_index] = 0x00;
cursor[cursor_index + 1] = 0x00;
cursor[cursor_index + 2] = 0x00;
cursor[cursor_index + 3] = 0x00;
continue;
}
let mask_index: u8 = 0x80 >> i;
let and_v = (and_mask_value[row * (width / 8) + col] & mask_index) != 0;
let xor_v = (xor_mask_value[row * (width / 8) + col] & mask_index) != 0;
if !and_v && !xor_v {
cursor[cursor_index] = 0x00;
cursor[cursor_index + 1] = 0x00;
cursor[cursor_index + 2] = 0x00;
cursor[cursor_index + 3] = 0xff;
} else if !and_v && xor_v {
cursor[cursor_index] = 0xff;
cursor[cursor_index + 1] = 0xff;
cursor[cursor_index + 2] = 0xff;
cursor[cursor_index + 3] = 0xff;
} else if and_v && !xor_v {
cursor[cursor_index] = 0x00;
cursor[cursor_index + 1] = 0x00;
cursor[cursor_index + 2] = 0x00;
cursor[cursor_index + 3] = 0x00;
} else {
cursor[cursor_index] = 0x00;
cursor[cursor_index + 1] = 0x00;
cursor[cursor_index + 2] = 0x00;
cursor[cursor_index + 3] = 0xff;
}
}
}
}
}
pub fn cal_image_hostmem(
format: u32,
width: u32,
height: u32,
cursor_size: u32,
) -> (Option<usize>, u32) {
if format == VIRTIO_GPU_FORMAT_MONOCHROME {
if width > cursor_size
|| height > cursor_size
|| !width.is_multiple_of(8)
|| !height.is_multiple_of(8)
{
error!(
"GuestError: monochrome cursor use invalid size: {} {}.",
width, height
);
(None, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER)
} else {
let mem = (width * height / 8 * 2) as usize;
(Some(mem), 0)
}
} else {
let pixman_format = match get_pixman_format(format) {
Ok(f) => f,
Err(e) => {
error!("GuestError: {:?}", e);
return (None, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER);
}
};
let bpp = pixman_format_bpp(pixman_format as u32);
let stride = ((u64::from(width) * u64::from(bpp) + 0x1f) >> 5) * (size_of::<u32>() as u64);
match stride.checked_mul(u64::from(height)) {
None => {
error!(
"stride * height is overflow: width {} height {} stride {} bpp {}",
width, height, stride, bpp,
);
(None, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER)
}
Some(v) => (Some(v as usize), 0),
}
}
}
fn is_rect_in_resource(rect: &VirtioGpuRect, res: &GpuResource) -> bool {
let x_in = rect
.x_coord
.checked_add(rect.width)
.filter(|&sum| sum <= res.width)
.is_some();
let y_in = rect
.y_coord
.checked_add(rect.height)
.filter(|&sum| sum <= res.height)
.is_some();
x_in && y_in
}
impl GpuIoHandler {
fn change_run_stage(&self, rt: &GpuRuntimeState) -> Result<()> {
if get_run_stage() == VmRunningStage::Bios && !rt.scanouts.is_empty() {
if let Some(con) = &rt.scanouts[0].con.as_ref().and_then(|c| c.upgrade()) {
let dev_name = con.lock().unwrap().dev_name.clone();
display_set_major_screen(&dev_name)?;
set_run_stage(VmRunningStage::Os);
};
}
Ok(())
}
fn get_request<T: ByteCode>(&mut self, header: &VirtioGpuRequest, req: &mut T) -> Result<()> {
unsafe { iov_to_buf_direct(&header.out_iovec, 0, req.as_mut_bytes()) }.and_then(|size| {
if size == size_of::<T>() {
Ok(())
} else {
Err(anyhow!("Invalid header for gpu request: len {}.", size))
}
})
}
fn complete_one_request(&mut self, index: u16, len: u32) -> Result<()> {
let mut queue_lock = self.ctrl_queue.lock().unwrap();
queue_lock.vring.add_used(index, len).with_context(|| {
format!(
"Failed to add used ring(gpu ctrl), index {}, len {}",
index, len,
)
})?;
if queue_lock.vring.should_notify(self.driver_features) {
(self.interrupt_cb)(&VirtioInterruptType::Vring, Some(&queue_lock), false)
.with_context(|| "Failed to trigger interrupt(gpu ctrl)")?;
trace::virtqueue_send_interrupt("Gpu", &*queue_lock as *const _ as u64);
}
Ok(())
}
fn send_response<T: ByteCode + CtrlHdr>(
&mut self,
req: &VirtioGpuRequest,
resp: &mut T,
) -> Result<()> {
if (req.header.flags & VIRTIO_GPU_FLAG_FENCE) != 0 {
let header = resp.mut_ctrl_hdr();
header.flags |= VIRTIO_GPU_FLAG_FENCE;
header.fence_id = req.header.fence_id;
header.ctx_id = req.header.ctx_id;
}
let len = unsafe { iov_from_buf_direct(&req.in_iovec, resp.as_bytes())? };
if len != size_of::<T>() {
error!(
"GuestError: An incomplete response will be used instead of the expected: expected \
length is {}, actual length is {}. \
Also, be aware that the virtual machine may suspended if response is too short to \
carry the necessary information.",
size_of::<T>(), len,
);
}
self.complete_one_request(req.index, len as u32)
}
fn response_nodata(&mut self, resp_head_type: u32, req: &VirtioGpuRequest) -> Result<()> {
let mut resp = VirtioGpuCtrlHdr {
hdr_type: resp_head_type,
..Default::default()
};
self.send_response(req, &mut resp)
}
fn update_cursor_image(
&mut self,
rt: &mut GpuRuntimeState,
info_cursor: &VirtioGpuUpdateCursor,
) {
let (res_idx, error) =
rt.get_backed_resource_idx(info_cursor.resource_id, "cmd_update_cursor");
if res_idx.is_none() {
error!("Failed to update cursor image, errcode: {}", error);
return;
}
let res = &rt.resources_list[res_idx.unwrap()];
let scanout = &mut rt.scanouts[info_cursor.pos.scanout_id as usize];
let mse = scanout.mouse.as_mut().unwrap();
let mse_data_size = mse.data.len();
if res.format == VIRTIO_GPU_FORMAT_MONOCHROME {
set_monochrome_cursor(
mse,
&res.monochrome_cursor,
res.width as usize,
res.height as usize,
);
} else {
let res_width = get_image_width(res.pixman_image);
let res_height = get_image_height(res.pixman_image);
if res_width as u32 != mse.width || res_height as u32 != mse.height {
return;
}
let res_data_ptr = get_image_data(res.pixman_image) as *mut u8;
unsafe {
ptr::copy(res_data_ptr, mse.data.as_mut_ptr(), mse_data_size);
}
}
if res.format == VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM
|| res.format == VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM
{
let mut i = 0;
while i < mse_data_size {
mse.data.swap(i, i + 2);
i += 4;
}
}
scanout.cursor_visible = true;
}
fn update_cursor(
&mut self,
rt: &mut GpuRuntimeState,
info_cursor: &VirtioGpuUpdateCursor,
hdr_type: u32,
) -> Result<()> {
trace::trace_scope_start!(update_cursor);
let scanout = &mut rt.scanouts[info_cursor.pos.scanout_id as usize];
match &mut scanout.mouse {
None => {
let mouse = DisplayMouse::new(
self.cursor_size,
self.cursor_size,
info_cursor.hot_x,
info_cursor.hot_y,
);
scanout.mouse = Some(mouse);
}
Some(mouse) => {
if hdr_type == VIRTIO_GPU_CMD_UPDATE_CURSOR {
mouse.hot_x = info_cursor.hot_x;
mouse.hot_y = info_cursor.hot_y;
}
}
}
if info_cursor.resource_id > 0 {
self.update_cursor_image(rt, info_cursor);
}
let scanout = &mut rt.scanouts[info_cursor.pos.scanout_id as usize];
display_cursor_define(&scanout.con, scanout.mouse.as_ref().unwrap())?;
Ok(())
}
fn cmd_update_cursor(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_cursor = VirtioGpuUpdateCursor::default();
self.get_request(req, &mut info_cursor)?;
if info_cursor.pos.scanout_id >= self.num_scanouts {
error!(
"GuestError: The scanout id {} is out of range.",
info_cursor.pos.scanout_id
);
return Ok(());
}
trace::virtio_gpu_update_cursor(
info_cursor.pos.scanout_id,
info_cursor.pos.x_coord,
info_cursor.pos.y_coord,
info_cursor.resource_id,
if req.header.hdr_type == VIRTIO_GPU_CMD_MOVE_CURSOR {
"move"
} else {
"update"
},
);
let scanout = &mut rt.scanouts[info_cursor.pos.scanout_id as usize];
if req.header.hdr_type == VIRTIO_GPU_CMD_MOVE_CURSOR {
if info_cursor.resource_id == 0 && scanout.cursor_visible && scanout.mouse.is_some() {
let data = &mut scanout.mouse.as_mut().unwrap().data;
for (i, item) in data.iter_mut().enumerate() {
if i % 4 == 3 {
*item = 0_u8;
}
}
display_cursor_define(&scanout.con, scanout.mouse.as_ref().unwrap())?;
scanout.cursor_visible = false;
} else if info_cursor.resource_id > 0 && !scanout.cursor_visible {
self.update_cursor(rt, &info_cursor, VIRTIO_GPU_CMD_MOVE_CURSOR)?;
}
} else if req.header.hdr_type == VIRTIO_GPU_CMD_UPDATE_CURSOR {
self.update_cursor(rt, &info_cursor, VIRTIO_GPU_CMD_UPDATE_CURSOR)?;
} else {
bail!("Wrong header type for cursor queue");
}
Ok(())
}
fn cmd_get_display_info(&mut self, rt: &GpuRuntimeState, req: &VirtioGpuRequest) -> Result<()> {
let mut display_info = VirtioGpuDisplayInfo::default();
display_info.header.hdr_type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
let output_states_lock = self.output_states.lock().unwrap();
for i in 0..self.num_scanouts {
if (rt.enable_output_bitmask & (1 << i)) != 0 {
let i = i as usize;
display_info.pmodes[i].enabled = 1;
display_info.pmodes[i].rect.width = output_states_lock[i].width;
display_info.pmodes[i].rect.height = output_states_lock[i].height;
display_info.pmodes[i].flags = 0;
}
}
drop(output_states_lock);
info!("virtio-gpu get the display info {:?}", display_info);
self.send_response(req, &mut display_info)
}
fn cmd_get_edid(&mut self, rt: &GpuRuntimeState, req: &VirtioGpuRequest) -> Result<()> {
let mut edid_req = VirtioGpuGetEdid::default();
self.change_run_stage(rt)?;
self.get_request(req, &mut edid_req)?;
if edid_req.scanouts >= self.num_scanouts {
error!(
"GuestError: The scanouts {} of request exceeds the max_outputs {}.",
edid_req.scanouts, self.num_scanouts
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER, req);
}
trace::virtio_gpu_get_edid(edid_req.scanouts);
let mut edid_resp = VirtioGpuRespEdid::default();
edid_resp.header.hdr_type = VIRTIO_GPU_RESP_OK_EDID;
let output_states_lock = self.output_states.lock().unwrap();
let mut edid_info = EdidInfo::new(
"HWV",
"STRA Monitor",
100,
output_states_lock[edid_req.scanouts as usize].width,
output_states_lock[edid_req.scanouts as usize].height,
);
drop(output_states_lock);
edid_info.edid_array_fulfill(&mut edid_resp.edid);
edid_resp.size = edid_resp.edid.len() as u32;
self.send_response(req, &mut edid_resp)
}
fn cmd_resource_create_2d(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_create_2d = VirtioGpuResourceCreate2d::default();
self.get_request(req, &mut info_create_2d)?;
trace::virtio_gpu_resource_create_2d(
info_create_2d.resource_id,
info_create_2d.format,
info_create_2d.width,
info_create_2d.height,
);
if info_create_2d.resource_id == 0 {
error!("GuestError: resource id 0 is not allowed.");
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, req);
}
if rt.get_resource_idx(info_create_2d.resource_id).is_some() {
error!(
"GuestError: resource {} already exists.",
info_create_2d.resource_id
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, req);
}
let mut res = GpuResource {
width: info_create_2d.width,
height: info_create_2d.height,
format: info_create_2d.format,
resource_id: info_create_2d.resource_id,
..Default::default()
};
let (mem, error) = cal_image_hostmem(res.format, res.width, res.height, self.cursor_size);
if mem.is_none() {
return self.response_nodata(error, req);
}
res.host_mem = mem.unwrap() as u64;
if res
.host_mem
.checked_add(rt.used_hostmem)
.filter(|&sum| sum <= self.max_hostmem)
.is_some()
{
if res.format == VIRTIO_GPU_FORMAT_MONOCHROME {
res.monochrome_cursor = vec![0_u8; (res.width * res.height / 8 * 2) as usize];
} else {
res.pixman_image = create_pixman_image(
get_pixman_format(res.format).unwrap(),
info_create_2d.width as i32,
info_create_2d.height as i32,
ptr::null_mut(),
0,
);
}
}
if res.monochrome_cursor.is_empty() && res.pixman_image.is_null() {
error!(
"GuestError: Fail to create resource(id {}, width {}, height {}) on host.",
res.resource_id, res.width, res.height
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY, req);
}
rt.used_hostmem += res.host_mem;
rt.resources_list.push(res);
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
fn cmd_resource_unref(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_resource_unref = VirtioGpuResourceUnref::default();
self.get_request(req, &mut info_resource_unref)?;
trace::virtio_gpu_resource_unref(info_resource_unref.resource_id);
if let Some(res_index) = rt.get_resource_idx(info_resource_unref.resource_id) {
rt.resource_destroy(res_index);
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
} else {
error!(
"GuestError: illegal resource specified {}.",
info_resource_unref.resource_id,
);
self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, req)
}
}
fn cmd_set_scanout(&mut self, rt: &mut GpuRuntimeState, req: &VirtioGpuRequest) -> Result<()> {
let mut info_set_scanout = VirtioGpuSetScanout::default();
self.get_request(req, &mut info_set_scanout)?;
trace::virtio_gpu_set_scanout(
info_set_scanout.scanout_id,
info_set_scanout.resource_id,
info_set_scanout.rect.width,
info_set_scanout.rect.height,
info_set_scanout.rect.x_coord,
info_set_scanout.rect.y_coord,
);
if info_set_scanout.scanout_id >= self.num_scanouts {
error!(
"GuestError: The scanout id {} is out of range.",
info_set_scanout.scanout_id
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID, req);
}
if info_set_scanout.resource_id == 0 {
rt.disable_scanout(info_set_scanout.scanout_id as usize);
return self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req);
}
let (res_idx, error) =
rt.get_backed_resource_idx(info_set_scanout.resource_id, "cmd_set_scanout");
if res_idx.is_none() {
return self.response_nodata(error, req);
}
let res = &mut rt.resources_list[res_idx.unwrap()];
if info_set_scanout.rect.width < 16
|| info_set_scanout.rect.height < 16
|| !is_rect_in_resource(&info_set_scanout.rect, res)
{
error!(
"GuestError: The resource (id: {} width: {} height: {}) is outfit for scanout (id: {} width: {} height: {} x_coord: {} y_coord: {}).",
res.resource_id,
res.width,
res.height,
info_set_scanout.scanout_id,
info_set_scanout.rect.width,
info_set_scanout.rect.height,
info_set_scanout.rect.x_coord,
info_set_scanout.rect.y_coord,
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER, req);
}
let pixman_format = get_image_format(res.pixman_image);
let bpp = u32::from(pixman_format_bpp(pixman_format as u32)).div_ceil(8);
let pixman_stride = get_image_stride(res.pixman_image);
let offset = info_set_scanout.rect.x_coord * bpp
+ info_set_scanout.rect.y_coord * pixman_stride as u32;
let res_data = if info_set_scanout.resource_id & VIRTIO_GPU_RES_FRAMEBUF != 0 {
res.iov[0].iov_base as *mut u32
} else {
get_image_data(res.pixman_image)
};
let res_data_offset = unsafe { res_data.offset(offset as isize) };
let scanout = &mut rt.scanouts[info_set_scanout.scanout_id as usize];
if scanout.surface.is_none()
|| get_image_data(scanout.surface.unwrap().image) != res_data_offset
|| scanout.width != info_set_scanout.rect.width
|| scanout.height != info_set_scanout.rect.height
{
let surface = create_surface(
scanout,
info_set_scanout,
res,
pixman_format,
pixman_stride,
res_data_offset,
);
if surface.image.is_null() {
error!("HostError: surface image create failed, check pixman library.");
return self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req);
}
}
let old_res_id = scanout.resource_id;
if let Some(old_res_idx) = rt.get_resource_idx(old_res_id) {
let old_res = &mut rt.resources_list[old_res_idx];
old_res.scanouts_bitmask &= !(1 << info_set_scanout.scanout_id);
}
let res = &mut rt.resources_list[res_idx.unwrap()];
res.scanouts_bitmask |= 1 << info_set_scanout.scanout_id;
let scanout = &mut rt.scanouts[info_set_scanout.scanout_id as usize];
scanout.resource_id = info_set_scanout.resource_id;
scanout.x = info_set_scanout.rect.x_coord;
scanout.y = info_set_scanout.rect.y_coord;
scanout.width = info_set_scanout.rect.width;
scanout.height = info_set_scanout.rect.height;
if (self.driver_features & (1 << VIRTIO_GPU_F_EDID)) == 0
&& (info_set_scanout.resource_id & VIRTIO_GPU_RES_WIN_FRAMEBUF) != 0
{
self.change_run_stage(rt)?;
}
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
fn cmd_resource_flush(&mut self, rt: &GpuRuntimeState, req: &VirtioGpuRequest) -> Result<()> {
let mut info_res_flush = VirtioGpuResourceFlush::default();
self.get_request(req, &mut info_res_flush)?;
trace::virtio_gpu_resource_flush(
info_res_flush.resource_id,
info_res_flush.rect.width,
info_res_flush.rect.height,
info_res_flush.rect.x_coord,
info_res_flush.rect.y_coord,
);
let res_index = rt.get_resource_idx(info_res_flush.resource_id);
if res_index.is_none() {
error!(
"GuestError: The resource_id {} in resource flush request is not existed.",
info_res_flush.resource_id
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, req);
}
let res = &rt.resources_list[res_index.unwrap()];
if !is_rect_in_resource(&info_res_flush.rect, res) {
error!(
"GuestError: The resource (id: {} width: {} height: {}) is outfit for flush rectangle (width: {} height: {} x_coord: {} y_coord: {}).",
res.resource_id, res.width, res.height,
info_res_flush.rect.width, info_res_flush.rect.height,
info_res_flush.rect.x_coord, info_res_flush.rect.y_coord,
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER, req);
}
let mut flush_reg = pixman_region16_t::default();
let flush_reg_ptr = &mut flush_reg as *mut pixman_region16_t;
unsafe {
pixman_region_init_rect(
flush_reg_ptr,
info_res_flush.rect.x_coord as i32,
info_res_flush.rect.y_coord as i32,
info_res_flush.rect.width,
info_res_flush.rect.height,
);
}
for i in 0..self.num_scanouts {
if res.scanouts_bitmask & (1 << i) == 0 {
continue;
}
let scanout = &rt.scanouts[i as usize];
let mut rect_reg = pixman_region16_t::default();
let mut final_reg = pixman_region16_t::default();
let rect_reg_ptr = &mut rect_reg as *mut pixman_region16_t;
let final_reg_ptr = &mut final_reg as *mut pixman_region16_t;
unsafe {
pixman_region_init(final_reg_ptr);
pixman_region_init_rect(
rect_reg_ptr,
scanout.x as i32,
scanout.y as i32,
scanout.width,
scanout.height,
);
pixman_region_intersect(final_reg_ptr, flush_reg_ptr, rect_reg_ptr);
pixman_region_translate(final_reg_ptr, -(scanout.x as i32), -(scanout.y as i32));
let extents = pixman_region_extents(final_reg_ptr);
display_graphic_update(
&scanout.con,
i32::from((*extents).x1),
i32::from((*extents).y1),
i32::from((*extents).x2 - (*extents).x1),
i32::from((*extents).y2 - (*extents).y1),
)?;
pixman_region_fini(rect_reg_ptr);
pixman_region_fini(final_reg_ptr);
}
}
unsafe {
pixman_region_fini(flush_reg_ptr);
}
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
fn cmd_transfer_to_host_2d_params_check(
&mut self,
rt: &GpuRuntimeState,
info_transfer: &VirtioGpuTransferToHost2d,
) -> (Option<usize>, u32) {
let (res_idx, error) =
rt.get_backed_resource_idx(info_transfer.resource_id, "cmd_transfer_to_host_2d");
if res_idx.is_none() {
return (None, error);
}
let res = &rt.resources_list[res_idx.unwrap()];
if res.resource_id & VIRTIO_GPU_RES_FRAMEBUF != 0 {
return (None, VIRTIO_GPU_RESP_OK_NODATA);
}
if !is_rect_in_resource(&info_transfer.rect, res) {
error!(
"GuestError: The resource (id: {} width: {} height: {}) is outfit for transfer rectangle (offset: {} width: {} height: {} x_coord: {} y_coord: {}).",
res.resource_id,
res.width,
res.height,
info_transfer.offset,
info_transfer.rect.width,
info_transfer.rect.height,
info_transfer.rect.x_coord,
info_transfer.rect.y_coord,
);
(None, VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER)
} else {
(res_idx, 0)
}
}
fn cmd_transfer_to_host_2d_update_resource(
&mut self,
rt: &mut GpuRuntimeState,
trans_info: &VirtioGpuTransferToHost2d,
res_idx: usize,
) -> Result<()> {
let res = &mut rt.resources_list[res_idx];
let pixman_format = get_image_format(res.pixman_image);
let width = get_image_width(res.pixman_image) as u32;
let bpp = u32::from(pixman_format_bpp(pixman_format as u32)).div_ceil(8);
let stride = get_image_stride(res.pixman_image) as u32;
let data: *mut u8 = get_image_data(res.pixman_image).cast();
if res.format == VIRTIO_GPU_FORMAT_MONOCHROME {
let v = unsafe { iov_to_buf_direct(&res.iov, 0, &mut res.monochrome_cursor)? };
if v != res.monochrome_cursor.len() {
error!("No enough data is copied for transfer_to_host_2d with monochrome");
}
return Ok(());
}
if trans_info.rect.x_coord == 0 && trans_info.rect.width == width {
let offset_dst = (trans_info.rect.y_coord * stride) as usize;
let trans_size = (trans_info.rect.height * stride) as usize;
let dst = unsafe { from_raw_parts_mut(data.add(offset_dst), trans_size) };
unsafe { iov_to_buf_direct(&res.iov, trans_info.offset, dst) }.inspect(|&v| {
if v < trans_size {
warn!("No enough data is copied for transfer_to_host_2d");
}
})?;
return Ok(());
}
let mut offset_src = trans_info.offset as usize;
let mut offset_dst =
(trans_info.rect.y_coord * stride + trans_info.rect.x_coord * bpp) as usize;
let line_size = (trans_info.rect.width * bpp) as usize;
for _ in 0..trans_info.rect.height {
let dst = unsafe { from_raw_parts_mut(data.add(offset_dst), line_size) };
unsafe { iov_to_buf_direct(&res.iov, offset_src as u64, dst) }.inspect(|&v| {
if v < line_size {
warn!("No enough data is copied for transfer_to_host_2d");
}
})?;
offset_src += stride as usize;
offset_dst += stride as usize;
}
Ok(())
}
fn cmd_transfer_to_host_2d(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_transfer = VirtioGpuTransferToHost2d::default();
self.get_request(req, &mut info_transfer)?;
trace::virtio_gpu_xfer_toh_2d(info_transfer.resource_id);
let (res_idx, error) = self.cmd_transfer_to_host_2d_params_check(rt, &info_transfer);
if res_idx.is_none() {
return self.response_nodata(error, req);
}
self.cmd_transfer_to_host_2d_update_resource(rt, &info_transfer, res_idx.unwrap())?;
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
fn cmd_resource_attach_backing(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_attach_backing = VirtioGpuResourceAttachBacking::default();
self.get_request(req, &mut info_attach_backing)?;
trace::virtio_gpu_resource_attach_backing(info_attach_backing.resource_id);
let res_idx = rt.get_resource_idx(info_attach_backing.resource_id);
if res_idx.is_none() {
error!(
"The resource_id {} in attach backing request request is not existed.",
info_attach_backing.resource_id
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID, req);
}
let res = &mut rt.resources_list[res_idx.unwrap()];
if !res.iov.is_empty() {
error!(
"GuestError: The resource_id {} in resource attach backing request already has iov.",
info_attach_backing.resource_id
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req);
}
if info_attach_backing.nr_entries > 16384 {
error!(
"GuestError: The nr_entries in resource attach backing request is too large ( {} > 16384).",
info_attach_backing.nr_entries
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req);
}
let entries = info_attach_backing.nr_entries;
let ents_size = size_of::<VirtioGpuMemEntry>() as u64 * u64::from(entries);
let head_size = size_of::<VirtioGpuResourceAttachBacking>() as u64;
if u64::from(req.out_len) < (ents_size + head_size) {
error!(
"GuestError: The nr_entries {} in resource attach backing request is larger than total len {}.",
info_attach_backing.nr_entries, req.out_len,
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req);
}
let mut ents = Vec::<VirtioGpuMemEntry>::new();
ents.resize(entries as usize, VirtioGpuMemEntry::default());
let ents_buf =
unsafe { from_raw_parts_mut(ents.as_mut_ptr() as *mut u8, ents_size as usize) };
let v = unsafe { iov_to_buf_direct(&req.out_iovec, head_size, ents_buf)? };
if v as u64 != ents_size {
error!(
"Virtio-GPU: Load no enough ents buf when attach backing, {} vs {}",
v, ents_size
);
return self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req);
}
let mut elemiovec = Vec::with_capacity(ents.len());
for ent in ents.iter() {
elemiovec.push(ElemIovec {
addr: GuestAddress(ent.addr),
len: ent.length,
});
}
match gpa_hva_iovec_map(&elemiovec, &self.mem_space, &None) {
Ok((_, iov)) => {
res.iov = iov;
res.backing_iov = elemiovec;
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
Err(e) => {
error!("Virtio-GPU: Map entry base failed, {:?}", e);
self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req)
}
}
}
fn cmd_resource_detach_backing(
&mut self,
rt: &mut GpuRuntimeState,
req: &VirtioGpuRequest,
) -> Result<()> {
let mut info_detach_backing = VirtioGpuResourceDetachBacking::default();
self.get_request(req, &mut info_detach_backing)?;
trace::virtio_gpu_resource_detach_backing(info_detach_backing.resource_id);
let (res_idx, error) = rt.get_backed_resource_idx(
info_detach_backing.resource_id,
"cmd_resource_detach_backing",
);
if res_idx.is_none() {
return self.response_nodata(error, req);
}
let res = &mut rt.resources_list[res_idx.unwrap()];
res.iov.clear();
res.backing_iov.clear();
self.response_nodata(VIRTIO_GPU_RESP_OK_NODATA, req)
}
fn process_control_queue(&mut self, mut req_queue: Vec<VirtioGpuRequest>) -> Result<()> {
let runtime = self.runtime.clone();
let mut rt = runtime.lock().unwrap();
for req in req_queue.iter_mut() {
if let Err(e) = match req.header.hdr_type {
VIRTIO_GPU_CMD_GET_DISPLAY_INFO => self.cmd_get_display_info(&rt, req),
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D => self.cmd_resource_create_2d(&mut rt, req),
VIRTIO_GPU_CMD_RESOURCE_UNREF => self.cmd_resource_unref(&mut rt, req),
VIRTIO_GPU_CMD_SET_SCANOUT => self.cmd_set_scanout(&mut rt, req),
VIRTIO_GPU_CMD_RESOURCE_FLUSH => self.cmd_resource_flush(&rt, req),
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D => self.cmd_transfer_to_host_2d(&mut rt, req),
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING => {
self.cmd_resource_attach_backing(&mut rt, req)
}
VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING => {
self.cmd_resource_detach_backing(&mut rt, req)
}
VIRTIO_GPU_CMD_GET_EDID => self.cmd_get_edid(&rt, req),
_ => {
error!(
"Failed to process unsupported command: {}",
req.header.hdr_type
);
self.response_nodata(VIRTIO_GPU_RESP_ERR_UNSPEC, req)
}
} {
error!("Fail to handle GPU request, {:?}.", e);
}
}
Ok(())
}
fn ctrl_queue_evt_handler(&mut self) -> Result<()> {
let mut queue = self.ctrl_queue.lock().unwrap();
let mut req_queue = Vec::new();
loop {
if self.migrating.load(Ordering::SeqCst) {
break;
}
let mut elem = queue
.vring
.pop_avail(&self.mem_space, self.driver_features)?;
if elem.desc_num == 0 {
break;
}
match VirtioGpuRequest::new(&self.mem_space, &mut elem) {
Ok(req) => {
req_queue.push(req);
}
Err(e) => {
error!(
"GuestError: Request will be ignored, because request header is incomplete and {:?}. \
Also, be aware that the virtual machine may suspended as response is not sent.",
e
);
}
}
}
drop(queue);
self.process_control_queue(req_queue)?;
Ok(())
}
fn cursor_queue_evt_handler(&mut self) -> Result<()> {
let cursor_queue = self.cursor_queue.clone();
let runtime = self.runtime.clone();
let mut queue = cursor_queue.lock().unwrap();
loop {
if self.migrating.load(Ordering::SeqCst) {
break;
}
let mut elem = queue
.vring
.pop_avail(&self.mem_space, self.driver_features)?;
if elem.desc_num == 0 {
break;
}
match VirtioGpuRequest::new(&self.mem_space, &mut elem) {
Ok(req) => match self.cmd_update_cursor(&mut runtime.lock().unwrap(), &req) {
Ok(_) => {}
Err(e) => {
error!("Failed to handle gpu cursor cmd for {:?}.", e);
}
},
Err(err) => {
error!("Failed to create GPU request, {:?}, just ignore it", err);
}
};
queue.vring.add_used(elem.index, 0).with_context(|| {
format!("Failed to add used ring(cursor), index {}", elem.index)
})?;
if queue.vring.should_notify(self.driver_features) {
(self.interrupt_cb)(&VirtioInterruptType::Vring, Some(&queue), false)
.with_context(|| {
VirtioError::InterruptTrigger("gpu cursor", VirtioInterruptType::Vring)
})?;
trace::virtqueue_send_interrupt("Cursor", &*queue as *const _ as u64);
}
}
Ok(())
}
}
impl EventNotifierHelper for GpuIoHandler {
fn internal_notifiers(handler: Arc<Mutex<Self>>) -> Vec<EventNotifier> {
let handler_raw = handler.lock().unwrap();
let mut notifiers = Vec::new();
let handler_clone = handler.clone();
let h: Rc<NotifierCallback> = Rc::new(move |_, fd: RawFd| {
read_fd(fd);
let mut locked_handler = handler_clone.lock().unwrap();
let _inflight = locked_handler.io_inflight.inc_ref();
if let Err(e) = locked_handler.ctrl_queue_evt_handler() {
error!("Failed to process ctrlq for virtio gpu, err: {:?}", e);
}
None
});
notifiers.push(EventNotifier::new(
NotifierOperation::AddShared,
handler_raw.ctrl_queue_evt.as_raw_fd(),
None,
EventSet::IN,
vec![h],
));
let handler_clone = handler.clone();
let h: Rc<NotifierCallback> = Rc::new(move |_, fd: RawFd| {
read_fd(fd);
let mut locked_handler = handler_clone.lock().unwrap();
let _inflight = locked_handler.io_inflight.inc_ref();
if let Err(e) = locked_handler.cursor_queue_evt_handler() {
error!("Failed to process cursorq for virtio gpu, err: {:?}", e);
}
None
});
notifiers.push(EventNotifier::new(
NotifierOperation::AddShared,
handler_raw.cursor_queue_evt.as_raw_fd(),
None,
EventSet::IN,
vec![h],
));
notifiers
}
}
#[derive(Clone, Copy, Debug, ByteCode, Serialize, Deserialize)]
pub struct VirtioGpuConfig {
pub events_read: u32,
pub events_clear: u32,
pub num_scanouts: u32,
pub num_capsets: u32,
}
pub struct Gpu {
base: VirtioBase,
cfg: GpuDevConfig,
config_space: Arc<Mutex<VirtioGpuConfig>>,
output_states: Arc<Mutex<[VirtioGpuOutputState; VIRTIO_GPU_MAX_OUTPUTS]>>,
consoles: Vec<Option<Weak<Mutex<DisplayConsole>>>>,
mem_space: Arc<AddressSpace>,
runtime: Arc<Mutex<GpuRuntimeState>>,
migrating: Arc<AtomicBool>,
io_inflight: IoRef,
queue_evts: Arc<Mutex<Vec<Arc<EventFd>>>>,
bar0_fb: Option<FileBackend>,
}
unsafe impl Send for Gpu {}
unsafe impl Sync for Gpu {}
impl Gpu {
pub fn new(cfg: GpuDevConfig, mem_space: Arc<AddressSpace>) -> Gpu {
Self {
base: VirtioBase::new(VIRTIO_TYPE_GPU, QUEUE_NUM_GPU, DEFAULT_VIRTQUEUE_SIZE),
cfg,
config_space: Arc::new(Mutex::new(VirtioGpuConfig::default())),
output_states: Arc::new(Mutex::new(
[VirtioGpuOutputState::default(); VIRTIO_GPU_MAX_OUTPUTS],
)),
consoles: Vec::new(),
mem_space,
runtime: Arc::new(Mutex::new(GpuRuntimeState::default())),
migrating: Arc::new(AtomicBool::new(false)),
io_inflight: IoRef::default(),
queue_evts: Arc::new(Mutex::new(Vec::new())),
bar0_fb: None,
}
}
pub fn set_bar0_fb(&mut self, fb: Option<FileBackend>) {
if !self.cfg.enable_bar0 {
self.bar0_fb = None;
return;
}
self.bar0_fb = fb;
}
pub fn get_bar0_fb(&self) -> Option<FileBackend> {
self.bar0_fb.as_ref().cloned()
}
fn build_device_config_space(&mut self) {
let mut config_space = self.config_space.lock().unwrap();
config_space.num_scanouts = self.cfg.max_outputs;
}
fn blob_range(blob_len: &mut u64, len: usize) -> GpuBlobRange {
let offset = *blob_len;
*blob_len += len as u64;
GpuBlobRange {
offset,
len: len as u64,
}
}
fn blob_slice<'a>(blob: &'a [u8], range: &GpuBlobRange) -> Result<&'a [u8]> {
let start = range.offset as usize;
let len = range.len as usize;
let end = start
.checked_add(len)
.with_context(|| "Invalid virtio-gpu blob range")?;
if end > blob.len() {
bail!("Virtio-gpu blob range is out of bounds");
}
Ok(&blob[start..end])
}
fn pixman_data(res: &GpuResource) -> Option<&[u8]> {
if res.pixman_image.is_null() {
return None;
}
let stride = get_image_stride(res.pixman_image);
let height = get_image_height(res.pixman_image);
if stride <= 0 || height <= 0 {
return None;
}
let len = (stride as usize).checked_mul(height as usize)?;
let data = get_image_data(res.pixman_image).cast::<u8>();
if data.is_null() {
return None;
}
Some(unsafe { from_raw_parts(data, len) })
}
fn pixman_image_len(image: *mut pixman_image_t) -> Result<usize> {
let stride = get_image_stride(image);
let height = get_image_height(image);
if stride <= 0 || height <= 0 {
bail!("Invalid virtio-gpu image size");
}
(stride as usize)
.checked_mul(height as usize)
.with_context(|| "Invalid virtio-gpu image size")
}
fn iov_matches_data(iov: &[Iovec], data: &[u8]) -> bool {
let mut offset = 0_usize;
for iov in iov {
if offset == data.len() {
break;
}
let len = (iov.iov_len as usize).min(data.len() - offset);
if len == 0 {
continue;
}
let iov_data = unsafe { from_raw_parts(iov.iov_base as *const u8, len) };
if iov_data != &data[offset..offset + len] {
return false;
}
offset += len;
}
offset == data.len()
}
fn snapshot_resource(blob_len: &mut u64, res: &GpuResource) -> GpuResourceState {
let image = Self::pixman_data(res).and_then(|data| {
if (res.resource_id & VIRTIO_GPU_RES_FRAMEBUF != 0 && !res.iov.is_empty())
|| Self::iov_matches_data(&res.iov, data)
{
None
} else {
Some(Self::blob_range(blob_len, data.len()))
}
});
let monochrome_cursor = if res.monochrome_cursor.is_empty() {
None
} else {
Some(Self::blob_range(blob_len, res.monochrome_cursor.len()))
};
let backing_iov = res
.backing_iov
.iter()
.map(|iov| GpuBackingState {
gpa: iov.addr.0,
len: iov.len,
})
.collect();
GpuResourceState {
resource_id: res.resource_id,
width: res.width,
height: res.height,
format: res.format,
scanouts_bitmask: res.scanouts_bitmask,
host_mem: res.host_mem,
backing_iov,
image,
monochrome_cursor,
}
}
fn snapshot_scanout(blob_len: &mut u64, scanout: &GpuScanout) -> GpuScanoutState {
let cursor = scanout.mouse.as_ref().map(|mouse| GpuCursorState {
width: mouse.width,
height: mouse.height,
hot_x: mouse.hot_x,
hot_y: mouse.hot_y,
data: Self::blob_range(blob_len, mouse.data.len()),
});
GpuScanoutState {
width: scanout.width,
height: scanout.height,
x: scanout.x,
y: scanout.y,
resource_id: scanout.resource_id,
cursor_visible: scanout.cursor_visible,
cursor,
}
}
fn append_resource_blob(data: &mut Vec<u8>, res: &GpuResource, state: &GpuResourceState) {
if state.image.is_some() {
let image = Self::pixman_data(res).unwrap();
data.extend_from_slice(image);
}
if !res.monochrome_cursor.is_empty() {
data.extend_from_slice(&res.monochrome_cursor);
}
}
fn append_scanout_blob(data: &mut Vec<u8>, scanout: &GpuScanout) {
if let Some(mouse) = &scanout.mouse {
data.extend_from_slice(&mouse.data);
}
}
fn build_state_blob(&self) -> Result<Vec<u8>> {
let mut blob_len = 0_u64;
let rt = self.runtime.lock().unwrap();
let output_states = self.output_states.lock().unwrap().to_vec();
let resources = rt
.resources_list
.iter()
.map(|res| Self::snapshot_resource(&mut blob_len, res))
.collect();
let scanouts = rt
.scanouts
.iter()
.map(|scanout| Self::snapshot_scanout(&mut blob_len, scanout))
.collect();
let state = GpuState {
device_features: self.base.device_features,
driver_features: self.base.driver_features,
broken: self.base.broken.load(Ordering::SeqCst),
config_space: *self.config_space.lock().unwrap(),
output_states,
resources,
scanouts,
enable_output_bitmask: rt.enable_output_bitmask,
used_hostmem: rt.used_hostmem,
};
let json = serde_json::to_vec(&state)?;
let header = GpuVmstateHeader {
magic: GPU_VMSTATE_MAGIC,
version: GPU_VMSTATE_VERSION,
json_len: json.len() as u64,
blob_len,
};
let blob_len_usize = blob_len as usize;
let state_len = size_of::<GpuVmstateHeader>()
.checked_add(json.len())
.and_then(|len| len.checked_add(blob_len_usize))
.with_context(|| "Virtio-gpu state length overflow")?;
let mut data = Vec::with_capacity(state_len);
data.extend_from_slice(header.as_bytes());
data.extend_from_slice(&json);
for (res, state) in rt.resources_list.iter().zip(state.resources.iter()) {
Self::append_resource_blob(&mut data, res, state);
}
for scanout in &rt.scanouts {
Self::append_scanout_blob(&mut data, scanout);
}
Ok(data)
}
fn split_state_blob(state: &[u8]) -> Result<(GpuState, &[u8])> {
if state.len() < size_of::<GpuVmstateHeader>() {
bail!("Virtio-gpu state is too short");
}
let header = GpuVmstateHeader::from_bytes(&state[..size_of::<GpuVmstateHeader>()])
.with_context(|| migration::error::MigrationError::FromBytesError("GPU_HEADER"))?;
if header.magic != GPU_VMSTATE_MAGIC || header.version != GPU_VMSTATE_VERSION {
bail!("Unsupported virtio-gpu vmstate header");
}
let json_start = size_of::<GpuVmstateHeader>();
let json_len = header.json_len as usize;
let blob_len = header.blob_len as usize;
let json_end = json_start
.checked_add(json_len)
.with_context(|| "Invalid virtio-gpu json length")?;
let blob_end = json_end
.checked_add(blob_len)
.with_context(|| "Invalid virtio-gpu blob length")?;
if blob_end != state.len() {
bail!("Invalid virtio-gpu vmstate length");
}
let gpu_state: GpuState = serde_json::from_slice(&state[json_start..json_end])
.with_context(|| migration::error::MigrationError::FromBytesError("GPU"))?;
Ok((gpu_state, &state[json_end..blob_end]))
}
fn rebuild_resource(
mem_space: &AddressSpace,
state: &GpuResourceState,
blob: &[u8],
) -> Result<GpuResource> {
let (mem, _) = cal_image_hostmem(state.format, state.width, state.height, u32::MAX);
let host_mem = mem.with_context(|| "Invalid virtio-gpu resource size")? as u64;
if host_mem != state.host_mem {
bail!("Virtio-gpu resource host memory size mismatch");
}
let backing_iov: Vec<ElemIovec> = state
.backing_iov
.iter()
.map(|iov| ElemIovec {
addr: GuestAddress(iov.gpa),
len: iov.len,
})
.collect();
let iov = if backing_iov.is_empty() {
Vec::new()
} else {
gpa_hva_iovec_map(&backing_iov, mem_space, &None)
.with_context(|| "Failed to remap virtio-gpu resource backing")?
.1
};
let mut res = GpuResource {
resource_id: state.resource_id,
width: state.width,
height: state.height,
format: state.format,
iov,
backing_iov,
scanouts_bitmask: state.scanouts_bitmask,
host_mem: state.host_mem,
..Default::default()
};
if let Some(range) = &state.monochrome_cursor {
res.monochrome_cursor = Self::blob_slice(blob, range)?.to_vec();
}
if state.format != VIRTIO_GPU_FORMAT_MONOCHROME {
let pixman_format = get_pixman_format(state.format)?;
res.pixman_image = create_pixman_image(
pixman_format,
state.width as i32,
state.height as i32,
ptr::null_mut(),
0,
);
if res.pixman_image.is_null() {
bail!("Failed to recreate virtio-gpu pixman image");
}
}
if let Some(range) = &state.image {
let data = Self::blob_slice(blob, range)?;
let image_len = Self::pixman_image_len(res.pixman_image)?;
if data.len() != image_len {
bail!("Virtio-gpu image blob size mismatch");
}
unsafe {
ptr::copy_nonoverlapping(
data.as_ptr(),
get_image_data(res.pixman_image).cast::<u8>(),
image_len,
);
}
} else if !res.pixman_image.is_null() && res.resource_id & VIRTIO_GPU_RES_FRAMEBUF == 0 {
if res.iov.is_empty() {
bail!("Virtio-gpu image blob is missing and resource has no backing");
}
let image_len = Self::pixman_image_len(res.pixman_image)?;
let data = get_image_data(res.pixman_image).cast::<u8>();
if data.is_null() {
bail!("Invalid virtio-gpu image data");
}
let dst = unsafe { from_raw_parts_mut(data, image_len) };
let len = unsafe { iov_to_buf_direct(&res.iov, 0, dst)? };
if len != image_len {
bail!("Virtio-gpu backing size is smaller than image");
}
}
Ok(res)
}
fn rebuild_scanout(
scanout: &mut GpuScanout,
state: &GpuScanoutState,
resources: &mut [GpuResource],
blob: &[u8],
) -> Result<()> {
scanout.width = state.width;
scanout.height = state.height;
scanout.x = state.x;
scanout.y = state.y;
scanout.resource_id = state.resource_id;
scanout.cursor_visible = state.cursor_visible;
if let Some(cursor) = &state.cursor {
scanout.mouse = Some(DisplayMouse {
width: cursor.width,
height: cursor.height,
hot_x: cursor.hot_x,
hot_y: cursor.hot_y,
data: Self::blob_slice(blob, &cursor.data)?.to_vec(),
});
display_cursor_define(&scanout.con, scanout.mouse.as_ref().unwrap())?;
}
if state.resource_id == 0 {
return Ok(());
}
let res_idx = resources
.iter()
.position(|res| res.resource_id == state.resource_id)
.with_context(|| "Failed to find virtio-gpu scanout resource")?;
let res = &mut resources[res_idx];
if res.pixman_image.is_null() || state.width == 0 || state.height == 0 {
return Ok(());
}
let info_set_scanout = VirtioGpuSetScanout {
rect: VirtioGpuRect {
x_coord: state.x,
y_coord: state.y,
width: state.width,
height: state.height,
},
scanout_id: 0,
resource_id: state.resource_id,
};
let pixman_format = get_image_format(res.pixman_image);
let bpp = u32::from(pixman_format_bpp(pixman_format as u32)).div_ceil(8);
let pixman_stride = get_image_stride(res.pixman_image);
let offset = state.x * bpp + state.y * pixman_stride as u32;
let res_data = if state.resource_id & VIRTIO_GPU_RES_FRAMEBUF != 0 && !res.iov.is_empty() {
res.iov[0].iov_base as *mut u32
} else {
get_image_data(res.pixman_image)
};
let res_data_offset = unsafe { res_data.offset(offset as isize) };
let surface = create_surface(
scanout,
info_set_scanout,
res,
pixman_format,
pixman_stride,
res_data_offset,
);
if surface.image.is_null() {
bail!("Failed to recreate virtio-gpu scanout surface");
}
Ok(())
}
fn restore_state_blob(&mut self, state: &[u8]) -> Result<()> {
let (gpu_state, blob) = Self::split_state_blob(state)?;
let total_hostmem = gpu_state.resources.iter().try_fold(0_u64, |acc, res| {
acc.checked_add(res.host_mem)
.with_context(|| "Virtio-gpu used host memory overflow")
})?;
if total_hostmem != gpu_state.used_hostmem {
bail!(
"Virtio-gpu used host memory mismatch, total {}, state {}",
total_hostmem,
gpu_state.used_hostmem
);
}
if gpu_state.used_hostmem > self.cfg.max_hostmem {
bail!(
"Virtio-gpu used host memory {} exceeds max_hostmem {}",
gpu_state.used_hostmem,
self.cfg.max_hostmem
);
}
self.base.device_features = gpu_state.device_features;
self.base.driver_features = gpu_state.driver_features;
self.base.broken.store(gpu_state.broken, Ordering::SeqCst);
*self.config_space.lock().unwrap() = gpu_state.config_space;
let mut output_states = self.output_states.lock().unwrap();
for (dst, src) in output_states.iter_mut().zip(gpu_state.output_states.iter()) {
*dst = *src;
}
drop(output_states);
let mut new_rt = GpuRuntimeState::new(&self.consoles);
new_rt.enable_output_bitmask = gpu_state.enable_output_bitmask;
new_rt.used_hostmem = gpu_state.used_hostmem;
for res_state in &gpu_state.resources {
new_rt
.resources_list
.push(Self::rebuild_resource(&self.mem_space, res_state, blob)?);
}
for (idx, scanout_state) in gpu_state.scanouts.iter().enumerate() {
if idx >= new_rt.scanouts.len() {
break;
}
Self::rebuild_scanout(
&mut new_rt.scanouts[idx],
scanout_state,
&mut new_rt.resources_list,
blob,
)?;
}
*self.runtime.lock().unwrap() = new_rt;
Ok(())
}
}
impl VirtioDevice for Gpu {
gen_base_func!(virtio_base, virtio_base_mut, VirtioBase, base);
fn device_quirk(&self) -> Option<VirtioDeviceQuirk> {
if self.cfg.enable_bar0 {
return Some(VirtioDeviceQuirk::VirtioGpuEnableBar0);
}
None
}
fn realize(&mut self) -> Result<()> {
if self.cfg.max_outputs > VIRTIO_GPU_MAX_OUTPUTS as u32 {
bail!(
"Invalid max_outputs {} which is bigger than {}",
self.cfg.max_outputs,
VIRTIO_GPU_MAX_OUTPUTS
);
}
let mut output_states = self.output_states.lock().unwrap();
output_states[0].width = self.cfg.xres;
output_states[0].height = self.cfg.yres;
let gpu_opts = Arc::new(GpuOpts {
output_states: self.output_states.clone(),
config_space: self.config_space.clone(),
interrupt_cb: None,
enable_bar0: self.cfg.enable_bar0,
});
for i in 0..self.cfg.max_outputs {
let dev_name = format!("virtio-gpu{}", i);
let con = console_init(dev_name, ConsoleType::Graphic, gpu_opts.clone());
let con_ref = con.as_ref().unwrap().upgrade().unwrap();
output_states[i as usize].con_id = con_ref.lock().unwrap().con_id;
self.consoles.push(con);
}
drop(output_states);
self.runtime = Arc::new(Mutex::new(GpuRuntimeState::new(&self.consoles)));
self.init_config_features()?;
Ok(())
}
fn init_config_features(&mut self) -> Result<()> {
self.base.device_features = (1u64 << VIRTIO_F_VERSION_1)
| (1u64 << VIRTIO_F_RING_INDIRECT_DESC)
| (1u64 << VIRTIO_F_RING_EVENT_IDX);
if self.cfg.edid {
self.base.device_features |= 1 << VIRTIO_GPU_F_EDID;
}
self.base.device_features |= 1 << VIRTIO_GPU_F_MONOCHROME;
trace::virtio_gpu_init_config_features(self.base.device_features);
self.build_device_config_space();
Ok(())
}
fn unrealize(&mut self) -> Result<()> {
for con in &self.consoles {
console_close(con)?;
}
Ok(())
}
fn read_config(&self, offset: u64, data: &mut [u8]) -> Result<()> {
let config_space = self.config_space.lock().unwrap();
read_config_default(config_space.as_bytes(), offset, data)
}
fn write_config(&mut self, offset: u64, data: &[u8]) -> Result<()> {
let mut config_space = self.config_space.lock().unwrap();
check_config_space_rw(config_space.as_bytes(), offset, data)?;
let mut config_cpy = *config_space;
let config_cpy_slice = config_cpy.as_mut_bytes();
config_cpy_slice[(offset as usize)..(offset as usize + data.len())].copy_from_slice(data);
if config_cpy.events_clear != 0 {
config_space.events_read &= !config_cpy.events_clear;
}
Ok(())
}
fn activate(
&mut self,
mem_space: Arc<AddressSpace>,
interrupt_cb: Arc<VirtioInterrupt>,
queue_evts: Vec<Arc<EventFd>>,
) -> Result<()> {
let queues = &self.base.queues;
if queues.len() != QUEUE_NUM_GPU {
return Err(anyhow!(VirtioError::IncorrectQueueNum(
QUEUE_NUM_GPU,
queues.len()
)));
}
let gpu_opts = Arc::new(GpuOpts {
output_states: self.output_states.clone(),
config_space: self.config_space.clone(),
interrupt_cb: Some(interrupt_cb.clone()),
enable_bar0: self.cfg.enable_bar0,
});
for con in &self.consoles {
let con_ref = con.as_ref().unwrap().upgrade().unwrap();
con_ref.lock().unwrap().dev_opts = gpu_opts.clone();
}
*self.queue_evts.lock().unwrap() = queue_evts.clone();
let handler = GpuIoHandler {
ctrl_queue: queues[0].clone(),
cursor_queue: queues[1].clone(),
mem_space,
ctrl_queue_evt: queue_evts[0].clone(),
cursor_queue_evt: queue_evts[1].clone(),
interrupt_cb,
driver_features: self.base.driver_features,
runtime: self.runtime.clone(),
migrating: self.migrating.clone(),
io_inflight: self.io_inflight.clone(),
num_scanouts: self.cfg.max_outputs,
output_states: self.output_states.clone(),
max_hostmem: self.cfg.max_hostmem,
cursor_size: self.cfg.cursor_size,
};
let notifiers = EventNotifierHelper::internal_notifiers(Arc::new(Mutex::new(handler)));
register_event_helper(notifiers, None, &mut self.base.deactivate_evts)?;
info!("virtio-gpu has been activated");
Ok(())
}
fn deactivate(&mut self) -> Result<()> {
if get_run_stage() == VmRunningStage::Os {
display_set_major_screen("ramfb")?;
set_run_stage(VmRunningStage::Bios);
}
let result = unregister_event_helper(None, &mut self.base.deactivate_evts);
self.queue_evts.lock().unwrap().clear();
*self.runtime.lock().unwrap() = GpuRuntimeState::new(&self.consoles);
info!("virtio-gpu deactivate {:?}", result);
result
}
}
impl StateTransfer for Gpu {
fn get_state_vec(&self) -> Result<Vec<u8>> {
self.build_state_blob()
}
fn set_state_mut(&mut self, state: &[u8], _version: u32) -> Result<()> {
self.restore_state_blob(state)
}
fn get_device_alias(&self) -> u64 {
MigrationManager::get_desc_alias(&GpuState::descriptor().name).unwrap_or(!0)
}
}
impl MigrationHook for Gpu {
fn resume(&mut self) -> Result<()> {
let locked_evts = self.queue_evts.lock().unwrap();
for evt in locked_evts.iter() {
if let Err(e) = evt.write(1) {
error!(
"Failed to trigger gpu queue event {}, {:?}",
evt.as_raw_fd(),
e
);
}
}
Ok(())
}
fn max_state_size(&self) -> usize {
MAX_LARGE_DEVICE_STATE_SIZE
}
fn notify_status(&self, save: bool, status: MigrationStatus) -> Result<()> {
if save {
match status {
MigrationStatus::Active => {
self.migrating.store(true, Ordering::SeqCst);
info!("Drain the request for gpu device {}", self.cfg.id);
wait_io_done(&self.io_inflight, DEFAULT_IO_TIMEOUT, &self.cfg.id);
}
MigrationStatus::Completed
| MigrationStatus::Failed
| MigrationStatus::Canceled => {
self.migrating.store(false, Ordering::SeqCst);
}
_ => {}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use machine_manager::config::str_slip_to_clap;
#[test]
fn test_parse_virtio_gpu_pci_cmdline() {
let gpu_cmd = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=5,edid=false,\
xres=2048,yres=800,enable_bar0=true,max_hostmem=268435457";
let gpu_cfg = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd, true, false)).unwrap();
assert_eq!(gpu_cfg.id, "gpu_1");
assert_eq!(gpu_cfg.bus, "pcie.0");
assert_eq!(gpu_cfg.addr, (4, 0));
assert_eq!(gpu_cfg.max_outputs, 5);
assert_eq!(gpu_cfg.xres, 2048);
assert_eq!(gpu_cfg.yres, 800);
assert!(!gpu_cfg.edid);
assert_eq!(gpu_cfg.max_hostmem, 268435457);
assert!(gpu_cfg.enable_bar0);
assert_eq!(gpu_cfg.cursor_size, 128);
let gpu_cmd2 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0";
let gpu_cfg =
GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd2, true, false)).unwrap();
assert_eq!(gpu_cfg.max_outputs, 1);
assert_eq!(gpu_cfg.xres, 1024);
assert_eq!(gpu_cfg.yres, 768);
assert!(gpu_cfg.edid);
assert_eq!(gpu_cfg.max_hostmem, VIRTIO_GPU_DEFAULT_MAX_HOSTMEM);
assert!(!gpu_cfg.enable_bar0);
assert_eq!(gpu_cfg.cursor_size, 128);
let gpu_cmd3 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=17";
let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd3, true, false));
assert!(result.is_err());
let gpu_cmd4 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_outputs=0";
let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd4, true, false));
assert!(result.is_err());
let gpu_cmd5 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,max_hostmem=0";
let result = GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd5, true, false));
assert!(result.is_err());
let gpu_cmd6 = "virtio-gpu-pci,id=gpu_1,bus=pcie.0,addr=0x4.0x0,cursor_size=64";
let gpu_cfg =
GpuDevConfig::try_parse_from(str_slip_to_clap(gpu_cmd6, true, false)).unwrap();
assert_eq!(gpu_cfg.cursor_size, 64);
}
}