diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 877746ef24..aa674a8497 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -mod backend; mod protocol; +mod virtio_2d_backend; +mod virtio_3d_backend; +mod virtio_backend; use std::cell::RefCell; use std::collections::VecDeque; +use std::fs::File; use std::i64; use std::io::Read; use std::mem::{self, size_of}; @@ -19,14 +22,12 @@ use std::time::Duration; use data_model::*; -use sys_util::{ - debug, error, warn, Error, EventFd, GuestAddress, GuestMemory, PollContext, PollToken, -}; +use sys_util::{debug, error, warn, EventFd, GuestAddress, GuestMemory, PollContext, PollToken}; pub use gpu_display::EventDevice; use gpu_display::*; -use gpu_renderer::{Renderer, RendererFlags}; - +use gpu_renderer::RendererFlags; +use msg_socket::{MsgReceiver, MsgSender}; use resources::Alloc; use super::{ @@ -36,8 +37,9 @@ use super::{ use super::{PciCapabilityType, VirtioPciShmCap, VirtioPciShmCapID}; -use self::backend::Backend; use self::protocol::*; +use self::virtio_2d_backend::Virtio2DBackend; +use self::virtio_3d_backend::Virtio3DBackend; use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability}; use vm_control::VmMemoryControlRequestSocket; @@ -45,6 +47,12 @@ use vm_control::VmMemoryControlRequestSocket; pub const DEFAULT_DISPLAY_WIDTH: u32 = 1280; pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024; +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum GpuMode { + Mode2D, + Mode3D, +} + #[derive(Debug)] pub struct GpuParameters { pub display_width: u32, @@ -53,6 +61,7 @@ pub struct GpuParameters { pub renderer_use_gles: bool, pub renderer_use_glx: bool, pub renderer_use_surfaceless: bool, + pub mode: GpuMode, } pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters { @@ -62,6 +71,7 @@ pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters { renderer_use_gles: true, renderer_use_glx: false, renderer_use_surfaceless: true, + mode: GpuMode::Mode3D, }; // First queue is for virtio gpu commands. Second queue is for cursor commands, which we expect @@ -73,6 +83,277 @@ const GPU_BAR_NUM: u8 = 4; const GPU_BAR_OFFSET: u64 = 0; const GPU_BAR_SIZE: u64 = 1 << 33; +/// A virtio-gpu backend state tracker which supports display and potentially accelerated rendering. +/// +/// Commands from the virtio-gpu protocol can be submitted here using the methods, and they will be +/// realized on the hardware. Most methods return a `GpuResponse` that indicate the success, +/// failure, or requested data for the given command. +trait Backend { + /// Returns the number of capsets provided by the Backend. + fn capsets() -> u32 + where + Self: Sized; + + /// Returns the bitset of virtio features provided by the Backend. + fn features() -> u64 + where + Self: Sized; + + /// Constructs a backend. + fn build( + possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, + renderer_flags: RendererFlags, + event_devices: Vec, + gpu_device_socket: VmMemoryControlRequestSocket, + pci_bar: Alloc, + ) -> Option> + where + Self: Sized; + + fn display(&self) -> &Rc>; + + /// Processes the internal `display` events and returns `true` if the main display was closed. + fn process_display(&mut self) -> bool; + + /// Creates a fence with the given id that can be used to determine when the previous command + /// completed. + fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse; + + /// Returns the id of the latest fence to complete. + fn fence_poll(&mut self) -> u32; + + /// For accelerated rendering capable backends, switch to the default rendering context. + fn force_ctx_0(&mut self) {} + + /// Attaches the given input device to the given surface of the display (to allow for input + /// from a X11 window for example). + fn import_event_device(&mut self, event_device: EventDevice, scanout: u32); + + /// If supported, export the resource with the given id to a file. + fn export_resource(&mut self, id: u32) -> Option; + + /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. + fn display_info(&self) -> [(u32, u32); 1]; + + /// Creates a 2D resource with the given properties and associates it with the given id. + fn create_resource_2d(&mut self, id: u32, width: u32, height: u32, format: u32) -> GpuResponse; + + /// Removes the guest's reference count for the given resource id. + fn unref_resource(&mut self, id: u32) -> GpuResponse; + + /// Sets the given resource id as the source of scanout to the display. + fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse; + + /// Flushes the given rectangle of pixels of the given resource to the display. + fn flush_resource(&mut self, id: u32, x: u32, y: u32, width: u32, height: u32) -> GpuResponse; + + /// Copes the given rectangle of pixels of the given resource's backing memory to the host side + /// resource. + fn transfer_to_resource_2d( + &mut self, + id: u32, + x: u32, + y: u32, + width: u32, + height: u32, + src_offset: u64, + mem: &GuestMemory, + ) -> GpuResponse; + + /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)` + /// tuples in the guest's physical address space. + fn attach_backing( + &mut self, + id: u32, + mem: &GuestMemory, + vecs: Vec<(GuestAddress, usize)>, + ) -> GpuResponse; + + /// Detaches any backing memory from the given resource, if there is any. + fn detach_backing(&mut self, id: u32) -> GpuResponse; + + /// Updates the cursor's memory to the given id, and sets its position to the given coordinates. + fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse; + + /// Moves the cursor's position to the given coordinates. + fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse; + + /// Gets the renderer's capset information associated with `index`. + fn get_capset_info(&self, index: u32) -> GpuResponse; + + /// Gets the capset of `version` associated with `id`. + fn get_capset(&self, id: u32, version: u32) -> GpuResponse; + + /// Creates a fresh renderer context with the given `id`. + fn create_renderer_context(&mut self, _id: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Destorys the renderer context associated with `id`. + fn destroy_renderer_context(&mut self, _id: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Attaches the indicated resource to the given context. + fn context_attach_resource(&mut self, _ctx_id: u32, _res_id: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// detaches the indicated resource to the given context. + fn context_detach_resource(&mut self, _ctx_id: u32, _res_id: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Creates a 3D resource with the given properties and associates it with the given id. + fn resource_create_3d( + &mut self, + _id: u32, + _target: u32, + _format: u32, + _bind: u32, + _width: u32, + _height: u32, + _depth: u32, + _array_size: u32, + _last_level: u32, + _nr_samples: u32, + _flags: u32, + ) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Copes the given 3D rectangle of pixels of the given resource's backing memory to the host + /// side resource. + fn transfer_to_resource_3d( + &mut self, + _ctx_id: u32, + _res_id: u32, + _x: u32, + _y: u32, + _z: u32, + _width: u32, + _height: u32, + _depth: u32, + _level: u32, + _stride: u32, + _layer_stride: u32, + _offset: u64, + ) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Copes the given rectangle of pixels from the resource to the given resource's backing + /// memory. + fn transfer_from_resource_3d( + &mut self, + _ctx_id: u32, + _res_id: u32, + _x: u32, + _y: u32, + _z: u32, + _width: u32, + _height: u32, + _depth: u32, + _level: u32, + _stride: u32, + _layer_stride: u32, + _offset: u64, + ) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Submits a command buffer to the given rendering context. + fn submit_command(&mut self, _ctx_id: u32, _commands: &mut [u8]) -> GpuResponse { + GpuResponse::ErrUnspec + } + + fn allocation_metadata( + &mut self, + _request_id: u32, + _request: Vec, + mut _response: Vec, + ) -> GpuResponse { + GpuResponse::ErrUnspec + } + + fn resource_create_v2( + &mut self, + _resource_id: u32, + _guest_memory_type: u32, + _guest_caching_type: u32, + _size: u64, + _pci_addr: u64, + _mem: &GuestMemory, + _vecs: Vec<(GuestAddress, usize)>, + _args: Vec, + ) -> GpuResponse { + GpuResponse::ErrUnspec + } + + fn resource_v2_unref(&mut self, _resource_id: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } +} + +#[derive(Clone)] +enum BackendKind { + Virtio2D, + Virtio3D, +} + +impl BackendKind { + /// Returns the number of capsets provided by the Backend. + fn capsets(&self) -> u32 { + match self { + BackendKind::Virtio2D => Virtio2DBackend::capsets(), + BackendKind::Virtio3D => Virtio3DBackend::capsets(), + } + } + + /// Returns the bitset of virtio features provided by the Backend. + fn features(&self) -> u64 { + match self { + BackendKind::Virtio2D => Virtio2DBackend::features(), + BackendKind::Virtio3D => Virtio3DBackend::features(), + } + } + + /// Initializes the backend. + fn build( + &self, + possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, + renderer_flags: RendererFlags, + event_devices: Vec, + gpu_device_socket: VmMemoryControlRequestSocket, + pci_bar: Alloc, + ) -> Option> { + match self { + BackendKind::Virtio2D => Virtio2DBackend::build( + possible_displays, + display_width, + display_height, + renderer_flags, + event_devices, + gpu_device_socket, + pci_bar, + ), + BackendKind::Virtio3D => Virtio3DBackend::build( + possible_displays, + display_width, + display_height, + renderer_flags, + event_devices, + gpu_device_socket, + pci_bar, + ), + } + } +} + struct ReturnDescriptor { index: u16, len: u32, @@ -88,11 +369,11 @@ struct Frontend { return_ctrl_descriptors: VecDeque, return_cursor_descriptors: VecDeque, fence_descriptors: Vec, - backend: Backend, + backend: Box, } impl Frontend { - fn new(backend: Backend) -> Frontend { + fn new(backend: Box) -> Frontend { Frontend { return_ctrl_descriptors: Default::default(), return_cursor_descriptors: Default::default(), @@ -101,7 +382,7 @@ impl Frontend { } } - fn display(&self) -> &Rc> { + fn display(&mut self) -> &Rc> { self.backend.display() } @@ -109,8 +390,26 @@ impl Frontend { self.backend.process_display() } - fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) { - self.backend.process_resource_bridge(resource_bridge); + fn process_resource_bridge(&mut self, resource_bridge: &ResourceResponseSocket) { + let request = match resource_bridge.recv() { + Ok(msg) => msg, + Err(e) => { + error!("error receiving resource bridge request: {}", e); + return; + } + }; + + let response = match request { + ResourceRequest::GetResource { id } => self + .backend + .export_resource(id) + .map(ResourceResponse::Resource) + .unwrap_or(ResourceResponse::Invalid), + }; + + if let Err(e) = resource_bridge.send(&response) { + error!("error sending resource bridge request: {}", e); + } } fn process_gpu_command( @@ -609,7 +908,9 @@ impl Worker { let _ = self.exit_evt.write(1); } } - Token::ResourceBridge { index } => process_resource_bridge[index] = true, + Token::ResourceBridge { index } => { + process_resource_bridge[index] = true; + } Token::InterruptResample => { self.interrupt.interrupt_resample(); } @@ -692,75 +993,6 @@ impl DisplayBackend { } } -// Builds a gpu backend with one of the given possible display backends, or None if they all -// failed. -fn build_backend( - possible_displays: &[DisplayBackend], - display_width: u32, - display_height: u32, - renderer_flags: RendererFlags, - event_devices: Vec, - gpu_device_socket: VmMemoryControlRequestSocket, - pci_bar: Alloc, -) -> Option { - let mut renderer_flags = renderer_flags; - let mut display_opt = None; - for display in possible_displays { - match display.build() { - Ok(c) => { - // If X11 is being used, that's an indication that the renderer should also be using - // glx. Otherwise, we are likely in an enviroment in which GBM will work for doing - // allocations of buffers we wish to display. TODO(zachr): this is a heuristic (or - // terrible hack depending on your POV). We should do something either smarter or - // more configurable - if display.is_x() { - renderer_flags = RendererFlags::new().use_glx(true); - } - display_opt = Some(c); - break; - } - Err(e) => error!("failed to open display: {}", e), - }; - } - let display = match display_opt { - Some(d) => d, - None => { - error!("failed to open any displays"); - return None; - } - }; - - if cfg!(debug_assertions) { - let ret = unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO) }; - if ret == -1 { - warn!("unable to dup2 stdout to stderr: {}", Error::last()); - } - } - - let renderer = match Renderer::init(renderer_flags) { - Ok(r) => r, - Err(e) => { - error!("failed to initialize gpu renderer: {}", e); - return None; - } - }; - - let mut backend = Backend::new( - display, - display_width, - display_height, - renderer, - gpu_device_socket, - pci_bar, - ); - - for event_device in event_devices { - backend.import_event_device(event_device, 0); - } - - Some(backend) -} - pub struct Gpu { exit_evt: EventFd, gpu_device_socket: Option, @@ -775,6 +1007,7 @@ pub struct Gpu { display_height: u32, renderer_flags: RendererFlags, pci_bar: Option, + backend_kind: BackendKind, } impl Gpu { @@ -793,6 +1026,11 @@ impl Gpu { .use_glx(gpu_parameters.renderer_use_glx) .use_surfaceless(gpu_parameters.renderer_use_surfaceless); + let backend_kind = match gpu_parameters.mode { + GpuMode::Mode2D => BackendKind::Virtio2D, + GpuMode::Mode3D => BackendKind::Virtio3D, + }; + Gpu { exit_evt, gpu_device_socket, @@ -807,6 +1045,7 @@ impl Gpu { display_height: gpu_parameters.display_height, renderer_flags, pci_bar: None, + backend_kind, } } @@ -819,7 +1058,7 @@ impl Gpu { events_read: Le32::from(events_read), events_clear: Le32::from(0), num_scanouts: Le32::from(self.num_scanouts.get() as u32), - num_capsets: Le32::from(3), + num_capsets: Le32::from(self.backend_kind.capsets()), } } } @@ -867,10 +1106,7 @@ impl VirtioDevice for Gpu { } fn features(&self) -> u64 { - 1 << VIRTIO_GPU_F_VIRGL - | 1 << VIRTIO_F_VERSION_1 - | 1 << VIRTIO_GPU_F_MEMORY - | 1 << VIRTIO_GPU_F_HOST_COHERENT + self.backend_kind.features() } fn ack_features(&mut self, value: u64) { @@ -919,6 +1155,7 @@ impl VirtioDevice for Gpu { let resource_bridges = mem::replace(&mut self.resource_bridges, Vec::new()); + let backend_kind = self.backend_kind.clone(); let ctrl_queue = queues.remove(0); let ctrl_evt = queue_evts.remove(0); let cursor_queue = queues.remove(0); @@ -935,7 +1172,7 @@ impl VirtioDevice for Gpu { thread::Builder::new() .name("virtio_gpu".to_string()) .spawn(move || { - let backend = match build_backend( + let backend = match backend_kind.build( &display_backends, display_width, display_height, diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs new file mode 100644 index 0000000000..d92b015916 --- /dev/null +++ b/devices/src/virtio/gpu/virtio_2d_backend.rs @@ -0,0 +1,644 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Implementation of a virtio-gpu protocol command processor which supports only display. + +use std::cell::RefCell; +use std::cmp::{max, min}; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap as Map; +use std::fmt::{self, Display}; +use std::fs::File; +use std::marker::PhantomData; +use std::rc::Rc; +use std::usize; + +use data_model::*; +use gpu_display::*; +use gpu_renderer::RendererFlags; +use resources::Alloc; +use sys_util::{error, GuestAddress, GuestMemory}; +use vm_control::VmMemoryControlRequestSocket; + +use super::protocol::GpuResponse; +pub use super::virtio_backend::{VirtioBackend, VirtioResource}; +use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1}; + +#[derive(Debug)] +pub enum Error { + CheckedArithmetic { + field1: (&'static str, usize), + field2: (&'static str, usize), + op: &'static str, + }, + CheckedRange { + field1: (&'static str, usize), + field2: (&'static str, usize), + }, + MemCopy(VolatileMemoryError), +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + CheckedArithmetic { + field1: (label1, value1), + field2: (label2, value2), + op, + } => write!( + f, + "arithmetic failed: {}({}) {} {}({})", + label1, value1, op, label2, value2 + ), + CheckedRange { + field1: (label1, value1), + field2: (label2, value2), + } => write!( + f, + "range check failed: {}({}) vs {}({})", + label1, value1, label2, value2 + ), + MemCopy(e) => write!(f, "{}", e), + } + } +} + +macro_rules! checked_arithmetic { + ($x:ident $op:ident $y:ident $op_name:expr) => { + $x.$op($y).ok_or_else(|| Error::CheckedArithmetic { + field1: (stringify!($x), $x as usize), + field2: (stringify!($y), $y as usize), + op: $op_name, + }) + }; + ($x:ident + $y:ident) => { + checked_arithmetic!($x checked_add $y "+") + }; + ($x:ident - $y:ident) => { + checked_arithmetic!($x checked_sub $y "-") + }; + ($x:ident * $y:ident) => { + checked_arithmetic!($x checked_mul $y "*") + }; +} + +macro_rules! checked_range { + ($x:expr; <= $y:expr) => { + if $x <= $y { + Ok(()) + } else { + Err(Error::CheckedRange { + field1: (stringify!($x), $x as usize), + field2: (stringify!($y), $y as usize), + }) + } + }; + ($x:ident <= $y:ident) => { + check_range!($x; <= $y) + }; +} + +pub struct Virtio2DResource { + width: u32, + height: u32, + guest_iovecs: Vec<(GuestAddress, usize)>, + guest_mem: Option, + host_mem: Vec, + host_mem_stride: u32, + no_sync_send: PhantomData<*mut ()>, +} + +/// Transfers a resource from potentially many chunked src VolatileSlices to a dst VolatileSlice. +pub fn transfer<'a, S: Iterator>>( + resource_w: u32, + resource_h: u32, + rect_x: u32, + rect_y: u32, + rect_w: u32, + rect_h: u32, + dst_stride: u32, + dst_offset: u64, + dst: VolatileSlice, + src_stride: u32, + src_offset: u64, + mut srcs: S, +) -> Result<(), Error> { + if rect_w == 0 || rect_h == 0 { + return Ok(()); + } + + checked_range!(checked_arithmetic!(rect_x + rect_w)?; <= resource_w)?; + checked_range!(checked_arithmetic!(rect_y + rect_h)?; <= resource_h)?; + + let bytes_per_pixel = 4 as u64; + + let rect_x = rect_x as u64; + let rect_y = rect_y as u64; + let rect_w = rect_w as u64; + let rect_h = rect_h as u64; + + let dst_stride = dst_stride as u64; + let dst_offset = dst_offset as u64; + let dst_resource_offset = dst_offset + (rect_y * dst_stride) + (rect_x * bytes_per_pixel); + + let src_stride = src_stride as u64; + let src_offset = src_offset as u64; + let src_resource_offset = src_offset + (rect_y * src_stride) + (rect_x * bytes_per_pixel); + + let mut next_src; + let mut next_line; + let mut current_height = 0 as u64; + let mut src_opt = srcs.next(); + + // Cumulative start offset of the current src. + let mut src_start_offset = 0 as u64; + while let Some(src) = src_opt { + if current_height >= rect_h { + break; + } + + let src_size = src.size() as u64; + + // Cumulative end offset of the current src. + let src_end_offset = checked_arithmetic!(src_start_offset + src_size)?; + + let src_line_vertical_offset = checked_arithmetic!(current_height * src_stride)?; + let src_line_horizontal_offset = checked_arithmetic!(rect_w * bytes_per_pixel)?; + + // Cumulative start/end offsets of the next line to copy within all srcs. + let src_line_start_offset = + checked_arithmetic!(src_resource_offset + src_line_vertical_offset)?; + let src_line_end_offset = + checked_arithmetic!(src_line_start_offset + src_line_horizontal_offset)?; + + // Clamp the line start/end offset to be inside the current src. + let src_copyable_start_offset = max(src_line_start_offset, src_start_offset); + let src_copyable_end_offset = min(src_line_end_offset, src_end_offset); + + if src_copyable_start_offset < src_copyable_end_offset { + let copyable_size = + checked_arithmetic!(src_copyable_end_offset - src_copyable_start_offset)?; + + let offset_within_src = match src_copyable_start_offset.checked_sub(src_start_offset) { + Some(difference) => difference, + None => 0, + }; + + if src_line_end_offset > src_end_offset { + next_src = true; + next_line = false; + } else if src_line_end_offset == src_end_offset { + next_src = true; + next_line = true; + } else { + next_src = false; + next_line = true; + } + + let src_subslice = src + .get_slice(offset_within_src, copyable_size) + .map_err(|e| Error::MemCopy(e))?; + + let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?; + let dst_line_horizontal_offset = + checked_arithmetic!(src_copyable_start_offset - src_line_start_offset)?; + let dst_line_offset = + checked_arithmetic!(dst_line_vertical_offset + dst_line_horizontal_offset)?; + let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?; + + let dst_subslice = dst + .get_slice(dst_start_offset, copyable_size) + .map_err(|e| Error::MemCopy(e))?; + + src_subslice.copy_to_volatile_slice(dst_subslice); + } else { + if src_line_start_offset >= src_start_offset { + next_src = true; + next_line = false; + } else { + next_src = false; + next_line = true; + } + }; + + if next_src { + src_start_offset = checked_arithmetic!(src_start_offset + src_size)?; + src_opt = srcs.next(); + } + + if next_line { + current_height += 1; + } + } + + Ok(()) +} + +impl Virtio2DResource { + /// Attaches scatter-gather memory to this resource. + pub fn attach_backing( + &mut self, + iovecs: Vec<(GuestAddress, usize)>, + mem: &GuestMemory, + ) -> bool { + if iovecs + .iter() + .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err()) + { + return false; + } + self.detach_backing(); + self.guest_mem = Some(mem.clone()); + for (addr, len) in iovecs { + self.guest_iovecs.push((addr, len)); + } + true + } + + /// Detaches previously attached scatter-gather memory from this resource. + pub fn detach_backing(&mut self) { + self.guest_iovecs.clear(); + self.guest_mem = None; + } + + fn as_mut(&mut self) -> &mut dyn VirtioResource { + self + } +} + +impl VirtioResource for Virtio2DResource { + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } + + fn import_to_display(&mut self, _display: &Rc>) -> Option { + None + } + + /// Performs a transfer to the given host side resource from its backing in guest memory. + fn write_from_guest_memory( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + src_offset: u64, + _mem: &GuestMemory, + ) { + let guest_mem = match &self.guest_mem { + Some(mem) => mem, + None => { + error!("failed to write to resource: no guest memory attached"); + return; + } + }; + + if self + .guest_iovecs + .iter() + .any(|&(addr, len)| guest_mem.get_slice(addr.offset(), len as u64).is_err()) + { + error!("failed to write to resource: invalid iovec attached"); + return; + } + + let mut src_slices = Vec::new(); + for (addr, len) in &self.guest_iovecs { + // Unwrap will not panic because we already checked the slices. + src_slices.push(guest_mem.get_slice(addr.offset(), *len as u64).unwrap()); + } + + let host_mem_len = self.host_mem.len() as u64; + + let src_stride = self.host_mem_stride; + let src_offset = src_offset; + + let dst_stride = self.host_mem_stride; + let dst_offset = 0; + + if let Err(e) = transfer( + self.width(), + self.height(), + x, + y, + width, + height, + dst_stride, + dst_offset, + self.host_mem + .as_mut_slice() + .get_slice(0, host_mem_len) + .unwrap(), + src_stride, + src_offset, + src_slices.iter().cloned(), + ) { + error!("failed to write to resource: {}", e); + } + } + + /// Reads from this host side resource to a volatile slice of memory. + fn read_to_volatile( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + dst: VolatileSlice, + dst_stride: u32, + ) { + let src_stride = self.host_mem_stride; + let src_offset = 0; + + let dst_offset = 0; + + let host_mem_len = self.host_mem.len() as u64; + + if let Err(e) = transfer( + self.width(), + self.height(), + x, + y, + width, + height, + dst_stride, + dst_offset, + dst, + src_stride, + src_offset, + [self + .host_mem + .as_mut_slice() + .get_slice(0, host_mem_len) + .unwrap()] + .iter() + .cloned(), + ) { + error!("failed to read from resource: {}", e); + } + } +} + +/// The virtio-gpu backend state tracker which does not support accelerated rendering. +pub struct Virtio2DBackend { + base: VirtioBackend, + resources: Map, + /// All commands processed by this 2D backend are synchronous and are completed immediately so + /// we just need to keep track of the latest created fence and return that in fence_poll(). + latest_created_fence_id: u32, +} + +impl Virtio2DBackend { + pub fn new(display: GpuDisplay, display_width: u32, display_height: u32) -> Virtio2DBackend { + Virtio2DBackend { + base: VirtioBackend { + display: Rc::new(RefCell::new(display)), + display_width, + display_height, + event_devices: Default::default(), + scanout_resource_id: None, + scanout_surface_id: None, + cursor_resource_id: None, + cursor_surface_id: None, + }, + resources: Default::default(), + latest_created_fence_id: 0, + } + } +} + +impl Backend for Virtio2DBackend { + /// Returns the number of capsets provided by the Backend. + fn capsets() -> u32 { + 0 + } + + /// Returns the bitset of virtio features provided by the Backend. + fn features() -> u64 { + 1 << VIRTIO_F_VERSION_1 + } + + /// Returns the underlying Backend. + fn build( + possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, + _renderer_flags: RendererFlags, + _event_devices: Vec, + _gpu_device_socket: VmMemoryControlRequestSocket, + _pci_bar: Alloc, + ) -> Option> { + let mut display_opt = None; + for display in possible_displays { + match display.build() { + Ok(c) => { + display_opt = Some(c); + break; + } + Err(e) => error!("failed to open display: {}", e), + }; + } + let display = match display_opt { + Some(d) => d, + None => { + error!("failed to open any displays"); + return None; + } + }; + + Some(Box::new(Virtio2DBackend::new( + display, + display_width, + display_height, + ))) + } + + fn display(&self) -> &Rc> { + &self.base.display + } + + /// Processes the internal `display` events and returns `true` if the main display was closed. + fn process_display(&mut self) -> bool { + self.base.process_display() + } + + /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. + fn display_info(&self) -> [(u32, u32); 1] { + self.base.display_info() + } + + /// Attaches the given input device to the given surface of the display (to allow for input + /// from a X11 window for example). + fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { + self.base.import_event_device(event_device, scanout); + } + + /// If supported, export the resource with the given id to a file. + fn export_resource(&mut self, _id: u32) -> Option { + None + } + + /// Creates a fence with the given id that can be used to determine when the previous command + /// completed. + fn create_fence(&mut self, _ctx_id: u32, fence_id: u32) -> GpuResponse { + self.latest_created_fence_id = fence_id; + + GpuResponse::OkNoData + } + + /// Returns the id of the latest fence to complete. + fn fence_poll(&mut self) -> u32 { + self.latest_created_fence_id + } + + fn create_resource_2d( + &mut self, + id: u32, + width: u32, + height: u32, + _format: u32, + ) -> GpuResponse { + if id == 0 { + return GpuResponse::ErrInvalidResourceId; + } + match self.resources.entry(id) { + Entry::Vacant(slot) => { + // All virtio formats are 4 bytes per pixel. + let resource_bpp = 4; + let resource_stride = resource_bpp * width; + let resource_size = (resource_stride as usize) * (height as usize); + + let gpu_resource = Virtio2DResource { + width, + height, + guest_iovecs: Vec::new(), + guest_mem: None, + host_mem: vec![0; resource_size], + host_mem_stride: resource_stride, + no_sync_send: PhantomData, + }; + slot.insert(gpu_resource); + GpuResponse::OkNoData + } + Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, + } + } + + /// Removes the guest's reference count for the given resource id. + fn unref_resource(&mut self, id: u32) -> GpuResponse { + match self.resources.remove(&id) { + Some(_) => GpuResponse::OkNoData, + None => GpuResponse::ErrInvalidResourceId, + } + } + + /// Sets the given resource id as the source of scanout to the display. + fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse { + if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() { + self.base.set_scanout(resource_id) + } else { + GpuResponse::ErrInvalidResourceId + } + } + + /// Flushes the given rectangle of pixels of the given resource to the display. + fn flush_resource( + &mut self, + id: u32, + _x: u32, + _y: u32, + _width: u32, + _height: u32, + ) -> GpuResponse { + if id == 0 { + return GpuResponse::OkNoData; + } + + let resource = match self.resources.get_mut(&id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; + + self.base.flush_resource(resource, id) + } + + /// Copes the given rectangle of pixels of the given resource's backing memory to the host side + /// resource. + fn transfer_to_resource_2d( + &mut self, + id: u32, + x: u32, + y: u32, + width: u32, + height: u32, + src_offset: u64, + mem: &GuestMemory, + ) -> GpuResponse { + if let Some(resource) = self.resources.get_mut(&id) { + resource.write_from_guest_memory(x, y, width, height, src_offset, mem); + GpuResponse::OkNoData + } else { + GpuResponse::ErrInvalidResourceId + } + } + + /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)` + /// tuples in the guest's physical address space. + fn attach_backing( + &mut self, + id: u32, + mem: &GuestMemory, + vecs: Vec<(GuestAddress, usize)>, + ) -> GpuResponse { + match self.resources.get_mut(&id) { + Some(resource) => { + if resource.attach_backing(vecs, mem) { + GpuResponse::OkNoData + } else { + GpuResponse::ErrUnspec + } + } + None => GpuResponse::ErrInvalidResourceId, + } + } + + /// Detaches any backing memory from the given resource, if there is any. + fn detach_backing(&mut self, id: u32) -> GpuResponse { + match self.resources.get_mut(&id) { + Some(resource) => { + resource.detach_backing(); + GpuResponse::OkNoData + } + None => GpuResponse::ErrInvalidResourceId, + } + } + + /// Updates the cursor's memory to the given id, and sets its position to the given coordinates. + fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse { + let resource = self.resources.get_mut(&id).map(|r| r.as_mut()); + + self.base.update_cursor(id, x, y, resource) + } + + /// Moves the cursor's position to the given coordinates. + fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse { + self.base.move_cursor(x, y) + } + + /// Gets the renderer's capset information associated with `index`. + fn get_capset_info(&self, _index: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } + + /// Gets the capset of `version` associated with `id`. + fn get_capset(&self, _id: u32, _version: u32) -> GpuResponse { + GpuResponse::ErrUnspec + } +} diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs similarity index 68% rename from devices/src/virtio/gpu/backend.rs rename to devices/src/virtio/gpu/virtio_3d_backend.rs index 88456121dc..21a5ac12f5 100644 --- a/devices/src/virtio/gpu/backend.rs +++ b/devices/src/virtio/gpu/virtio_3d_backend.rs @@ -2,11 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -//! Implementation for the transport agnostic virtio-gpu protocol, including display and rendering. +//! Implementation of a virtio-gpu protocol command processor which supports display and accelerated +//! rendering. use std::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; +use std::fs::File; use std::os::unix::io::AsRawFd; use std::rc::Rc; use std::usize; @@ -14,14 +16,13 @@ use std::usize; use libc::EINVAL; use data_model::*; - use msg_socket::{MsgReceiver, MsgSender}; use resources::Alloc; -use sys_util::{error, GuestAddress, GuestMemory}; +use sys_util::{error, warn, Error, GuestAddress, GuestMemory}; use gpu_display::*; use gpu_renderer::{ - Box3, Context as RendererContext, Error as GpuRendererError, Renderer, + Box3, Context as RendererContext, Error as GpuRendererError, Renderer, RendererFlags, Resource as GpuRendererResource, ResourceCreateArgs, }; @@ -29,11 +30,15 @@ use super::protocol::{ AllocationMetadataResponse, GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3, VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_MEMORY_HOST_COHERENT, }; -use crate::virtio::resource_bridge::*; +pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource}; +use crate::virtio::gpu::{ + Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY, + VIRTIO_GPU_F_VIRGL, +}; use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse}; -struct VirtioGpuResource { +struct Virtio3DResource { width: u32, height: u32, gpu_resource: GpuRendererResource, @@ -41,9 +46,9 @@ struct VirtioGpuResource { kvm_slot: Option, } -impl VirtioGpuResource { - pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> VirtioGpuResource { - VirtioGpuResource { +impl Virtio3DResource { + pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> Virtio3DResource { + Virtio3DResource { width, height, gpu_resource, @@ -57,8 +62,8 @@ impl VirtioGpuResource { height: u32, kvm_slot: u32, gpu_resource: GpuRendererResource, - ) -> VirtioGpuResource { - VirtioGpuResource { + ) -> Virtio3DResource { + Virtio3DResource { width, height, gpu_resource, @@ -67,7 +72,21 @@ impl VirtioGpuResource { } } - pub fn import_to_display(&mut self, display: &Rc>) -> Option { + fn as_mut(&mut self) -> &mut dyn VirtioResource { + self + } +} + +impl VirtioResource for Virtio3DResource { + fn width(&self) -> u32 { + self.width + } + + fn height(&self) -> u32 { + self.height + } + + fn import_to_display(&mut self, display: &Rc>) -> Option { if let Some((self_display, import)) = &self.display_import { if Rc::ptr_eq(self_display, display) { return Some(*import); @@ -103,7 +122,7 @@ impl VirtioGpuResource { } } - pub fn write_from_guest_memory( + fn write_from_guest_memory( &mut self, x: u32, y: u32, @@ -128,7 +147,7 @@ impl VirtioGpuResource { } } - pub fn read_to_volatile( + fn read_to_volatile( &mut self, x: u32, y: u32, @@ -152,29 +171,17 @@ impl VirtioGpuResource { } } -/// The virtio-gpu backend state tracker. -/// -/// Commands from the virtio-gpu protocol can be submitted here using the methods, and they will be -/// realized on the hardware. Most methods return a `GpuResponse` that indicate the success, -/// failure, or requested data for the given command. -pub struct Backend { - display: Rc>, - display_width: u32, - display_height: u32, +/// The virtio-gpu backend state tracker which supports accelerated rendering. +pub struct Virtio3DBackend { + base: VirtioBackend, renderer: Renderer, - resources: Map, + resources: Map, contexts: Map, - // Maps event devices to scanout number. - event_devices: Map, gpu_device_socket: VmMemoryControlRequestSocket, - scanout_surface: Option, - cursor_surface: Option, - scanout_resource: u32, - cursor_resource: u32, pci_bar: Alloc, } -impl Backend { +impl Virtio3DBackend { /// Creates a new backend for virtio-gpu that realizes all commands using the given `display` /// for showing the results, and `renderer` for submitting rendering commands. /// @@ -187,94 +194,166 @@ impl Backend { renderer: Renderer, gpu_device_socket: VmMemoryControlRequestSocket, pci_bar: Alloc, - ) -> Backend { - Backend { - display: Rc::new(RefCell::new(display)), - display_width, - display_height, + ) -> Virtio3DBackend { + Virtio3DBackend { + base: VirtioBackend { + display: Rc::new(RefCell::new(display)), + display_width, + display_height, + event_devices: Default::default(), + scanout_resource_id: None, + scanout_surface_id: None, + cursor_resource_id: None, + cursor_surface_id: None, + }, renderer, resources: Default::default(), contexts: Default::default(), - event_devices: Default::default(), gpu_device_socket, - scanout_surface: None, - cursor_surface: None, - scanout_resource: 0, - cursor_resource: 0, pci_bar, } } +} - /// Gets a reference to the display passed into `new`. - pub fn display(&self) -> &Rc> { - &self.display +impl Backend for Virtio3DBackend { + /// Returns the number of capsets provided by the Backend. + fn capsets() -> u32 { + 3 } - pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { - // TODO(zachr): support more than one scanout. - if scanout != 0 { - return; - } + /// Returns the bitset of virtio features provided by the Backend. + fn features() -> u64 { + 1 << VIRTIO_GPU_F_VIRGL + | 1 << VIRTIO_F_VERSION_1 + | 1 << VIRTIO_GPU_F_MEMORY + | 1 << VIRTIO_GPU_F_HOST_COHERENT + } - let mut display = self.display.borrow_mut(); - let event_device_id = match display.import_event_device(event_device) { - Ok(id) => id, - Err(e) => { - error!("error importing event device: {}", e); - return; + /// Returns the underlying Backend. + fn build( + possible_displays: &[DisplayBackend], + display_width: u32, + display_height: u32, + renderer_flags: RendererFlags, + event_devices: Vec, + gpu_device_socket: VmMemoryControlRequestSocket, + pci_bar: Alloc, + ) -> Option> { + let mut renderer_flags = renderer_flags; + let mut display_opt = None; + for display in possible_displays { + match display.build() { + Ok(c) => { + // If X11 is being used, that's an indication that the renderer should also be + // using glx. Otherwise, we are likely in an enviroment in which GBM will work + // for doing allocations of buffers we wish to display. TODO(zachr): this is a + // heuristic (or terrible hack depending on your POV). We should do something + // either smarter or more configurable. + if display.is_x() { + renderer_flags = RendererFlags::new().use_glx(true); + } + display_opt = Some(c); + break; + } + Err(e) => error!("failed to open display: {}", e), + }; + } + let display = match display_opt { + Some(d) => d, + None => { + error!("failed to open any displays"); + return None; } }; - self.scanout_surface - .map(|s| display.attach_event_device(s, event_device_id)); - self.event_devices.insert(event_device_id, scanout); + + if cfg!(debug_assertions) { + let ret = unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO) }; + if ret == -1 { + warn!("unable to dup2 stdout to stderr: {}", Error::last()); + } + } + + let renderer = match Renderer::init(renderer_flags) { + Ok(r) => r, + Err(e) => { + error!("failed to initialize gpu renderer: {}", e); + return None; + } + }; + + let mut backend_3d = Virtio3DBackend::new( + display, + display_width, + display_height, + renderer, + gpu_device_socket, + pci_bar, + ); + + for event_device in event_devices { + backend_3d.import_event_device(event_device, 0); + } + + Some(Box::new(backend_3d)) + } + + /// Gets a reference to the display passed into `new`. + fn display(&self) -> &Rc> { + &self.base.display } /// Processes the internal `display` events and returns `true` if the main display was closed. - pub fn process_display(&mut self) -> bool { - let mut display = self.display.borrow_mut(); - display.dispatch_events(); - self.scanout_surface - .map(|s| display.close_requested(s)) - .unwrap_or(false) - } - - pub fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) { - let request = match resource_bridge.recv() { - Ok(msg) => msg, - Err(e) => { - error!("error receiving resource bridge request: {}", e); - return; - } - }; - - let response = match request { - ResourceRequest::GetResource { id } => self - .resources - .get(&id) - .and_then(|resource| resource.gpu_resource.export().ok()) - .and_then(|export| Some(export.1)) - .map(ResourceResponse::Resource) - .unwrap_or(ResourceResponse::Invalid), - }; - - if let Err(e) = resource_bridge.send(&response) { - error!("error sending resource bridge request: {}", e); - } + fn process_display(&mut self) -> bool { + self.base.process_display() } /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. - pub fn display_info(&self) -> [(u32, u32); 1] { - [(self.display_width, self.display_height)] + fn display_info(&self) -> [(u32, u32); 1] { + self.base.display_info() + } + + /// Attaches the given input device to the given surface of the display (to allow for input + /// from an X11 window for example). + fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { + self.base.import_event_device(event_device, scanout); + } + + /// If supported, export the resource with the given id to a file. + fn export_resource(&mut self, id: u32) -> Option { + let test: Option = self + .resources + .get(&id) // Option + .and_then(|resource| resource.gpu_resource.export().ok()) // Option<(Query, File)> + .and_then(|t| Some(t.1)); // Option + return test; + } + + /// Creates a fence with the given id that can be used to determine when the previous command + /// completed. + fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse { + // There is a mismatch of ordering that is intentional. + // This create_fence matches the other functions in Backend, yet + // the renderer matches the virgl interface. + match self.renderer.create_fence(fence_id, ctx_id) { + Ok(_) => GpuResponse::OkNoData, + Err(e) => { + error!("failed to create fence: {}", e); + GpuResponse::ErrUnspec + } + } + } + + /// Returns the id of the latest fence to complete. + fn fence_poll(&mut self) -> u32 { + self.renderer.poll() + } + + fn force_ctx_0(&mut self) { + self.renderer.force_ctx_0(); } /// Creates a 2D resource with the given properties and associated it with the given id. - pub fn create_resource_2d( - &mut self, - id: u32, - width: u32, - height: u32, - format: u32, - ) -> GpuResponse { + fn create_resource_2d(&mut self, id: u32, width: u32, height: u32, format: u32) -> GpuResponse { if id == 0 { return GpuResponse::ErrInvalidResourceId; } @@ -284,7 +363,7 @@ impl Backend { match gpu_resource { Ok(gpu_resource) => { let virtio_gpu_resource = - VirtioGpuResource::new(width, height, gpu_resource); + Virtio3DResource::new(width, height, gpu_resource); slot.insert(virtio_gpu_resource); GpuResponse::OkNoData } @@ -299,7 +378,7 @@ impl Backend { } /// Removes the guest's reference count for the given resource id. - pub fn unref_resource(&mut self, id: u32) -> GpuResponse { + fn unref_resource(&mut self, id: u32) -> GpuResponse { match self.resources.remove(&id) { Some(_) => GpuResponse::OkNoData, None => GpuResponse::ErrInvalidResourceId, @@ -307,130 +386,38 @@ impl Backend { } /// Sets the given resource id as the source of scanout to the display. - pub fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse { - let mut display = self.display.borrow_mut(); - if resource_id == 0 { - if let Some(surface) = self.scanout_surface.take() { - display.release_surface(surface); - } - self.scanout_resource = 0; - if let Some(surface) = self.cursor_surface.take() { - display.release_surface(surface); - } - self.cursor_resource = 0; - GpuResponse::OkNoData - } else if self.resources.get_mut(&resource_id).is_some() { - self.scanout_resource = resource_id; - - if self.scanout_surface.is_none() { - match display.create_surface(None, self.display_width, self.display_height) { - Ok(surface) => { - // TODO(zachr): do not assume every event device belongs to this scanout_id; - // in other words, support multiple display outputs. - for &event_device in self.event_devices.keys() { - display.attach_event_device(surface, event_device); - } - self.scanout_surface = Some(surface); - } - Err(e) => error!("failed to create display surface: {}", e), - } - } - GpuResponse::OkNoData + fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse { + if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() { + self.base.set_scanout(resource_id) } else { GpuResponse::ErrInvalidResourceId } } - fn flush_resource_to_surface( + /// Flushes the given rectangle of pixels of the given resource to the display. + fn flush_resource( &mut self, - resource_id: u32, - surface_id: u32, + id: u32, _x: u32, _y: u32, _width: u32, _height: u32, - ) -> GpuResponse { - let resource = match self.resources.get_mut(&resource_id) { - Some(r) => r, - None => return GpuResponse::ErrInvalidResourceId, - }; - - if let Some(import_id) = resource.import_to_display(&self.display) { - self.display.borrow_mut().flip_to(surface_id, import_id); - return GpuResponse::OkNoData; - } - - // Import failed, fall back to a copy. - let mut display = self.display.borrow_mut(); - // Prevent overwriting a buffer that is currently being used by the compositor. - if display.next_buffer_in_use(surface_id) { - return GpuResponse::OkNoData; - } - - let fb = match display.framebuffer_region( - surface_id, - 0, - 0, - self.display_width, - self.display_height, - ) { - Some(fb) => fb, - None => { - error!("failed to access framebuffer for surface {}", surface_id); - return GpuResponse::ErrUnspec; - } - }; - - resource.read_to_volatile( - 0, - 0, - self.display_width, - self.display_height, - fb.as_volatile_slice(), - fb.stride(), - ); - display.flip(surface_id); - - GpuResponse::OkNoData - } - - /// Flushes the given rectangle of pixels of the given resource to the display. - pub fn flush_resource( - &mut self, - id: u32, - x: u32, - y: u32, - width: u32, - height: u32, ) -> GpuResponse { if id == 0 { return GpuResponse::OkNoData; } - let mut response = GpuResponse::OkNoData; + let resource = match self.resources.get_mut(&id) { + Some(r) => r, + None => return GpuResponse::ErrInvalidResourceId, + }; - if id == self.scanout_resource { - if let Some(surface_id) = self.scanout_surface { - response = self.flush_resource_to_surface(id, surface_id, x, y, width, height); - } - } - - if response != GpuResponse::OkNoData { - return response; - } - - if id == self.cursor_resource { - if let Some(surface_id) = self.cursor_surface { - response = self.flush_resource_to_surface(id, surface_id, x, y, width, height); - } - } - - response + self.base.flush_resource(resource, id) } /// Copes the given rectangle of pixels of the given resource's backing memory to the host side /// resource. - pub fn transfer_to_resource_2d( + fn transfer_to_resource_2d( &mut self, id: u32, x: u32, @@ -451,7 +438,7 @@ impl Backend { /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)` /// tuples in the guest's physical address space. - pub fn attach_backing( + fn attach_backing( &mut self, id: u32, mem: &GuestMemory, @@ -467,7 +454,7 @@ impl Backend { } /// Detaches any backing memory from the given resource, if there is any. - pub fn detach_backing(&mut self, id: u32) -> GpuResponse { + fn detach_backing(&mut self, id: u32) -> GpuResponse { match self.resources.get_mut(&id) { Some(resource) => { resource.gpu_resource.detach_backing(); @@ -478,71 +465,19 @@ impl Backend { } /// Updates the cursor's memory to the given id, and sets its position to the given coordinates. - pub fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse { - if id == 0 { - if let Some(surface) = self.cursor_surface.take() { - self.display.borrow_mut().release_surface(surface); - } - self.cursor_resource = 0; - GpuResponse::OkNoData - } else if let Some(resource) = self.resources.get_mut(&id) { - self.cursor_resource = id; - if self.cursor_surface.is_none() { - match self.display.borrow_mut().create_surface( - self.scanout_surface, - resource.width, - resource.height, - ) { - Ok(surface) => self.cursor_surface = Some(surface), - Err(e) => { - error!("failed to create cursor surface: {}", e); - return GpuResponse::ErrUnspec; - } - } - } + fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse { + let resource = self.resources.get_mut(&id).map(|r| r.as_mut()); - let cursor_surface = self.cursor_surface.unwrap(); - self.display.borrow_mut().set_position(cursor_surface, x, y); - - // Gets the resource's pixels into the display by importing the buffer. - if let Some(import_id) = resource.import_to_display(&self.display) { - self.display.borrow_mut().flip_to(cursor_surface, import_id); - return GpuResponse::OkNoData; - } - - // Importing failed, so try copying the pixels into the surface's slower shared memory - // framebuffer. - if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface) { - resource.read_to_volatile( - 0, - 0, - resource.width, - resource.height, - fb.as_volatile_slice(), - fb.stride(), - ) - } - self.display.borrow_mut().flip(cursor_surface); - GpuResponse::OkNoData - } else { - GpuResponse::ErrInvalidResourceId - } + self.base.update_cursor(id, x, y, resource) } /// Moves the cursor's position to the given coordinates. - pub fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse { - if let Some(cursor_surface) = self.cursor_surface { - if let Some(scanout_surface) = self.scanout_surface { - let mut display = self.display.borrow_mut(); - display.set_position(cursor_surface, x, y); - display.commit(scanout_surface); - } - } - GpuResponse::OkNoData + fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse { + self.base.move_cursor(x, y) } /// Gets the renderer's capset information associated with `index`. - pub fn get_capset_info(&self, index: u32) -> GpuResponse { + fn get_capset_info(&self, index: u32) -> GpuResponse { let id = match index { 0 => VIRTIO_GPU_CAPSET_VIRGL, 1 => VIRTIO_GPU_CAPSET_VIRGL2, @@ -555,12 +490,12 @@ impl Backend { } /// Gets the capset of `version` associated with `id`. - pub fn get_capset(&self, id: u32, version: u32) -> GpuResponse { + fn get_capset(&self, id: u32, version: u32) -> GpuResponse { GpuResponse::OkCapset(self.renderer.get_cap_set(id, version)) } /// Creates a fresh renderer context with the given `id`. - pub fn create_renderer_context(&mut self, id: u32) -> GpuResponse { + fn create_renderer_context(&mut self, id: u32) -> GpuResponse { if id == 0 { return GpuResponse::ErrInvalidContextId; } @@ -580,7 +515,7 @@ impl Backend { } /// Destorys the renderer context associated with `id`. - pub fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse { + fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse { match self.contexts.remove(&id) { Some(_) => GpuResponse::OkNoData, None => GpuResponse::ErrInvalidContextId, @@ -588,7 +523,7 @@ impl Backend { } /// Attaches the indicated resource to the given context. - pub fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { + fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { match ( self.contexts.get_mut(&ctx_id), self.resources.get_mut(&res_id), @@ -603,7 +538,7 @@ impl Backend { } /// detaches the indicated resource to the given context. - pub fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { + fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse { match ( self.contexts.get_mut(&ctx_id), self.resources.get_mut(&res_id), @@ -618,7 +553,7 @@ impl Backend { } /// Creates a 3D resource with the given properties and associated it with the given id. - pub fn resource_create_3d( + fn resource_create_3d( &mut self, id: u32, target: u32, @@ -682,7 +617,7 @@ impl Backend { }; let virtio_gpu_resource = - VirtioGpuResource::new(width, height, gpu_resource); + Virtio3DResource::new(width, height, gpu_resource); slot.insert(virtio_gpu_resource); response } @@ -696,7 +631,7 @@ impl Backend { } /// Copes the given 3D rectangle of pixels of the given resource's backing memory to the host /// side resource. - pub fn transfer_to_resource_3d( + fn transfer_to_resource_3d( &mut self, ctx_id: u32, res_id: u32, @@ -750,7 +685,7 @@ impl Backend { /// Copes the given rectangle of pixels from the resource to the given resource's backing /// memory. - pub fn transfer_from_resource_3d( + fn transfer_from_resource_3d( &mut self, ctx_id: u32, res_id: u32, @@ -803,7 +738,7 @@ impl Backend { } /// Submits a command buffer to the given rendering context. - pub fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> GpuResponse { + fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> GpuResponse { match self.contexts.get_mut(&ctx_id) { Some(ctx) => match ctx.submit(&mut commands[..]) { Ok(_) => GpuResponse::OkNoData, @@ -816,28 +751,7 @@ impl Backend { } } - pub fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse { - // There is a mismatch of ordering that is intentional. - // This create_fence matches the other functions in Backend, yet - // the renderer matches the virgl interface. - match self.renderer.create_fence(fence_id, ctx_id) { - Ok(_) => GpuResponse::OkNoData, - Err(e) => { - error!("failed to create fence: {}", e); - GpuResponse::ErrUnspec - } - } - } - - pub fn fence_poll(&mut self) -> u32 { - self.renderer.poll() - } - - pub fn force_ctx_0(&mut self) { - self.renderer.force_ctx_0(); - } - - pub fn allocation_metadata( + fn allocation_metadata( &mut self, request_id: u32, request: Vec, @@ -861,7 +775,7 @@ impl Backend { } } - pub fn resource_create_v2( + fn resource_create_v2( &mut self, resource_id: u32, guest_memory_type: u32, @@ -911,9 +825,9 @@ impl Backend { Ok(_resq) => match self.gpu_device_socket.recv() { Ok(response) => match response { VmMemoryResponse::RegisterMemory { pfn: _, slot } => { - entry.insert(VirtioGpuResource::v2_new( - self.display_width, - self.display_height, + entry.insert(Virtio3DResource::v2_new( + self.base.display_width, + self.base.display_height, slot, resource, )); @@ -940,9 +854,9 @@ impl Backend { } } _ => { - entry.insert(VirtioGpuResource::new( - self.display_width, - self.display_height, + entry.insert(Virtio3DResource::new( + self.base.display_width, + self.base.display_height, resource, )); @@ -954,7 +868,7 @@ impl Backend { } } - pub fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse { + fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse { match self.resources.remove(&resource_id) { Some(entry) => match entry.kvm_slot { Some(kvm_slot) => { diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs new file mode 100644 index 0000000000..605cf5a75e --- /dev/null +++ b/devices/src/virtio/gpu/virtio_backend.rs @@ -0,0 +1,281 @@ +// Copyright 2020 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::cell::RefCell; +use std::collections::BTreeMap as Map; +use std::num::NonZeroU32; +use std::rc::Rc; + +use super::protocol::GpuResponse; +use data_model::*; +use gpu_display::*; +use sys_util::{error, GuestMemory}; + +pub trait VirtioResource { + fn width(&self) -> u32; + + fn height(&self) -> u32; + + fn import_to_display(&mut self, display: &Rc>) -> Option; + + /// Performs a transfer to the given resource in the host from its backing in guest memory. + fn write_from_guest_memory( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + src_offset: u64, + _mem: &GuestMemory, + ); + + /// Reads from this resource in the host to a volatile slice of memory. + fn read_to_volatile( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + dst: VolatileSlice, + dst_stride: u32, + ); +} + +/// Handles some of the common functionality across the virtio 2D and 3D backends. +pub struct VirtioBackend { + pub display: Rc>, + pub display_width: u32, + pub display_height: u32, + pub scanout_resource_id: Option, + pub scanout_surface_id: Option, + pub cursor_resource_id: Option, + pub cursor_surface_id: Option, + // Maps event devices to scanout number. + pub event_devices: Map, +} + +impl VirtioBackend { + pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) { + // TODO(zachr): support more than one scanout. + if scanout != 0 { + return; + } + + let mut display = self.display.borrow_mut(); + let event_device_id = match display.import_event_device(event_device) { + Ok(id) => id, + Err(e) => { + error!("error importing event device: {}", e); + return; + } + }; + self.scanout_surface_id + .map(|s| display.attach_event_device(s, event_device_id)); + self.event_devices.insert(event_device_id, scanout); + } + + /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. + pub fn display_info(&self) -> [(u32, u32); 1] { + [(self.display_width, self.display_height)] + } + + /// Processes the internal `display` events and returns `true` if the main display was closed. + pub fn process_display(&mut self) -> bool { + let mut display = self.display.borrow_mut(); + display.dispatch_events(); + self.scanout_surface_id + .map(|s| display.close_requested(s)) + .unwrap_or(false) + } + + /// Sets the given resource id as the source of scanout to the display. + pub fn set_scanout(&mut self, resource_id: u32) -> GpuResponse { + let mut display = self.display.borrow_mut(); + if resource_id == 0 { + if let Some(surface_id) = self.scanout_surface_id.take() { + display.release_surface(surface_id); + } + self.scanout_resource_id = None; + GpuResponse::OkNoData + } else { + self.scanout_resource_id = NonZeroU32::new(resource_id); + + if self.scanout_surface_id.is_none() { + match display.create_surface(None, self.display_width, self.display_height) { + Ok(surface_id) => { + self.scanout_surface_id = Some(surface_id); + } + Err(e) => error!("failed to create display surface: {}", e), + } + } + GpuResponse::OkNoData + } + } + + pub fn flush_resource( + &mut self, + resource: &mut dyn VirtioResource, + resource_id: u32, + ) -> GpuResponse { + let mut response = GpuResponse::OkNoData; + + if let Some(scannout_resource_id) = self.scanout_resource_id { + if scannout_resource_id.get() == resource_id { + response = self.flush_scannout_resource_to_surface(resource); + } + } + + if response != GpuResponse::OkNoData { + return response; + } + + if let Some(cursor_resource_id) = self.cursor_resource_id { + if cursor_resource_id.get() == resource_id { + response = self.flush_cursor_resource_to_surface(resource); + } + } + + response + } + + pub fn flush_scannout_resource_to_surface( + &mut self, + resource: &mut dyn VirtioResource, + ) -> GpuResponse { + match self.scanout_surface_id { + Some(surface_id) => self.flush_resource_to_surface(resource, surface_id), + None => GpuResponse::OkNoData, + } + } + + pub fn flush_cursor_resource_to_surface( + &mut self, + resource: &mut dyn VirtioResource, + ) -> GpuResponse { + match self.cursor_surface_id { + Some(surface_id) => self.flush_resource_to_surface(resource, surface_id), + None => GpuResponse::OkNoData, + } + } + + pub fn flush_resource_to_surface( + &mut self, + resource: &mut dyn VirtioResource, + surface_id: u32, + ) -> GpuResponse { + if let Some(import_id) = resource.import_to_display(&self.display) { + self.display.borrow_mut().flip_to(surface_id, import_id); + return GpuResponse::OkNoData; + } + + // Import failed, fall back to a copy. + let mut display = self.display.borrow_mut(); + // Prevent overwriting a buffer that is currently being used by the compositor. + if display.next_buffer_in_use(surface_id) { + return GpuResponse::OkNoData; + } + + let fb = match display.framebuffer_region( + surface_id, + 0, + 0, + self.display_width, + self.display_height, + ) { + Some(fb) => fb, + None => { + error!("failed to access framebuffer for surface {}", surface_id); + return GpuResponse::ErrUnspec; + } + }; + + resource.read_to_volatile( + 0, + 0, + self.display_width, + self.display_height, + fb.as_volatile_slice(), + fb.stride(), + ); + + display.flip(surface_id); + + GpuResponse::OkNoData + } + + /// Updates the cursor's memory to the given id, and sets its position to the given coordinates. + pub fn update_cursor( + &mut self, + id: u32, + x: u32, + y: u32, + resource: Option<&mut dyn VirtioResource>, + ) -> GpuResponse { + if id == 0 { + if let Some(surface_id) = self.cursor_surface_id.take() { + self.display.borrow_mut().release_surface(surface_id); + } + self.cursor_resource_id = None; + GpuResponse::OkNoData + } else if let Some(resource) = resource { + self.cursor_resource_id = NonZeroU32::new(id); + + if self.cursor_surface_id.is_none() { + match self.display.borrow_mut().create_surface( + self.scanout_surface_id, + resource.width(), + resource.height(), + ) { + Ok(surface_id) => self.cursor_surface_id = Some(surface_id), + Err(e) => { + error!("failed to create cursor surface: {}", e); + return GpuResponse::ErrUnspec; + } + } + } + + let cursor_surface_id = self.cursor_surface_id.unwrap(); + self.display + .borrow_mut() + .set_position(cursor_surface_id, x, y); + + // Gets the resource's pixels into the display by importing the buffer. + if let Some(import_id) = resource.import_to_display(&self.display) { + self.display + .borrow_mut() + .flip_to(cursor_surface_id, import_id); + return GpuResponse::OkNoData; + } + + // Importing failed, so try copying the pixels into the surface's slower shared memory + // framebuffer. + if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface_id) { + resource.read_to_volatile( + 0, + 0, + resource.width(), + resource.height(), + fb.as_volatile_slice(), + fb.stride(), + ) + } + self.display.borrow_mut().flip(cursor_surface_id); + GpuResponse::OkNoData + } else { + GpuResponse::ErrInvalidResourceId + } + } + + /// Moves the cursor's position to the given coordinates. + pub fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse { + if let Some(cursor_surface_id) = self.cursor_surface_id { + if let Some(scanout_surface_id) = self.scanout_surface_id { + let mut display = self.display.borrow_mut(); + display.set_position(cursor_surface_id, x, y); + display.commit(scanout_surface_id); + } + } + GpuResponse::OkNoData + } +} diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs index 04ddb54385..b96b8ef71a 100644 --- a/gpu_display/src/gpu_display_wl.rs +++ b/gpu_display/src/gpu_display_wl.rs @@ -348,7 +348,7 @@ impl DisplayT for DisplayWl { fn release_event_device(&mut self, _event_device_id: u32) { // unsupported } - fn attach_event_device(&mut self, surface_id: u32, event_device_id: u32) { + fn attach_event_device(&mut self, _surface_id: u32, _event_device_id: u32) { // unsupported } } diff --git a/src/main.rs b/src/main.rs index e053082aec..fb1be25956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use crosvm::{ linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption, }; #[cfg(feature = "gpu")] -use devices::virtio::gpu::{GpuParameters, DEFAULT_GPU_PARAMS}; +use devices::virtio::gpu::{GpuMode, GpuParameters, DEFAULT_GPU_PARAMS}; use devices::{SerialParameters, SerialType}; use disk::QcowFile; use msg_socket::{MsgReceiver, MsgSender, MsgSocket}; @@ -124,6 +124,12 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result { for (k, v) in opts { match k { + "2d" | "2D" => { + gpu_params.mode = GpuMode::Mode2D; + } + "3d" | "3D" => { + gpu_params.mode = GpuMode::Mode3D; + } "egl" => match v { "true" | "" => { gpu_params.renderer_use_egl = true;