diff options
author | Zach Reizner <zachr@google.com> | 2017-09-13 19:15:43 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-07-20 05:30:54 -0700 |
commit | 3a8100adc75d805300f23f7cff25e3e1d6b40b33 (patch) | |
tree | 08efcf2c527b429836d42d431ec84e7d096a94b0 /devices/src/virtio/gpu/backend.rs | |
parent | f40bb190ece97c908f8dba2efc7c1aceb4fc0e0b (diff) | |
download | crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.gz crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.bz2 crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.lz crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.xz crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.zst crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.zip |
gpu: implement virtio-gpu
Basic 2D and 3D support is there. The drm_cursor_test and null_platform_test in drm-tests should run to completion. The extra device is hidden behind both a build time feature called 'gpu' and the device is only added to a VM if the '--gpu' flag is given. TEST=build with --features=gpu; drm_cursor_test && null_platform_test BUG=chromium:837073 Change-Id: Ic91acaaebbee395599d7e1ba41c24c9ed2d84169 Reviewed-on: https://chromium-review.googlesource.com/1036862 Commit-Ready: Zach Reizner <zachr@chromium.org> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/virtio/gpu/backend.rs')
-rw-r--r-- | devices/src/virtio/gpu/backend.rs | 817 |
1 files changed, 817 insertions, 0 deletions
diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs new file mode 100644 index 0000000..50fd91c --- /dev/null +++ b/devices/src/virtio/gpu/backend.rs @@ -0,0 +1,817 @@ +// 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 data_model::*; + +use sys_util::{GuestAddress, GuestMemory}; + +use super::gpu_buffer::{Device, Buffer, Format, Flags}; +use super::gpu_display::*; +use super::gpu_renderer::{Box3, Renderer, Context as RendererContext, + Resource as GpuRendererResource, ResourceCreateArgs}; + +use super::protocol::GpuResponse; + +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<RefCell<GpuDisplay>>) -> Option<u32> { + 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, + 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, + _mem: &GuestMemory) { + let res = self.transfer_write(None, + 0, + 0, + 0, + Box3 { + x, + y, + z: 0, + w: width, + h: height, + d: 0, + }, + 0); + if let Err(e) = res { + error!("failed to write to resource (x={} y={} w={} h={}): {}", + x, + y, + width, + height, + 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<RefCell<GpuDisplay>>, u32)>, + backing: Vec<(GuestAddress, usize)>, + buffer: Buffer, +} + +impl From<Buffer> for BackedBuffer { + fn from(buffer: Buffer) -> BackedBuffer { + BackedBuffer { + display_import: None, + backing: Vec::new(), + buffer, + } + } +} + +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; + } + + fn detach_guest_backing(&mut self) { + self.backing.clear() + } + + fn buffer(&self) -> Option<&Buffer> { + Some(&self.buffer) + } + + fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> { + 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, + mem: &GuestMemory) { + let res = self.buffer + .write_from_sg(x, + y, + width, + height, + 0, + self.backing + .iter() + .map(|&(addr, len)| { + mem.get_slice(addr.offset(), len as u64).unwrap() + })); + 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<RefCell<GpuDisplay>>, + device: Device, + renderer: Renderer, + resources: Map<u32, Box<VirglResource>>, + contexts: Map<u32, RendererContext>, + scanout_surface: Option<u32>, + cursor_surface: Option<u32>, + 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<RefCell<GpuDisplay>> { + &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); + } + } + + return 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, + mem: &GuestMemory) + -> GpuResponse { + match self.resources.get_mut(&id) { + Some(res) => { + res.write_from_guest_memory(x, y, width, height, 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 { + match index { + 0 => { + let id = 1; // VIRTIO_GPU_CAPSET_VIRGL + let (version, size) = self.renderer.get_cap_set_info(id); + GpuResponse::OkCapsetInfo { id, version, size } + } + _ => GpuResponse::ErrInvalidParameter, + } + } + + /// 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, + } + } + + /// 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; + } + match self.resources.entry(id) { + Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, + Entry::Vacant(slot) => { + let res = self.renderer + .create_resource(ResourceCreateArgs { + handle: id, + target, + format, + bind, + width, + height, + depth, + array_size, + last_level, + nr_samples, + flags, + }); + 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, + } + } +} |