blob: dc389925df576e71af27aba358f52429ba70a0cb [file] [log] [blame]
// 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 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::os::unix::io::AsRawFd;
use std::rc::Rc;
use std::sync::Arc;
use std::usize;
use base::{error, warn, Error, ExternalMapping};
use data_model::*;
use msg_socket::{MsgReceiver, MsgSender};
use resources::Alloc;
use sync::Mutex;
use vm_memory::{GuestAddress, GuestMemory};
use gpu_display::*;
use gpu_renderer::{
Box3, Context as RendererContext, Renderer, RendererFlags, Resource as GpuRendererResource,
ResourceCreateArgs,
};
use super::protocol::{
GpuResponse::*, GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE,
VIRTIO_GPU_CAPSET3, VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2,
};
pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource};
use crate::virtio::gpu::{
Backend, VirtioScanoutBlobData, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_RESOURCE_UUID,
VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_F_VULKAN,
};
use crate::virtio::resource_bridge::{PlaneInfo, ResourceInfo, ResourceResponse};
use vm_control::{
MaybeOwnedFd, MemSlot, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse,
};
struct Virtio3DResource {
width: u32,
height: u32,
gpu_resource: GpuRendererResource,
display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>,
slot: Option<MemSlot>,
size: u64,
blob_flags: u32,
scanout_data: Option<VirtioScanoutBlobData>,
}
impl Virtio3DResource {
pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> Virtio3DResource {
Virtio3DResource {
width,
height,
gpu_resource,
display_import: None,
slot: None,
blob_flags: 0,
// The size of the host resource isn't really zero, but it's undefined by
// virtio_gpu_resource_create_3d
size: 0,
scanout_data: None,
}
}
pub fn blob_new(
width: u32,
height: u32,
gpu_resource: GpuRendererResource,
blob_flags: u32,
size: u64,
) -> Virtio3DResource {
Virtio3DResource {
width,
height,
gpu_resource,
display_import: None,
slot: None,
blob_flags,
size,
scanout_data: None,
}
}
fn as_mut(&mut self) -> &mut dyn VirtioResource {
self
}
fn response_from_query(&self) -> VirtioGpuResult {
let query = self.gpu_resource.query()?;
match query.out_num_fds {
0 => Ok(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;
Ok(OkResourcePlaneInfo {
format_modifier,
plane_info,
})
}
_ => Err(ErrUnspec),
}
}
}
impl VirtioResource for Virtio3DResource {
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
if let Some((self_display, import)) = &self.display_import {
if Rc::ptr_eq(self_display, display) {
return Some(*import);
}
}
let dmabuf = self.gpu_resource.export().ok()?;
let query = self.gpu_resource.query().ok()?;
let (width, height, format, stride, offset) = match self.scanout_data {
Some(data) => (
data.width,
data.height,
data.drm_format.into(),
data.strides[0],
data.offsets[0],
),
None => (
self.width,
self.height,
query.out_fourcc,
query.out_strides[0],
query.out_offsets[0],
),
};
match display.borrow_mut().import_dmabuf(
dmabuf.as_raw_fd(),
offset,
stride,
query.out_modifier,
width,
height,
format,
) {
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,
) {
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
);
}
}
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 which supports accelerated rendering.
pub struct Virtio3DBackend {
base: VirtioBackend,
renderer: Renderer,
resources: Map<u32, Virtio3DResource>,
contexts: Map<u32, RendererContext>,
gpu_device_socket: VmMemoryControlRequestSocket,
pci_bar: Alloc,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
}
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.
///
/// 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,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
) -> 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(),
gpu_device_socket,
pci_bar,
map_request,
external_blob,
}
}
}
impl Backend for Virtio3DBackend {
/// Returns the number of capsets provided by the Backend.
fn capsets() -> u32 {
3
}
fn features() -> u64 {
1 << VIRTIO_GPU_F_VIRGL
| 1 << VIRTIO_GPU_F_RESOURCE_UUID
| 1 << VIRTIO_GPU_F_RESOURCE_BLOB
| 1 << VIRTIO_GPU_F_VULKAN
}
/// Returns the underlying Backend.
fn build(
display: GpuDisplay,
display_width: u32,
display_height: u32,
renderer_flags: RendererFlags,
event_devices: Vec<EventDevice>,
gpu_device_socket: VmMemoryControlRequestSocket,
pci_bar: Alloc,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
) -> Option<Box<dyn Backend>> {
let mut renderer_flags = renderer_flags;
if display.is_x() {
// 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.
renderer_flags = RendererFlags::new().use_glx(true);
}
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());
}
}
renderer_flags.use_external_blob(external_blob);
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,
map_request,
external_blob,
);
for event_device in event_devices {
backend_3d
.import_event_device(event_device, 0)
.map_err(|e| error!("failed to import event device {}", e))
.ok()?;
}
Some(Box::new(backend_3d))
}
/// Gets a reference to the display passed into `new`.
fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
&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 an X11 window for example).
fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) -> VirtioGpuResult {
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) -> ResourceResponse {
let resource = match self.resources.get_mut(&id) {
Some(r) => r,
None => return ResourceResponse::Invalid,
};
let q = match resource.gpu_resource.query() {
Ok(query) => query,
Err(_) => return ResourceResponse::Invalid,
};
let file = match resource.gpu_resource.export() {
Ok(file) => file,
Err(_) => return ResourceResponse::Invalid,
};
ResourceResponse::Resource(ResourceInfo {
file,
planes: [
PlaneInfo {
offset: q.out_offsets[0],
stride: q.out_strides[0],
},
PlaneInfo {
offset: q.out_offsets[1],
stride: q.out_strides[1],
},
PlaneInfo {
offset: q.out_offsets[2],
stride: q.out_strides[2],
},
PlaneInfo {
offset: q.out_offsets[3],
stride: q.out_strides[3],
},
],
})
}
/// 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) -> VirtioGpuResult {
// 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.
self.renderer.create_fence(fence_id, ctx_id)?;
Ok(OkNoData)
}
/// 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.
fn create_resource_2d(
&mut self,
id: u32,
width: u32,
height: u32,
format: u32,
) -> VirtioGpuResult {
if id == 0 {
return Err(ErrInvalidResourceId);
}
match self.resources.entry(id) {
Entry::Vacant(slot) => {
let gpu_resource = self
.renderer
.create_resource_2d(id, width, height, format)?;
let virtio_gpu_resource = Virtio3DResource::new(width, height, gpu_resource);
slot.insert(virtio_gpu_resource);
Ok(OkNoData)
}
Entry::Occupied(_) => Err(ErrInvalidResourceId),
}
}
/// Removes the guest's reference count for the given resource id.
fn unref_resource(&mut self, id: u32) -> VirtioGpuResult {
match self.resources.remove(&id) {
Some(_) => Ok(OkNoData),
None => Err(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,
scanout_data: Option<VirtioScanoutBlobData>,
) -> VirtioGpuResult {
if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() {
match self.resources.get_mut(&resource_id) {
Some(resource) => resource.scanout_data = scanout_data,
None => (),
}
self.base.set_scanout(resource_id)
} else {
Err(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,
) -> VirtioGpuResult {
if id == 0 {
return Ok(OkNoData);
}
let resource = self.resources.get_mut(&id).ok_or(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,
) -> VirtioGpuResult {
let resource = self.resources.get_mut(&id).ok_or(ErrInvalidResourceId)?;
resource.write_from_guest_memory(x, y, width, height, src_offset, mem);
Ok(OkNoData)
}
/// 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)>,
) -> VirtioGpuResult {
let resource = self.resources.get_mut(&id).ok_or(ErrInvalidResourceId)?;
resource.gpu_resource.attach_backing(&vecs[..], mem)?;
Ok(OkNoData)
}
/// Detaches any backing memory from the given resource, if there is any.
fn detach_backing(&mut self, id: u32) -> VirtioGpuResult {
let resource = self.resources.get_mut(&id).ok_or(ErrInvalidResourceId)?;
resource.gpu_resource.detach_backing();
Ok(OkNoData)
}
/// 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) -> VirtioGpuResult {
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) -> VirtioGpuResult {
self.base.move_cursor(x, y)
}
/// Returns a uuid for the resource.
fn resource_assign_uuid(&mut self, id: u32) -> VirtioGpuResult {
match self.resources.entry(id) {
Entry::Vacant(_) => Err(ErrInvalidResourceId),
Entry::Occupied(_) => {
// TODO(stevensd): use real uuids once the virtio wayland protocol is updated to
// handle more than 32 bits. For now, the virtwl driver knows that the uuid is
// actually just the resource id.
let mut uuid: [u8; 16] = [0; 16];
for (idx, byte) in id.to_be_bytes().iter().enumerate() {
uuid[12 + idx] = *byte;
}
Ok(OkResourceUuid { uuid })
}
}
}
/// Gets the renderer's capset information associated with `index`.
fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
let id = match index {
0 => VIRTIO_GPU_CAPSET_VIRGL,
1 => VIRTIO_GPU_CAPSET_VIRGL2,
2 => VIRTIO_GPU_CAPSET3,
_ => return Err(ErrInvalidParameter),
};
let (version, size) = self.renderer.get_cap_set_info(id);
Ok(OkCapsetInfo { id, version, size })
}
/// Gets the capset of `version` associated with `id`.
fn get_capset(&self, id: u32, version: u32) -> VirtioGpuResult {
Ok(OkCapset(self.renderer.get_cap_set(id, version)))
}
/// Creates a fresh renderer context with the given `id`.
fn create_renderer_context(&mut self, id: u32) -> VirtioGpuResult {
if id == 0 {
return Err(ErrInvalidContextId);
}
match self.contexts.entry(id) {
Entry::Occupied(_) => Err(ErrInvalidContextId),
Entry::Vacant(slot) => {
let ctx = self.renderer.create_context(id)?;
slot.insert(ctx);
Ok(OkNoData)
}
}
}
/// Destorys the renderer context associated with `id`.
fn destroy_renderer_context(&mut self, id: u32) -> VirtioGpuResult {
match self.contexts.remove(&id) {
Some(_) => Ok(OkNoData),
None => Err(ErrInvalidContextId),
}
}
/// Attaches the indicated resource to the given context.
fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> VirtioGpuResult {
match (
self.contexts.get_mut(&ctx_id),
self.resources.get_mut(&res_id),
) {
(Some(ctx), Some(res)) => {
ctx.attach(&res.gpu_resource);
Ok(OkNoData)
}
(None, _) => Err(ErrInvalidContextId),
(_, None) => Err(ErrInvalidResourceId),
}
}
/// detaches the indicated resource to the given context.
fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> VirtioGpuResult {
match (
self.contexts.get_mut(&ctx_id),
self.resources.get_mut(&res_id),
) {
(Some(ctx), Some(res)) => {
ctx.detach(&res.gpu_resource);
Ok(OkNoData)
}
(None, _) => Err(ErrInvalidContextId),
(_, None) => Err(ErrInvalidResourceId),
}
}
/// Creates a 3D resource with the given properties and associated 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,
) -> VirtioGpuResult {
if id == 0 {
return Err(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(_) => Err(ErrInvalidResourceId),
Entry::Vacant(slot) => {
let gpu_resource = self.renderer.create_resource(create_args)?;
let virtio_gpu_resource = Virtio3DResource::new(width, height, gpu_resource);
let response = virtio_gpu_resource.response_from_query()?;
slot.insert(virtio_gpu_resource);
Ok(response)
}
}
}
/// 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,
) -> VirtioGpuResult {
let ctx = match ctx_id {
0 => None,
id => Some(self.contexts.get(&id).ok_or(ErrInvalidContextId)?),
};
let resource = self
.resources
.get_mut(&res_id)
.ok_or(ErrInvalidResourceId)?;
let transfer_box = Box3 {
x,
y,
z,
w: width,
h: height,
d: depth,
};
resource.gpu_resource.transfer_write(
ctx,
level,
stride,
layer_stride,
transfer_box,
offset,
)?;
Ok(OkNoData)
}
/// 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,
) -> VirtioGpuResult {
let ctx = match ctx_id {
0 => None,
id => Some(self.contexts.get(&id).ok_or(ErrInvalidContextId)?),
};
let resource = self
.resources
.get_mut(&res_id)
.ok_or(ErrInvalidResourceId)?;
let transfer_box = Box3 {
x,
y,
z,
w: width,
h: height,
d: depth,
};
resource.gpu_resource.transfer_read(
ctx,
level,
stride,
layer_stride,
transfer_box,
offset,
)?;
Ok(OkNoData)
}
/// Submits a command buffer to the given rendering context.
fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> VirtioGpuResult {
let ctx = self.contexts.get_mut(&ctx_id).ok_or(ErrInvalidContextId)?;
ctx.submit(&mut commands[..])?;
Ok(OkNoData)
}
fn resource_create_blob(
&mut self,
resource_id: u32,
ctx_id: u32,
blob_mem: u32,
blob_flags: u32,
blob_id: u64,
size: u64,
vecs: Vec<(GuestAddress, usize)>,
mem: &GuestMemory,
) -> VirtioGpuResult {
match self.resources.entry(resource_id) {
Entry::Vacant(entry) => {
let resource = self.renderer.resource_create_blob(
resource_id,
ctx_id,
blob_mem,
blob_flags,
blob_id,
size,
&vecs,
mem,
)?;
let virtio_gpu_resource = Virtio3DResource::blob_new(
self.base.display_width,
self.base.display_height,
resource,
blob_flags,
size,
);
let response = virtio_gpu_resource.response_from_query()?;
entry.insert(virtio_gpu_resource);
Ok(response)
}
Entry::Occupied(_) => Err(ErrInvalidResourceId),
}
}
fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult {
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(ErrInvalidResourceId)?;
if resource.blob_flags & VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE == 0 {
error!("resource not mappable");
return Err(ErrUnspec);
}
let map_info = resource.gpu_resource.map_info()?;
let export = resource.gpu_resource.export();
let request = match export {
Ok(ref export) => VmMemoryRequest::RegisterFdAtPciBarOffset(
self.pci_bar,
MaybeOwnedFd::Borrowed(export.as_raw_fd()),
resource.size as usize,
offset,
),
Err(_) => {
if self.external_blob {
return Err(ErrUnspec);
}
let mapping = resource.gpu_resource.map()?;
{
let mut map_req = self.map_request.lock();
if map_req.is_some() {
return Err(ErrUnspec);
}
*map_req = Some(mapping);
}
VmMemoryRequest::RegisterHostPointerAtPciBarOffset(self.pci_bar, offset)
}
};
self.gpu_device_socket.send(&request)?;
let response = self.gpu_device_socket.recv()?;
match response {
VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
resource.slot = Some(slot);
Ok(OkMapInfo { map_info })
}
VmMemoryResponse::Err(e) => Err(ErrSys(e)),
_ => Err(ErrUnspec),
}
}
fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult {
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(ErrInvalidResourceId)?;
let slot = resource.slot.ok_or(ErrUnspec)?;
let request = VmMemoryRequest::UnregisterMemory(slot);
self.gpu_device_socket.send(&request)?;
let response = self.gpu_device_socket.recv()?;
match response {
VmMemoryResponse::Ok => {
resource.slot = None;
Ok(OkNoData)
}
VmMemoryResponse::Err(e) => Err(ErrSys(e)),
_ => Err(ErrUnspec),
}
}
}