blob: ec75baaf76b8ab0bf1ff9316e20bb4f0b7123df1 [file] [log] [blame]
// 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 std::result::Result;
use std::sync::Arc;
use crate::virtio::gpu::GpuDisplayParameters;
use crate::virtio::resource_bridge::{BufferInfo, PlaneInfo, ResourceInfo, ResourceResponse};
use base::{error, ExternalMapping, SafeDescriptor, Tube};
use data_model::VolatileSlice;
use gpu_display::*;
use rutabaga_gfx::{
ResourceCreate3D, ResourceCreateBlob, Rutabaga, RutabagaBuilder, RutabagaFence,
RutabagaFenceHandler, RutabagaIovec, Transfer3D,
};
use libc::c_void;
use resources::Alloc;
use super::protocol::{
GpuResponse::{self, *},
GpuResponsePlaneInfo, VirtioGpuResult, VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE,
VIRTIO_GPU_BLOB_MEM_HOST3D,
};
use super::udmabuf::UdmabufDriver;
use super::VirtioScanoutBlobData;
use sync::Mutex;
use vm_memory::{GuestAddress, GuestMemory};
use vm_control::{MemSlot, VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
struct VirtioGpuResource {
resource_id: u32,
width: u32,
height: u32,
size: u64,
slot: Option<MemSlot>,
scanout_data: Option<VirtioScanoutBlobData>,
display_import: Option<u32>,
}
impl VirtioGpuResource {
/// Creates a new VirtioGpuResource with the given metadata. Width and height are used by the
/// display, while size is useful for hypervisor mapping.
pub fn new(resource_id: u32, width: u32, height: u32, size: u64) -> VirtioGpuResource {
VirtioGpuResource {
resource_id,
width,
height,
size,
slot: None,
scanout_data: None,
display_import: None,
}
}
}
struct VirtioGpuScanout {
width: u32,
height: u32,
surface_id: Option<u32>,
resource_id: Option<NonZeroU32>,
scanout_type: SurfaceType,
// If this scanout is a primary scanout, the scanout id.
scanout_id: Option<u32>,
// If this scanout is a cursor scanout, the scanout that this is cursor is overlayed onto.
parent_surface_id: Option<u32>,
}
impl VirtioGpuScanout {
fn new(width: u32, height: u32, scanout_id: u32) -> VirtioGpuScanout {
VirtioGpuScanout {
width,
height,
scanout_type: SurfaceType::Scanout,
scanout_id: Some(scanout_id),
surface_id: None,
resource_id: None,
parent_surface_id: None,
}
}
fn new_cursor() -> VirtioGpuScanout {
// Per virtio spec: "The mouse cursor image is a normal resource, except that it must be
// 64x64 in size."
VirtioGpuScanout {
width: 64,
height: 64,
scanout_type: SurfaceType::Cursor,
scanout_id: None,
surface_id: None,
resource_id: None,
parent_surface_id: None,
}
}
fn create_surface(
&mut self,
display: &Rc<RefCell<GpuDisplay>>,
new_parent_surface_id: Option<u32>,
) -> VirtioGpuResult {
let mut need_to_create = false;
if self.surface_id.is_none() {
need_to_create = true;
}
if self.parent_surface_id != new_parent_surface_id {
self.parent_surface_id = new_parent_surface_id;
need_to_create = true;
}
if !need_to_create {
return Ok(OkNoData);
}
self.release_surface(display);
let mut display = display.borrow_mut();
let surface_id = display.create_surface(
self.parent_surface_id,
self.width,
self.height,
self.scanout_type,
)?;
if let Some(scanout_id) = self.scanout_id {
display.set_scanout_id(surface_id, scanout_id)?;
}
self.surface_id = Some(surface_id);
Ok(OkNoData)
}
fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
if let Some(surface_id) = self.surface_id {
display.borrow_mut().release_surface(surface_id);
}
self.surface_id = None;
}
fn set_position(&self, display: &Rc<RefCell<GpuDisplay>>, x: u32, y: u32) -> VirtioGpuResult {
if let Some(surface_id) = self.surface_id {
display.borrow_mut().set_position(surface_id, x, y)?;
}
Ok(OkNoData)
}
fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
if let Some(surface_id) = self.surface_id {
display.borrow_mut().commit(surface_id)?;
}
Ok(OkNoData)
}
fn flush(
&mut self,
display: &Rc<RefCell<GpuDisplay>>,
resource: &mut VirtioGpuResource,
rutabaga: &mut Rutabaga,
) -> VirtioGpuResult {
let surface_id = match self.surface_id {
Some(id) => id,
_ => return Ok(OkNoData),
};
if let Some(import_id) =
VirtioGpuScanout::import_resource_to_display(display, resource, rutabaga)
{
display.borrow_mut().flip_to(surface_id, import_id)?;
return Ok(OkNoData);
}
// Import failed, fall back to a copy.
let mut display = display.borrow_mut();
// Prevent overwriting a buffer that is currently being used by the compositor.
if display.next_buffer_in_use(surface_id) {
return Ok(OkNoData);
}
let fb = display
.framebuffer_region(surface_id, 0, 0, self.width, self.height)
.ok_or(ErrUnspec)?;
let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height);
transfer.stride = fb.stride();
rutabaga.transfer_read(
0,
resource.resource_id,
transfer,
Some(fb.as_volatile_slice()),
)?;
display.flip(surface_id);
Ok(OkNoData)
}
fn import_resource_to_display(
display: &Rc<RefCell<GpuDisplay>>,
resource: &mut VirtioGpuResource,
rutabaga: &mut Rutabaga,
) -> Option<u32> {
if let Some(import_id) = resource.display_import {
return Some(import_id);
}
let dmabuf = rutabaga.export_blob(resource.resource_id).ok()?;
let query = rutabaga.query(resource.resource_id).ok()?;
let (width, height, format, stride, offset) = match resource.scanout_data {
Some(data) => (
data.width,
data.height,
data.drm_format.into(),
data.strides[0],
data.offsets[0],
),
None => (
resource.width,
resource.height,
query.drm_fourcc,
query.strides[0],
query.offsets[0],
),
};
let import_id = display
.borrow_mut()
.import_memory(
&dmabuf.os_handle,
offset,
stride,
query.modifier,
width,
height,
format,
)
.ok()?;
resource.display_import = Some(import_id);
Some(import_id)
}
}
/// Handles functionality related to displays, input events and hypervisor memory management.
pub struct VirtioGpu {
display: Rc<RefCell<GpuDisplay>>,
scanouts: Vec<VirtioGpuScanout>,
cursor_scanout: VirtioGpuScanout,
// Maps event devices to scanout number.
event_devices: Map<u32, u32>,
gpu_device_tube: Tube,
pci_bar: Alloc,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
rutabaga: Rutabaga,
resources: Map<u32, VirtioGpuResource>,
external_blob: bool,
udmabuf_driver: Option<UdmabufDriver>,
}
fn sglist_to_rutabaga_iovecs(
vecs: &[(GuestAddress, usize)],
mem: &GuestMemory,
) -> Result<Vec<RutabagaIovec>, ()> {
if vecs
.iter()
.any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
{
return Err(());
}
let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
for &(addr, len) in vecs {
let slice = mem.get_slice_at_addr(addr, len).unwrap();
rutabaga_iovecs.push(RutabagaIovec {
base: slice.as_mut_ptr() as *mut c_void,
len,
});
}
Ok(rutabaga_iovecs)
}
impl VirtioGpu {
/// Creates a new instance of the VirtioGpu state tracker.
pub fn new(
display: GpuDisplay,
display_params: Vec<GpuDisplayParameters>,
rutabaga_builder: RutabagaBuilder,
event_devices: Vec<EventDevice>,
gpu_device_tube: Tube,
pci_bar: Alloc,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
udmabuf: bool,
fence_handler: RutabagaFenceHandler,
render_server_fd: Option<SafeDescriptor>,
) -> Option<VirtioGpu> {
let rutabaga = rutabaga_builder
.build(fence_handler, render_server_fd)
.map_err(|e| error!("failed to build rutabaga {}", e))
.ok()?;
let mut udmabuf_driver = None;
if udmabuf {
udmabuf_driver = Some(
UdmabufDriver::new()
.map_err(|e| error!("failed to initialize udmabuf: {}", e))
.ok()?,
);
}
let scanouts = display_params
.iter()
.enumerate()
.map(|(display_index, &display_param)| {
VirtioGpuScanout::new(
display_param.width,
display_param.height,
display_index as u32,
)
})
.collect::<Vec<_>>();
let cursor_scanout = VirtioGpuScanout::new_cursor();
let mut virtio_gpu = VirtioGpu {
display: Rc::new(RefCell::new(display)),
scanouts,
cursor_scanout,
event_devices: Default::default(),
gpu_device_tube,
pci_bar,
map_request,
rutabaga,
resources: Default::default(),
external_blob,
udmabuf_driver,
};
for event_device in event_devices {
virtio_gpu
.import_event_device(event_device, 0)
.map_err(|e| error!("failed to import event device {}", e))
.ok()?;
}
Some(virtio_gpu)
}
/// Imports the event device
pub fn import_event_device(
&mut self,
event_device: EventDevice,
scanout_id: u32,
) -> VirtioGpuResult {
let mut display = self.display.borrow_mut();
let event_device_id = display.import_event_device(event_device)?;
self.event_devices.insert(event_device_id, scanout_id);
Ok(OkNoData)
}
/// Gets a reference to the display passed into `new`.
pub fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
&self.display
}
/// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
pub fn display_info(&self) -> Vec<(u32, u32)> {
self.scanouts
.iter()
.map(|scanout| (scanout.width, scanout.height))
.collect::<Vec<_>>()
}
/// Processes the internal `display` events and returns `true` if any display was closed.
pub fn process_display(&mut self) -> bool {
let mut display = self.display.borrow_mut();
let result = display.dispatch_events();
match result {
Ok(_) => (),
Err(e) => error!("failed to dispatch events: {}", e),
}
for scanout in &self.scanouts {
let close_requested = scanout
.surface_id
.map(|surface_id| display.close_requested(surface_id))
.unwrap_or(false);
if close_requested {
return true;
}
}
false
}
/// 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,
scanout_data: Option<VirtioScanoutBlobData>,
) -> VirtioGpuResult {
self.update_scanout_resource(SurfaceType::Scanout, scanout_id, scanout_data, resource_id)
}
/// If the resource is the scanout resource, flush it to the display.
pub fn flush_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
if resource_id == 0 {
return Ok(OkNoData);
}
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(ErrInvalidResourceId)?;
// `resource_id` has already been verified to be non-zero
let resource_id = match NonZeroU32::new(resource_id) {
Some(id) => Some(id),
None => return Ok(OkNoData),
};
for scanout in &mut self.scanouts {
if scanout.resource_id == resource_id {
scanout.flush(&self.display, resource, &mut self.rutabaga)?;
}
}
if self.cursor_scanout.resource_id == resource_id {
self.cursor_scanout
.flush(&self.display, resource, &mut self.rutabaga)?;
}
Ok(OkNoData)
}
/// Updates the cursor's memory to the given resource_id, and sets its position to the given
/// coordinates.
pub fn update_cursor(
&mut self,
resource_id: u32,
scanout_id: u32,
x: u32,
y: u32,
) -> VirtioGpuResult {
self.update_scanout_resource(SurfaceType::Cursor, scanout_id, None, resource_id)?;
self.cursor_scanout.set_position(&self.display, x, y)?;
self.flush_resource(resource_id)
}
/// Moves the cursor's position to the given coordinates.
pub fn move_cursor(&mut self, _scanout_id: u32, x: u32, y: u32) -> VirtioGpuResult {
self.cursor_scanout.set_position(&self.display, x, y)?;
self.cursor_scanout.commit(&self.display)?;
Ok(OkNoData)
}
/// Returns a uuid for the resource.
pub fn resource_assign_uuid(&self, resource_id: u32) -> VirtioGpuResult {
if !self.resources.contains_key(&resource_id) {
return Err(ErrInvalidResourceId);
}
// 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 resource_id.to_be_bytes().iter().enumerate() {
uuid[12 + idx] = *byte;
}
Ok(OkResourceUuid { uuid })
}
/// If supported, export the resource with the given `resource_id` to a file.
pub fn export_resource(&mut self, resource_id: u32) -> ResourceResponse {
let file = match self.rutabaga.export_blob(resource_id) {
Ok(handle) => handle.os_handle.into(),
Err(_) => return ResourceResponse::Invalid,
};
let q = match self.rutabaga.query(resource_id) {
Ok(query) => query,
Err(_) => return ResourceResponse::Invalid,
};
ResourceResponse::Resource(ResourceInfo::Buffer(BufferInfo {
file,
planes: [
PlaneInfo {
offset: q.offsets[0],
stride: q.strides[0],
},
PlaneInfo {
offset: q.offsets[1],
stride: q.strides[1],
},
PlaneInfo {
offset: q.offsets[2],
stride: q.strides[2],
},
PlaneInfo {
offset: q.offsets[3],
stride: q.strides[3],
},
],
modifier: q.modifier,
}))
}
/// If supported, export the fence with the given `fence_id` to a file.
pub fn export_fence(&self, fence_id: u32) -> ResourceResponse {
match self.rutabaga.export_fence(fence_id) {
Ok(handle) => ResourceResponse::Resource(ResourceInfo::Fence {
file: handle.os_handle.into(),
}),
Err(_) => ResourceResponse::Invalid,
}
}
/// Gets rutabaga's capset information associated with `index`.
pub fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
let (capset_id, version, size) = self.rutabaga.get_capset_info(index)?;
Ok(OkCapsetInfo {
capset_id,
version,
size,
})
}
/// Gets a capset from rutabaga.
pub fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
let capset = self.rutabaga.get_capset(capset_id, version)?;
Ok(OkCapset(capset))
}
/// Forces rutabaga to use it's default context.
pub fn force_ctx_0(&self) {
self.rutabaga.force_ctx_0()
}
/// Creates a fence with the RutabagaFence that can be used to determine when the previous
/// command completed.
pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
self.rutabaga.create_fence(rutabaga_fence)?;
Ok(OkNoData)
}
/// Returns an array of RutabagaFence, describing completed fences.
pub fn fence_poll(&mut self) -> Vec<RutabagaFence> {
self.rutabaga.poll()
}
/// Creates a 3D resource with the given properties and resource_id.
pub fn resource_create_3d(
&mut self,
resource_id: u32,
resource_create_3d: ResourceCreate3D,
) -> VirtioGpuResult {
self.rutabaga
.resource_create_3d(resource_id, resource_create_3d)?;
let resource = VirtioGpuResource::new(
resource_id,
resource_create_3d.width,
resource_create_3d.height,
0,
);
// Rely on rutabaga to check for duplicate resource ids.
self.resources.insert(resource_id, resource);
Ok(self.result_from_query(resource_id))
}
/// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
/// tuples in the guest's physical address space. Converts to RutabageIovec from the memory
/// mapping.
pub fn attach_backing(
&mut self,
resource_id: u32,
mem: &GuestMemory,
vecs: Vec<(GuestAddress, usize)>,
) -> VirtioGpuResult {
let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
self.rutabaga.attach_backing(resource_id, rutabaga_iovecs)?;
Ok(OkNoData)
}
/// Detaches any previously attached iovecs from the resource.
pub fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
self.rutabaga.detach_backing(resource_id)?;
Ok(OkNoData)
}
/// Releases guest kernel reference on the resource.
pub fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
self.resources
.remove(&resource_id)
.ok_or(ErrInvalidResourceId)?;
self.rutabaga.unref_resource(resource_id)?;
Ok(OkNoData)
}
/// Copies data to host resource from the attached iovecs. Can also be used to flush caches.
pub fn transfer_write(
&mut self,
ctx_id: u32,
resource_id: u32,
transfer: Transfer3D,
) -> VirtioGpuResult {
self.rutabaga
.transfer_write(ctx_id, resource_id, transfer)?;
Ok(OkNoData)
}
/// Copies data from the host resource to:
/// 1) To the optional volatile slice
/// 2) To the host resource's attached iovecs
///
/// Can also be used to invalidate caches.
pub fn transfer_read(
&mut self,
ctx_id: u32,
resource_id: u32,
transfer: Transfer3D,
buf: Option<VolatileSlice>,
) -> VirtioGpuResult {
self.rutabaga
.transfer_read(ctx_id, resource_id, transfer, buf)?;
Ok(OkNoData)
}
/// Creates a blob resource using rutabaga.
pub fn resource_create_blob(
&mut self,
ctx_id: u32,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
vecs: Vec<(GuestAddress, usize)>,
mem: &GuestMemory,
) -> VirtioGpuResult {
let mut rutabaga_handle = None;
let mut rutabaga_iovecs = None;
if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
rutabaga_handle = match self.udmabuf_driver {
Some(ref driver) => Some(driver.create_udmabuf(mem, &vecs[..])?),
None => return Err(ErrUnspec),
}
} else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
rutabaga_iovecs =
Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
}
self.rutabaga.resource_create_blob(
ctx_id,
resource_id,
resource_create_blob,
rutabaga_iovecs,
rutabaga_handle,
)?;
let resource = VirtioGpuResource::new(resource_id, 0, 0, resource_create_blob.size);
// Rely on rutabaga to check for duplicate resource ids.
self.resources.insert(resource_id, resource);
Ok(self.result_from_query(resource_id))
}
/// Uses the hypervisor to map the rutabaga blob resource.
pub fn resource_map_blob(&mut self, resource_id: u32, offset: u64) -> VirtioGpuResult {
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(ErrInvalidResourceId)?;
let map_info = self.rutabaga.map_info(resource_id).map_err(|_| ErrUnspec)?;
let vulkan_info_opt = self.rutabaga.vulkan_info(resource_id).ok();
let source = if let Ok(export) = self.rutabaga.export_blob(resource_id) {
match vulkan_info_opt {
Some(vulkan_info) => VmMemorySource::Vulkan {
descriptor: export.os_handle,
handle_type: export.handle_type,
memory_idx: vulkan_info.memory_idx,
physical_device_idx: vulkan_info.physical_device_idx,
size: resource.size,
},
None => VmMemorySource::Descriptor {
descriptor: export.os_handle,
offset: 0,
size: resource.size,
},
}
} else {
if self.external_blob {
return Err(ErrUnspec);
}
let mapping = self.rutabaga.map(resource_id)?;
// Scope for lock
{
let mut map_req = self.map_request.lock();
if map_req.is_some() {
return Err(ErrUnspec);
}
*map_req = Some(mapping);
}
VmMemorySource::ExternalMapping {
size: resource.size,
}
};
let request = VmMemoryRequest::RegisterMemory {
source,
dest: VmMemoryDestination::ExistingAllocation {
allocation: self.pci_bar,
offset,
},
read_only: false,
};
self.gpu_device_tube.send(&request)?;
let response = self.gpu_device_tube.recv()?;
match response {
VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
resource.slot = Some(slot);
Ok(OkMapInfo { map_info })
}
VmMemoryResponse::Err(e) => Err(ErrBase(e)),
_ => Err(ErrUnspec),
}
}
/// Uses the hypervisor to unmap the blob resource.
pub 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_tube.send(&request)?;
let response = self.gpu_device_tube.recv()?;
match response {
VmMemoryResponse::Ok => {
resource.slot = None;
Ok(OkNoData)
}
VmMemoryResponse::Err(e) => Err(ErrBase(e)),
_ => Err(ErrUnspec),
}
}
/// Creates a rutabaga context.
pub fn create_context(&mut self, ctx_id: u32, context_init: u32) -> VirtioGpuResult {
self.rutabaga.create_context(ctx_id, context_init)?;
Ok(OkNoData)
}
/// Destroys a rutabaga context.
pub fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
self.rutabaga.destroy_context(ctx_id)?;
Ok(OkNoData)
}
/// Attaches a resource to a rutabaga context.
pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
self.rutabaga.context_attach_resource(ctx_id, resource_id)?;
Ok(OkNoData)
}
/// Detaches a resource from a rutabaga context.
pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
self.rutabaga.context_detach_resource(ctx_id, resource_id)?;
Ok(OkNoData)
}
/// Submits a command buffer to a rutabaga context.
pub fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> VirtioGpuResult {
self.rutabaga.submit_command(ctx_id, commands)?;
Ok(OkNoData)
}
// Non-public function -- no doc comment needed!
fn result_from_query(&mut self, resource_id: u32) -> GpuResponse {
match self.rutabaga.query(resource_id) {
Ok(query) => {
let mut plane_info = Vec::with_capacity(4);
for plane_index in 0..4 {
plane_info.push(GpuResponsePlaneInfo {
stride: query.strides[plane_index],
offset: query.offsets[plane_index],
});
}
let format_modifier = query.modifier;
OkResourcePlaneInfo {
format_modifier,
plane_info,
}
}
Err(_) => OkNoData,
}
}
fn update_scanout_resource(
&mut self,
scanout_type: SurfaceType,
scanout_id: u32,
scanout_data: Option<VirtioScanoutBlobData>,
resource_id: u32,
) -> VirtioGpuResult {
let mut scanout: &mut VirtioGpuScanout;
let mut scanout_parent_surface_id = None;
match scanout_type {
SurfaceType::Cursor => {
let parent_scanout_id = scanout_id;
scanout_parent_surface_id = self
.scanouts
.get(parent_scanout_id as usize)
.ok_or(ErrInvalidScanoutId)
.map(|parent_scanout| parent_scanout.surface_id)?;
scanout = &mut self.cursor_scanout;
}
SurfaceType::Scanout => {
scanout = self
.scanouts
.get_mut(scanout_id as usize)
.ok_or(ErrInvalidScanoutId)?;
}
};
// Virtio spec: "The driver can use resource_id = 0 to disable a scanout."
if resource_id == 0 {
// Ignore any initial set_scanout(..., resource_id: 0) calls.
if scanout.resource_id.is_some() {
scanout.release_surface(&self.display);
}
scanout.resource_id = None;
return Ok(OkNoData);
}
let resource = self
.resources
.get_mut(&resource_id)
.ok_or(ErrInvalidResourceId)?;
// Ensure scanout has a display surface.
match scanout_type {
SurfaceType::Cursor => {
if let Some(scanout_parent_surface_id) = scanout_parent_surface_id {
scanout.create_surface(&self.display, Some(scanout_parent_surface_id))?;
}
}
SurfaceType::Scanout => {
scanout.create_surface(&self.display, None)?;
}
}
resource.scanout_data = scanout_data;
// `resource_id` has already been verified to be non-zero
let resource_id = match NonZeroU32::new(resource_id) {
Some(id) => id,
None => return Ok(OkNoData),
};
scanout.resource_id = Some(resource_id);
Ok(OkNoData)
}
}