// 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 data_model::*; use sys_util::{GuestAddress, GuestMemory}; use super::gpu_buffer::{Buffer, Device, Flags, Format}; use super::gpu_display::*; use super::gpu_renderer::{ format_fourcc as renderer_fourcc, Box3, Context as RendererContext, Image as RendererImage, Renderer, Resource as GpuRendererResource, ResourceCreateArgs, }; use super::protocol::GpuResponse; use super::protocol::{VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2}; const DEFAULT_WIDTH: u32 = 1280; const DEFAULT_HEIGHT: u32 = 1024; /// Trait for virtio-gpu resources allocated by the guest. trait VirglResource { /// The width in pixels of this resource. fn width(&self) -> u32; /// The height in pixels of this resource. fn height(&self) -> u32; /// Associates the backing for this resource with the given guest memory. fn attach_guest_backing(&mut self, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>); /// Removes associated memory for this resource previously made with `attach_guest_backing`. fn detach_guest_backing(&mut self); /// Returns the GPU `Buffer` for this resource, if it has one. fn buffer(&self) -> Option<&Buffer> { None } /// Returns the renderer's concrete `GpuRendererResource` for this resource, if it has one. fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> { None } /// Returns an import ID for this resource onto the given display, if successful. fn import_to_display(&mut self, _display: &Rc>) -> Option { None } /// Copies the given rectangle of pixels from guest memory, using the backing specified from a /// call to `attach_guest_backing`. fn write_from_guest_memory( &mut self, x: u32, y: u32, width: u32, height: u32, src_offset: u64, mem: &GuestMemory, ); /// Reads from the given rectangle of pixels in the resource to the `dst` slice of memory. fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice); } impl VirglResource for GpuRendererResource { fn width(&self) -> u32 { match self.get_info() { Ok(info) => info.width, Err(_) => 0, } } fn height(&self) -> u32 { match self.get_info() { Ok(info) => info.height, Err(_) => 0, } } fn attach_guest_backing(&mut self, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>) { if let Err(e) = self.attach_backing(&vecs[..], mem) { error!("failed to attach backing to resource: {}", e); } } fn detach_guest_backing(&mut self) { self.detach_backing(); } fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> { Some(self) } fn write_from_guest_memory( &mut self, x: u32, y: u32, width: u32, height: u32, src_offset: u64, _mem: &GuestMemory, ) { let res = self.transfer_write( None, 0, 0, 0, Box3 { x, y, z: 0, w: width, h: height, d: 0, }, 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 ); } } fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) { let res = GpuRendererResource::read_to_volatile( self, None, 0, 0, 0, Box3 { x, y, z: 0, w: width, h: height, d: 0, }, 0, dst, ); if let Err(e) = res { error!("failed to read from resource: {}", e); } } } /// A buffer backed with a `gpu_buffer::Buffer`. struct BackedBuffer { display_import: Option<(Rc>, u32)>, backing: Vec<(GuestAddress, usize)>, buffer: Buffer, gpu_renderer_resource: Option, _image: Option, } impl BackedBuffer { fn new_renderer_registered( buffer: Buffer, gpu_renderer_resource: GpuRendererResource, image: RendererImage, ) -> BackedBuffer { BackedBuffer { display_import: None, backing: Vec::new(), buffer, gpu_renderer_resource: Some(gpu_renderer_resource), _image: Some(image), } } } impl From for BackedBuffer { fn from(buffer: Buffer) -> BackedBuffer { BackedBuffer { display_import: None, backing: Vec::new(), buffer, gpu_renderer_resource: None, _image: None, } } } impl VirglResource for BackedBuffer { fn width(&self) -> u32 { self.buffer.width() } fn height(&self) -> u32 { self.buffer.height() } fn attach_guest_backing(&mut self, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>) { self.backing = vecs.clone(); if let Some(ref mut resource) = self.gpu_renderer_resource { if let Err(e) = resource.attach_backing(&vecs[..], mem) { error!("failed to attach backing to BackBuffer resource: {}", e); } } } fn detach_guest_backing(&mut self) { if let Some(ref mut resource) = self.gpu_renderer_resource { resource.detach_backing(); } self.backing.clear(); } fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> { self.gpu_renderer_resource.as_mut() } fn buffer(&self) -> Option<&Buffer> { Some(&self.buffer) } fn import_to_display(&mut self, display: &Rc>) -> Option { if let Some((ref self_display, import)) = self.display_import { if Rc::ptr_eq(&self_display, display) { return Some(import); } } let dmabuf = match self.buffer.export_plane_fd(0) { Ok(dmabuf) => dmabuf, Err(e) => { error!("failed to get dmabuf for scanout: {}", e); return None; } }; match display.borrow_mut().import_dmabuf( dmabuf.as_raw_fd(), 0, /* offset */ self.buffer.stride(), self.buffer.format_modifier(), self.buffer.width(), self.buffer.height(), self.buffer.format().into(), ) { 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 } } } fn write_from_guest_memory( &mut self, x: u32, y: u32, width: u32, height: u32, src_offset: u64, mem: &GuestMemory, ) { if src_offset >= usize::MAX as u64 { error!( "failed to write to resource with given offset: {}", src_offset ); return; } let res = self.buffer.write_from_sg( x, y, width, height, 0, // plane src_offset as usize, self.backing .iter() .map(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).unwrap_or_default()), ); if let Err(e) = res { error!("failed to write to resource from guest memory: {:?}", e) } } fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) { if let Err(e) = self.buffer.read_to_volatile(x, y, width, height, 0, dst) { error!("failed to copy 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>, device: Device, renderer: Renderer, resources: Map>, contexts: Map, scanout_surface: Option, cursor_surface: Option, scanout_resource: u32, cursor_resource: u32, } impl Backend { /// Creates a new backend for virtio-gpu that realizes all commands using the given `device` for /// allocating buffers, `display` for showing the results, and `renderer` for submitting /// rendering commands. pub fn new(device: Device, display: GpuDisplay, renderer: Renderer) -> Backend { Backend { display: Rc::new(RefCell::new(display)), device, renderer, resources: Default::default(), contexts: Default::default(), scanout_surface: None, cursor_surface: None, scanout_resource: 0, cursor_resource: 0, } } /// Gets a reference to the display passed into `new`. pub fn display(&self) -> &Rc> { &self.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) } /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples. pub fn display_info(&self) -> &[(u32, u32)] { &[(DEFAULT_WIDTH, DEFAULT_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, fourcc: u32, ) -> GpuResponse { if id == 0 { return GpuResponse::ErrInvalidResourceId; } match self.resources.entry(id) { Entry::Vacant(slot) => { let res = self.device.create_buffer( width, height, Format::from(fourcc), Flags::empty().use_scanout(true).use_linear(true), ); match res { Ok(res) => { slot.insert(Box::from(BackedBuffer::from(res))); GpuResponse::OkNoData } Err(_) => { error!("failed to create renderer resource {}", fourcc); 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, id: u32) -> GpuResponse { let mut display = self.display.borrow_mut(); if 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(&id).is_some() { self.scanout_resource = id; if self.scanout_surface.is_none() { match display.create_surface(None, DEFAULT_WIDTH, DEFAULT_HEIGHT) { Ok(surface) => 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 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_memory(surface_id) { Some(fb) => fb, None => { error!("failed to access framebuffer for surface {}", surface_id); return GpuResponse::ErrUnspec; } }; resource.read_to_volatile(x, y, width, height, fb); 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) => { resource.attach_guest_backing(mem, vecs); GpuResponse::OkNoData } 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.detach_guest_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(buffer) = resource.buffer() { if let Some(fb) = self.display.borrow_mut().framebuffer_memory(cursor_surface) { if let Err(e) = buffer.read_to_volatile(0, 0, buffer.width(), buffer.height(), 0, fb) { error!("failed to copy resource to cursor: {:?}", e); return GpuResponse::ErrInvalidParameter; } } 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 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, _ => 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) .and_then(|res| res.gpu_renderer_resource()), ) { (Some(ctx), Some(res)) => { ctx.attach(res); 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) .and_then(|res| res.gpu_renderer_resource()), ) { (Some(ctx), Some(res)) => { ctx.detach(res); GpuResponse::OkNoData } (None, _) => GpuResponse::ErrInvalidContextId, (_, None) => GpuResponse::ErrInvalidResourceId, } } pub fn validate_args_as_fourcc(args: ResourceCreateArgs) -> Option { if args.depth == 1 && args.array_size == 1 && args.last_level == 0 && args.nr_samples == 0 { renderer_fourcc(args.format) } else { None } } /// 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) => match Backend::validate_args_as_fourcc(create_args) { Some(fourcc) => { let buffer = match self.device.create_buffer( width, height, Format::from(fourcc), Flags::empty().use_scanout(true).use_linear(true), ) { Ok(buffer) => buffer, Err(e) => { error!("failed to create buffer for 3d resource {}: {}", format, e); return GpuResponse::ErrUnspec; } }; let dma_buf_fd = match buffer.export_plane_fd(0) { Ok(dma_buf_fd) => dma_buf_fd, Err(e) => { error!("failed to export plane fd: {}", e); return GpuResponse::ErrUnspec; } }; let image = match self.renderer.image_from_dmabuf( fourcc, width, height, dma_buf_fd.as_raw_fd(), buffer.plane_offset(0), buffer.plane_stride(0), ) { Ok(image) => image, Err(e) => { error!("failed to create egl image: {}", e); return GpuResponse::ErrUnspec; } }; let res = self.renderer.import_resource(create_args, &image); match res { Ok(res) => { let mut backed = BackedBuffer::new_renderer_registered(buffer, res, image); slot.insert(Box::new(backed)); GpuResponse::OkNoData } Err(e) => { error!("failed to import renderer resource: {}", e); GpuResponse::ErrUnspec } } } None => { let res = self.renderer.create_resource(create_args); match res { Ok(res) => { slot.insert(Box::new(res)); GpuResponse::OkNoData } 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) => match res.gpu_renderer_resource() { Some(res) => { let transfer_box = Box3 { x, y, z, w: width, h: height, d: depth, }; let res = res.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, }, 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) => match res.gpu_renderer_resource() { Some(res) => { let transfer_box = Box3 { x, y, z, w: width, h: height, d: depth, }; let res = res.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, }, 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(); } }