// Copyright 2018 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 for the transport agnostic virtio-gpu protocol, including display and rendering. use std::cell::RefCell; use std::collections::btree_map::Entry; use std::collections::BTreeMap as Map; use std::os::unix::io::AsRawFd; use std::rc::Rc; use std::usize; use libc::EINVAL; use data_model::*; use msg_socket::{MsgReceiver, MsgSender}; use resources::Alloc; use sys_util::{error, GuestAddress, GuestMemory}; use gpu_display::*; use gpu_renderer::{ Box3, Context as RendererContext, Error as GpuRendererError, Renderer, Resource as GpuRendererResource, ResourceCreateArgs, }; 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::*; use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse}; struct VirtioGpuResource { width: u32, height: u32, gpu_resource: GpuRendererResource, display_import: Option<(Rc>, u32)>, kvm_slot: Option, } impl VirtioGpuResource { pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> VirtioGpuResource { VirtioGpuResource { width, height, gpu_resource, display_import: None, kvm_slot: None, } } pub fn v2_new( width: u32, height: u32, kvm_slot: u32, gpu_resource: GpuRendererResource, ) -> VirtioGpuResource { VirtioGpuResource { width, height, gpu_resource, display_import: None, kvm_slot: Some(kvm_slot), } } pub 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); } } let (query, dmabuf) = match self.gpu_resource.export() { Ok(export) => (export.0, export.1), Err(GpuRendererError::Virglrenderer(e)) if e == -EINVAL => return None, Err(e) => { error!("failed to query resource: {}", e); return None; } }; match display.borrow_mut().import_dmabuf( dmabuf.as_raw_fd(), query.out_offsets[0], query.out_strides[0], query.out_modifier, self.width, self.height, query.out_fourcc, ) { Ok(import_id) => { self.display_import = Some((display.clone(), import_id)); Some(import_id) } Err(e) => { error!("failed to import dmabuf for display: {}", e); None } } } pub fn write_from_guest_memory( &mut self, x: u32, y: u32, width: u32, height: u32, src_offset: u64, _mem: &GuestMemory, ) { let res = self.gpu_resource.transfer_write( None, 0, 0, 0, Box3::new_2d(x, y, width, height), src_offset, ); if let Err(e) = res { error!( "failed to write to resource (x={} y={} w={} h={}, src_offset={}): {}", x, y, width, height, src_offset, e ); } } pub fn read_to_volatile( &mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice, dst_stride: u32, ) { let res = self.gpu_resource.read_to_volatile( None, 0, dst_stride, 0, /* layer_stride */ Box3::new_2d(x, y, width, height), 0, /* offset */ dst, ); if let Err(e) = res { error!("failed to read from resource: {}", e); } } } /// 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, renderer: Renderer, 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 { /// 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. /// /// All buffer allocations will be done internally by the renderer or the display and buffer /// data is copied as needed. pub fn new( display: GpuDisplay, display_width: u32, display_height: u32, renderer: Renderer, gpu_device_socket: VmMemoryControlRequestSocket, pci_bar: Alloc, ) -> Backend { Backend { display: Rc::new(RefCell::new(display)), display_width, display_height, 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 } 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 .map(|s| display.attach_event_device(s, event_device_id)); self.event_devices.insert(event_device_id, scanout); } /// 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); } } /// 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)] } /// 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 { if id == 0 { return GpuResponse::ErrInvalidResourceId; } match self.resources.entry(id) { Entry::Vacant(slot) => { let gpu_resource = self.renderer.create_resource_2d(id, width, height, format); match gpu_resource { Ok(gpu_resource) => { let virtio_gpu_resource = VirtioGpuResource::new(width, height, gpu_resource); slot.insert(virtio_gpu_resource); GpuResponse::OkNoData } Err(e) => { error!("failed to create renderer resource: {}", e); GpuResponse::ErrUnspec } } } Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, } } /// Removes the guest's reference count for the given resource id. pub 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. 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 } else { GpuResponse::ErrInvalidResourceId } } fn flush_resource_to_surface( &mut self, resource_id: u32, surface_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; 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 } /// Copes the given rectangle of pixels of the given resource's backing memory to the host side /// resource. pub fn transfer_to_resource_2d( &mut self, id: u32, x: u32, y: u32, width: u32, height: u32, src_offset: u64, mem: &GuestMemory, ) -> GpuResponse { match self.resources.get_mut(&id) { Some(res) => { res.write_from_guest_memory(x, y, width, height, src_offset, mem); GpuResponse::OkNoData } None => GpuResponse::ErrInvalidResourceId, } } /// 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( &mut self, id: u32, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>, ) -> GpuResponse { match self.resources.get_mut(&id) { Some(resource) => match resource.gpu_resource.attach_backing(&vecs[..], mem) { Ok(_) => GpuResponse::OkNoData, Err(_) => GpuResponse::ErrUnspec, }, None => GpuResponse::ErrInvalidResourceId, } } /// Detaches any backing memory from the given resource, if there is any. pub fn detach_backing(&mut self, id: u32) -> GpuResponse { match self.resources.get_mut(&id) { Some(resource) => { resource.gpu_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. 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; } } } 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 } } /// 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 } /// Gets the renderer's capset information associated with `index`. pub fn get_capset_info(&self, index: u32) -> GpuResponse { let id = match index { 0 => VIRTIO_GPU_CAPSET_VIRGL, 1 => VIRTIO_GPU_CAPSET_VIRGL2, 2 => VIRTIO_GPU_CAPSET3, _ => return GpuResponse::ErrInvalidParameter, }; let (version, size) = self.renderer.get_cap_set_info(id); GpuResponse::OkCapsetInfo { id, version, size } } /// Gets the capset of `version` associated with `id`. pub 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 { if id == 0 { return GpuResponse::ErrInvalidContextId; } match self.contexts.entry(id) { Entry::Occupied(_) => GpuResponse::ErrInvalidContextId, Entry::Vacant(slot) => match self.renderer.create_context(id) { Ok(ctx) => { slot.insert(ctx); GpuResponse::OkNoData } Err(e) => { error!("failed to create renderer ctx: {}", e); GpuResponse::ErrUnspec } }, } } /// Destorys the renderer context associated with `id`. pub fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse { match self.contexts.remove(&id) { Some(_) => GpuResponse::OkNoData, None => GpuResponse::ErrInvalidContextId, } } /// Attaches the indicated resource to the given context. pub 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), ) { (Some(ctx), Some(res)) => { ctx.attach(&res.gpu_resource); GpuResponse::OkNoData } (None, _) => GpuResponse::ErrInvalidContextId, (_, None) => GpuResponse::ErrInvalidResourceId, } } /// detaches the indicated resource to the given context. pub 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), ) { (Some(ctx), Some(res)) => { ctx.detach(&res.gpu_resource); GpuResponse::OkNoData } (None, _) => GpuResponse::ErrInvalidContextId, (_, None) => GpuResponse::ErrInvalidResourceId, } } /// Creates a 3D resource with the given properties and associated it with the given id. pub 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 { if id == 0 { return GpuResponse::ErrInvalidResourceId; } let create_args = ResourceCreateArgs { handle: id, target, format, bind, width, height, depth, array_size, last_level, nr_samples, flags, }; match self.resources.entry(id) { Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, Entry::Vacant(slot) => { let gpu_resource = self.renderer.create_resource(create_args); match gpu_resource { Ok(gpu_resource) => { let query = match gpu_resource.query() { Ok(query) => query, Err(_) => return GpuResponse::ErrUnspec, }; let response = match query.out_num_fds { 0 => GpuResponse::OkNoData, 1 => { let mut plane_info = Vec::with_capacity(4); for plane_index in 0..4 { plane_info.push(GpuResponsePlaneInfo { stride: query.out_strides[plane_index], offset: query.out_offsets[plane_index], }); } let format_modifier = query.out_modifier; GpuResponse::OkResourcePlaneInfo { format_modifier, plane_info, } } _ => return GpuResponse::ErrUnspec, }; let virtio_gpu_resource = VirtioGpuResource::new(width, height, gpu_resource); slot.insert(virtio_gpu_resource); response } Err(e) => { error!("failed to create renderer resource: {}", e); GpuResponse::ErrUnspec } } } } } /// 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( &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 { let ctx = match ctx_id { 0 => None, id => match self.contexts.get(&id) { None => return GpuResponse::ErrInvalidContextId, ctx => ctx, }, }; match self.resources.get_mut(&res_id) { Some(res) => { let transfer_box = Box3 { x, y, z, w: width, h: height, d: depth, }; let res = res.gpu_resource.transfer_write( ctx, level, stride, layer_stride, transfer_box, offset, ); match res { Ok(_) => GpuResponse::OkNoData, Err(e) => { error!("failed to transfer to host: {}", e); GpuResponse::ErrUnspec } } } None => GpuResponse::ErrInvalidResourceId, } } /// Copes the given rectangle of pixels from the resource to the given resource's backing /// memory. pub 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 { let ctx = match ctx_id { 0 => None, id => match self.contexts.get(&id) { None => return GpuResponse::ErrInvalidContextId, ctx => ctx, }, }; match self.resources.get_mut(&res_id) { Some(res) => { let transfer_box = Box3 { x, y, z, w: width, h: height, d: depth, }; let res = res.gpu_resource.transfer_read( ctx, level, stride, layer_stride, transfer_box, offset, ); match res { Ok(_) => GpuResponse::OkNoData, Err(e) => { error!("failed to transfer from host: {}", e); GpuResponse::ErrUnspec } } } None => GpuResponse::ErrInvalidResourceId, } } /// Submits a command buffer to the given rendering context. pub 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, Err(e) => { error!("failed to submit command buffer: {}", e); GpuResponse::ErrUnspec } }, None => GpuResponse::ErrInvalidContextId, } } 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( &mut self, request_id: u32, request: Vec, mut response: Vec, ) -> GpuResponse { let res = self.renderer.allocation_metadata(&request, &mut response); match res { Ok(_) => { let res_info = AllocationMetadataResponse { request_id, response, }; GpuResponse::OkAllocationMetadata { res_info } } Err(_) => { error!("failed to get metadata"); GpuResponse::ErrUnspec } } } pub 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 { match self.resources.entry(resource_id) { Entry::Vacant(entry) => { let resource = match self.renderer.resource_create_v2( resource_id, guest_memory_type, guest_caching_type, size, mem, &vecs, &args, ) { Ok(resource) => resource, Err(e) => { error!("failed to create resource: {}", e); return GpuResponse::ErrUnspec; } }; match guest_memory_type { VIRTIO_GPU_MEMORY_HOST_COHERENT => { let dma_buf_fd = match resource.export() { Ok(export) => export.1, Err(e) => { error!("failed to export plane fd: {}", e); return GpuResponse::ErrUnspec; } }; let request = VmMemoryRequest::RegisterMemoryAtAddress( self.pci_bar, MaybeOwnedFd::Borrowed(dma_buf_fd.as_raw_fd()), size as usize, pci_addr, ); match self.gpu_device_socket.send(&request) { 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, slot, resource, )); GpuResponse::OkNoData } VmMemoryResponse::Err(e) => { error!("received an error: {}", e); GpuResponse::ErrUnspec } _ => { error!("recieved an unexpected response"); GpuResponse::ErrUnspec } }, Err(e) => { error!("failed to receive data: {}", e); GpuResponse::ErrUnspec } }, Err(e) => { error!("failed to send request: {}", e); GpuResponse::ErrUnspec } } } _ => { entry.insert(VirtioGpuResource::new( self.display_width, self.display_height, resource, )); GpuResponse::OkNoData } } } Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, } } pub 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) => { let request = VmMemoryRequest::UnregisterMemory(kvm_slot); match self.gpu_device_socket.send(&request) { Ok(_resq) => match self.gpu_device_socket.recv() { Ok(response) => match response { VmMemoryResponse::Ok => GpuResponse::OkNoData, VmMemoryResponse::Err(e) => { error!("received an error: {}", e); GpuResponse::ErrUnspec } _ => { error!("recieved an unexpected response"); GpuResponse::ErrUnspec } }, Err(e) => { error!("failed to receive data: {}", e); GpuResponse::ErrUnspec } }, Err(e) => { error!("failed to send request: {}", e); GpuResponse::ErrUnspec } } } None => GpuResponse::OkNoData, }, None => GpuResponse::ErrInvalidResourceId, } } }