devices/virtio/gpu/
virtio_gpu.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::cell::RefCell;
6use std::collections::BTreeMap as Map;
7use std::collections::BTreeSet as Set;
8use std::io::IoSliceMut;
9use std::num::NonZeroU32;
10use std::path::PathBuf;
11use std::rc::Rc;
12use std::result::Result;
13use std::sync::atomic::AtomicBool;
14use std::sync::atomic::Ordering;
15use std::sync::Arc;
16
17use anyhow::Context;
18use base::error;
19use base::FromRawDescriptor;
20use base::IntoRawDescriptor;
21use base::Protection;
22use base::SafeDescriptor;
23use base::VolatileSlice;
24use gpu_display::*;
25use hypervisor::MemCacheType;
26use libc::c_void;
27use rutabaga_gfx::Resource3DInfo;
28use rutabaga_gfx::ResourceCreate3D;
29use rutabaga_gfx::ResourceCreateBlob;
30use rutabaga_gfx::Rutabaga;
31use rutabaga_gfx::RutabagaDescriptor;
32#[cfg(windows)]
33use rutabaga_gfx::RutabagaError;
34use rutabaga_gfx::RutabagaFence;
35use rutabaga_gfx::RutabagaFromRawDescriptor;
36use rutabaga_gfx::RutabagaHandle;
37use rutabaga_gfx::RutabagaIntoRawDescriptor;
38use rutabaga_gfx::RutabagaIovec;
39use rutabaga_gfx::RutabagaMesaHandle;
40#[cfg(windows)]
41use rutabaga_gfx::RutabagaUnsupported;
42use rutabaga_gfx::Transfer3D;
43use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_DMABUF;
44use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD;
45use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
46use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
47use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
48use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
49use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
50use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
51use serde::Deserialize;
52use serde::Serialize;
53use sync::Mutex;
54use vm_control::gpu::DisplayMode;
55use vm_control::gpu::DisplayParameters;
56use vm_control::gpu::GpuControlCommand;
57use vm_control::gpu::GpuControlResult;
58use vm_control::gpu::MouseMode;
59use vm_control::VmMemorySource;
60use vm_memory::udmabuf::UdmabufDriver;
61use vm_memory::udmabuf::UdmabufDriverTrait;
62use vm_memory::GuestAddress;
63use vm_memory::GuestMemory;
64
65use super::protocol::virtio_gpu_rect;
66use super::protocol::GpuResponse;
67use super::protocol::GpuResponse::*;
68use super::protocol::GpuResponsePlaneInfo;
69use super::protocol::VirtioGpuResult;
70use super::protocol::VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE;
71use super::protocol::VIRTIO_GPU_BLOB_MEM_HOST3D;
72use super::VirtioScanoutBlobData;
73use crate::virtio::gpu::edid::DisplayInfo;
74use crate::virtio::gpu::edid::EdidBytes;
75use crate::virtio::gpu::snapshot::pack_directory_to_snapshot;
76use crate::virtio::gpu::snapshot::unpack_snapshot_to_directory;
77use crate::virtio::gpu::snapshot::DirectorySnapshot;
78use crate::virtio::gpu::GpuDisplayParameters;
79use crate::virtio::gpu::VIRTIO_GPU_MAX_SCANOUTS;
80use crate::virtio::resource_bridge::BufferInfo;
81use crate::virtio::resource_bridge::PlaneInfo;
82use crate::virtio::resource_bridge::ResourceInfo;
83use crate::virtio::resource_bridge::ResourceResponse;
84use crate::virtio::SharedMemoryMapper;
85
86pub fn to_rutabaga_descriptor(s: SafeDescriptor) -> RutabagaDescriptor {
87    // SAFETY:
88    // Safe because we own the SafeDescriptor at this point.
89    unsafe { RutabagaDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
90}
91
92fn to_safe_descriptor(r: RutabagaDescriptor) -> SafeDescriptor {
93    // SAFETY:
94    // Safe because we own the SafeDescriptor at this point.
95    unsafe { SafeDescriptor::from_raw_descriptor(r.into_raw_descriptor()) }
96}
97
98struct VirtioGpuResource {
99    resource_id: u32,
100    width: u32,
101    height: u32,
102    size: u64,
103    shmem_offset: Option<u64>,
104    scanout_data: Option<VirtioScanoutBlobData>,
105    display_import: Option<u32>,
106    rutabaga_external_mapping: bool,
107
108    // Only saved for snapshotting, so that we can re-attach backing iovecs with the correct new
109    // host addresses.
110    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
111}
112
113#[derive(Serialize, Deserialize)]
114struct VirtioGpuResourceSnapshot {
115    resource_id: u32,
116    width: u32,
117    height: u32,
118    size: u64,
119
120    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
121    shmem_offset: Option<u64>,
122    scanout_data: Option<VirtioScanoutBlobData>,
123}
124
125impl VirtioGpuResource {
126    /// Creates a new VirtioGpuResource with the given metadata.  Width and height are used by the
127    /// display, while size is useful for hypervisor mapping.
128    pub fn new(resource_id: u32, width: u32, height: u32, size: u64) -> VirtioGpuResource {
129        VirtioGpuResource {
130            resource_id,
131            width,
132            height,
133            size,
134            shmem_offset: None,
135            scanout_data: None,
136            display_import: None,
137            rutabaga_external_mapping: false,
138            backing_iovecs: None,
139        }
140    }
141
142    fn snapshot(&self) -> VirtioGpuResourceSnapshot {
143        // Only the 2D backend is fully supported and it doesn't use these fields. 3D is WIP.
144        assert!(self.display_import.is_none());
145
146        VirtioGpuResourceSnapshot {
147            resource_id: self.resource_id,
148            width: self.width,
149            height: self.height,
150            size: self.size,
151            backing_iovecs: self.backing_iovecs.clone(),
152            shmem_offset: self.shmem_offset,
153            scanout_data: self.scanout_data,
154        }
155    }
156
157    fn restore(s: VirtioGpuResourceSnapshot) -> Self {
158        let mut resource = VirtioGpuResource::new(s.resource_id, s.width, s.height, s.size);
159        resource.backing_iovecs = s.backing_iovecs;
160        resource.scanout_data = s.scanout_data;
161        resource
162    }
163}
164
165struct VirtioGpuScanout {
166    width: u32,
167    height: u32,
168    scanout_type: SurfaceType,
169    // If this scanout is a primary scanout, the scanout id.
170    scanout_id: Option<u32>,
171    // If this scanout is a primary scanout, the display properties.
172    display_params: Option<GpuDisplayParameters>,
173    // If this scanout is a cursor scanout, the scanout that this is cursor is overlayed onto.
174    parent_surface_id: Option<u32>,
175
176    surface_id: Option<u32>,
177    parent_scanout_id: Option<u32>,
178
179    resource_id: Option<NonZeroU32>,
180    position: Option<(u32, u32)>,
181}
182
183#[derive(Serialize, Deserialize)]
184struct VirtioGpuScanoutSnapshot {
185    width: u32,
186    height: u32,
187    scanout_type: SurfaceType,
188    scanout_id: Option<u32>,
189    display_params: Option<GpuDisplayParameters>,
190
191    // The surface IDs aren't guest visible. Instead of storing them and then having to fix up
192    // `gpu_display` internals, we'll allocate new ones on restore. So, we just need to store
193    // whether a surface was allocated and the parent's scanout ID.
194    has_surface: bool,
195    parent_scanout_id: Option<u32>,
196
197    resource_id: Option<NonZeroU32>,
198    position: Option<(u32, u32)>,
199}
200
201impl VirtioGpuScanout {
202    fn new_primary(scanout_id: u32, params: GpuDisplayParameters) -> VirtioGpuScanout {
203        let (width, height) = params.get_virtual_display_size();
204        VirtioGpuScanout {
205            width,
206            height,
207            scanout_type: SurfaceType::Scanout,
208            scanout_id: Some(scanout_id),
209            display_params: Some(params),
210            parent_surface_id: None,
211            surface_id: None,
212            parent_scanout_id: None,
213            resource_id: None,
214            position: None,
215        }
216    }
217
218    fn new_cursor() -> VirtioGpuScanout {
219        // Per virtio spec: "The mouse cursor image is a normal resource, except that it must be
220        // 64x64 in size."
221        VirtioGpuScanout {
222            width: 64,
223            height: 64,
224            scanout_type: SurfaceType::Cursor,
225            scanout_id: None,
226            display_params: None,
227            parent_surface_id: None,
228            surface_id: None,
229            parent_scanout_id: None,
230            resource_id: None,
231            position: None,
232        }
233    }
234
235    fn snapshot(&self) -> VirtioGpuScanoutSnapshot {
236        VirtioGpuScanoutSnapshot {
237            width: self.width,
238            height: self.height,
239            has_surface: self.surface_id.is_some(),
240            resource_id: self.resource_id,
241            scanout_type: self.scanout_type,
242            scanout_id: self.scanout_id,
243            display_params: self.display_params.clone(),
244            parent_scanout_id: self.parent_scanout_id,
245            position: self.position,
246        }
247    }
248
249    fn restore(
250        &mut self,
251        snapshot: VirtioGpuScanoutSnapshot,
252        parent_surface_id: Option<u32>,
253        display: &Rc<RefCell<GpuDisplay>>,
254    ) -> VirtioGpuResult {
255        // Scanouts are mainly controlled by the host, we just need to make sure it looks same,
256        // restore the resource_id association, and create a surface in the display.
257
258        assert_eq!(self.width, snapshot.width);
259        assert_eq!(self.height, snapshot.height);
260        assert_eq!(self.scanout_type, snapshot.scanout_type);
261        assert_eq!(self.scanout_id, snapshot.scanout_id);
262        assert_eq!(self.display_params, snapshot.display_params);
263
264        self.resource_id = snapshot.resource_id;
265        if snapshot.has_surface {
266            self.create_surface(display, parent_surface_id, None)?;
267        } else {
268            self.release_surface(display);
269        }
270        if let Some((x, y)) = snapshot.position {
271            self.set_position(display, x, y)?;
272        }
273
274        Ok(OkNoData)
275    }
276
277    fn create_surface(
278        &mut self,
279        display: &Rc<RefCell<GpuDisplay>>,
280        new_parent_surface_id: Option<u32>,
281        new_scanout_rect: Option<virtio_gpu_rect>,
282    ) -> VirtioGpuResult {
283        let mut need_to_create = false;
284
285        if self.surface_id.is_none() {
286            need_to_create = true;
287        }
288
289        if self.parent_surface_id != new_parent_surface_id {
290            self.parent_surface_id = new_parent_surface_id;
291            need_to_create = true;
292        }
293
294        if let Some(new_scanout_rect) = new_scanout_rect {
295            // The guest may request a new scanout size when modesetting happens (i.e. display
296            // resolution change). Detect when that happens and re-allocate a surface with the new
297            // size.
298            //
299            // Note that we do NOT update |self.display_params|, which is sourced from user input
300            // (initial display parameters), and (as of the time of writing) only matters to EDID
301            // information. EDID info shall remain the same for a given display even if the active
302            // resolution has changed.
303            let new_width = new_scanout_rect.width.to_native();
304            let new_height = new_scanout_rect.height.to_native();
305            if !(self.width == new_width && self.height == new_height) {
306                self.width = new_width;
307                self.height = new_height;
308                need_to_create = true;
309            }
310        }
311
312        if !need_to_create {
313            return Ok(OkNoData);
314        }
315
316        self.release_surface(display);
317
318        let mut display = display.borrow_mut();
319
320        let display_params = match self.display_params.clone() {
321            Some(mut params) => {
322                // The sizes in |self.display_params| doesn't necessarily match the requested
323                // surface size (see above note about when guest modesetting happens). Always
324                // override display mode to match the requested size.
325                params.mode = DisplayMode::Windowed(self.width, self.height);
326                params
327            }
328            None => {
329                DisplayParameters::default_with_mode(DisplayMode::Windowed(self.width, self.height))
330            }
331        };
332        let surface_id = display.create_surface(
333            self.parent_surface_id,
334            self.scanout_id,
335            &display_params,
336            self.scanout_type,
337        )?;
338
339        self.surface_id = Some(surface_id);
340
341        Ok(OkNoData)
342    }
343
344    fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
345        if let Some(surface_id) = self.surface_id {
346            display.borrow_mut().release_surface(surface_id);
347        }
348
349        self.surface_id = None;
350    }
351
352    fn set_mouse_mode(
353        &mut self,
354        display: &Rc<RefCell<GpuDisplay>>,
355        mouse_mode: MouseMode,
356    ) -> VirtioGpuResult {
357        if let Some(surface_id) = self.surface_id {
358            display
359                .borrow_mut()
360                .set_mouse_mode(surface_id, mouse_mode)?;
361        }
362        Ok(OkNoData)
363    }
364
365    fn set_position(
366        &mut self,
367        display: &Rc<RefCell<GpuDisplay>>,
368        x: u32,
369        y: u32,
370    ) -> VirtioGpuResult {
371        if let Some(surface_id) = self.surface_id {
372            display.borrow_mut().set_position(surface_id, x, y)?;
373            self.position = Some((x, y));
374        }
375        Ok(OkNoData)
376    }
377
378    fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
379        if let Some(surface_id) = self.surface_id {
380            display.borrow_mut().commit(surface_id)?;
381        }
382        Ok(OkNoData)
383    }
384
385    fn flush(
386        &mut self,
387        display: &Rc<RefCell<GpuDisplay>>,
388        resource: &mut VirtioGpuResource,
389        rutabaga: &mut Rutabaga,
390    ) -> VirtioGpuResult {
391        let surface_id = match self.surface_id {
392            Some(id) => id,
393            _ => return Ok(OkNoData),
394        };
395
396        if let Some(import_id) =
397            VirtioGpuScanout::import_resource_to_display(display, surface_id, resource, rutabaga)
398        {
399            display
400                .borrow_mut()
401                .flip_to(surface_id, import_id, None, None, None)
402                .map_err(|e| {
403                    error!("flip_to failed: {:#}", e);
404                    ErrUnspec
405                })?;
406            return Ok(OkNoData);
407        }
408
409        // Import failed, fall back to a copy.
410        let mut display = display.borrow_mut();
411
412        // Prevent overwriting a buffer that is currently being used by the compositor.
413        if display.next_buffer_in_use(surface_id) {
414            return Ok(OkNoData);
415        }
416
417        let fb = display
418            .framebuffer_region(surface_id, 0, 0, self.width, self.height)
419            .ok_or(ErrUnspec)?;
420
421        let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height, 0);
422        transfer.stride = fb.stride();
423        let fb_slice = fb.as_volatile_slice();
424        let buf = IoSliceMut::new(
425            // SAFETY: trivially safe
426            unsafe { std::slice::from_raw_parts_mut(fb_slice.as_mut_ptr(), fb_slice.size()) },
427        );
428        rutabaga.transfer_read(0, resource.resource_id, transfer, Some(buf))?;
429
430        display.flip(surface_id);
431        Ok(OkNoData)
432    }
433
434    fn import_resource_to_display(
435        display: &Rc<RefCell<GpuDisplay>>,
436        surface_id: u32,
437        resource: &mut VirtioGpuResource,
438        rutabaga: &mut Rutabaga,
439    ) -> Option<u32> {
440        if let Some(import_id) = resource.display_import {
441            return Some(import_id);
442        }
443        let blob = rutabaga.export_blob(resource.resource_id).ok()?;
444
445        let handle = match blob {
446            RutabagaHandle::AhbInfo(info) => {
447                let import_id = display
448                    .borrow_mut()
449                    .import_resource(
450                        surface_id,
451                        DisplayExternalResourceImport::AHardwareBuffer { info },
452                    )
453                    .ok()?;
454                resource.display_import = Some(import_id);
455                return Some(import_id);
456            }
457            other => RutabagaMesaHandle::try_from(other).ok()?,
458        };
459        let dmabuf = to_safe_descriptor(handle.os_handle);
460
461        let (width, height, format, stride, offset, modifier) = match resource.scanout_data {
462            Some(data) => (
463                data.width,
464                data.height,
465                data.drm_format,
466                data.strides[0],
467                data.offsets[0],
468                0,
469            ),
470            None => {
471                let query = rutabaga.resource3d_info(resource.resource_id).ok()?;
472                (
473                    resource.width,
474                    resource.height,
475                    query.drm_fourcc,
476                    query.strides[0],
477                    query.offsets[0],
478                    query.modifier,
479                )
480            }
481        };
482
483        let import_id = display
484            .borrow_mut()
485            .import_resource(
486                surface_id,
487                DisplayExternalResourceImport::Dmabuf {
488                    descriptor: &dmabuf,
489                    offset,
490                    stride,
491                    modifiers: modifier,
492                    width,
493                    height,
494                    fourcc: format,
495                },
496            )
497            .ok()?;
498        resource.display_import = Some(import_id);
499        Some(import_id)
500    }
501}
502
503/// Handles functionality related to displays, input events and hypervisor memory management.
504pub struct VirtioGpu {
505    display: Rc<RefCell<GpuDisplay>>,
506    scanouts: Map<u32, VirtioGpuScanout>,
507    scanouts_updated: Arc<AtomicBool>,
508    cursor_scanout: VirtioGpuScanout,
509    mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
510    rutabaga: Rutabaga,
511    resources: Map<u32, VirtioGpuResource>,
512    external_blob: bool,
513    fixed_blob_mapping: bool,
514    udmabuf_driver: Option<UdmabufDriver>,
515    snapshot_scratch_directory: Option<PathBuf>,
516    deferred_snapshot_load: Option<VirtioGpuSnapshot>,
517}
518
519// Only the 2D mode is supported. Notes on `VirtioGpu` fields:
520//
521//   * display: re-initialized from scratch using the scanout snapshots
522//   * scanouts: snapshot'd
523//   * scanouts_updated: snapshot'd
524//   * cursor_scanout: snapshot'd
525//   * mapper: not needed for 2d mode
526//   * rutabaga: re-initialized from scatch using the resource snapshots
527//   * resources: snapshot'd
528//   * external_blob: not needed for 2d mode
529//   * udmabuf_driver: not needed for 2d mode
530#[derive(Serialize, Deserialize)]
531pub struct VirtioGpuSnapshot {
532    scanouts: Map<u32, VirtioGpuScanoutSnapshot>,
533    scanouts_updated: bool,
534    cursor_scanout: VirtioGpuScanoutSnapshot,
535    rutabaga: DirectorySnapshot,
536    resources: Map<u32, VirtioGpuResourceSnapshot>,
537}
538
539#[derive(Serialize, Deserialize)]
540struct RutabagaResourceSnapshotSerializable {
541    resource_id: u32,
542
543    width: u32,
544    height: u32,
545    host_mem_size: usize,
546
547    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
548    component_mask: u8,
549    size: u64,
550}
551
552fn sglist_to_rutabaga_iovecs(
553    vecs: &[(GuestAddress, usize)],
554    mem: &GuestMemory,
555) -> Result<Vec<RutabagaIovec>, ()> {
556    if vecs
557        .iter()
558        .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
559    {
560        return Err(());
561    }
562
563    let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
564    for &(addr, len) in vecs {
565        let slice = mem.get_slice_at_addr(addr, len).unwrap();
566        rutabaga_iovecs.push(RutabagaIovec {
567            base: slice.as_mut_ptr() as *mut c_void,
568            len,
569        });
570    }
571    Ok(rutabaga_iovecs)
572}
573
574pub enum ProcessDisplayResult {
575    Success,
576    CloseRequested,
577    Error(GpuDisplayError),
578}
579
580impl VirtioGpu {
581    /// Creates a new instance of the VirtioGpu state tracker.
582    pub fn new(
583        display: GpuDisplay,
584        display_params: Vec<GpuDisplayParameters>,
585        display_event: Arc<AtomicBool>,
586        rutabaga: Rutabaga,
587        mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
588        external_blob: bool,
589        fixed_blob_mapping: bool,
590        udmabuf: bool,
591        snapshot_scratch_directory: Option<PathBuf>,
592    ) -> Option<VirtioGpu> {
593        let mut udmabuf_driver = None;
594        if udmabuf {
595            udmabuf_driver = Some(
596                UdmabufDriver::new()
597                    .map_err(|e| error!("failed to initialize udmabuf: {}", e))
598                    .ok()?,
599            );
600        }
601
602        let scanouts = display_params
603            .iter()
604            .enumerate()
605            .map(|(display_index, display_param)| {
606                (
607                    display_index as u32,
608                    VirtioGpuScanout::new_primary(display_index as u32, display_param.clone()),
609                )
610            })
611            .collect::<Map<_, _>>();
612        let cursor_scanout = VirtioGpuScanout::new_cursor();
613
614        Some(VirtioGpu {
615            display: Rc::new(RefCell::new(display)),
616            scanouts,
617            scanouts_updated: display_event,
618            cursor_scanout,
619            mapper,
620            rutabaga,
621            resources: Default::default(),
622            external_blob,
623            fixed_blob_mapping,
624            udmabuf_driver,
625            deferred_snapshot_load: None,
626            snapshot_scratch_directory,
627        })
628    }
629
630    /// Imports the event device
631    pub fn import_event_device(&mut self, event_device: EventDevice) -> VirtioGpuResult {
632        let mut display = self.display.borrow_mut();
633        let _event_device_id = display.import_event_device(event_device)?;
634        Ok(OkNoData)
635    }
636
637    /// Gets a reference to the display passed into `new`.
638    pub fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
639        &self.display
640    }
641
642    /// Gets the list of supported display resolutions as a slice of `(width, height, enabled)`
643    /// tuples.
644    pub fn display_info(&self) -> Vec<(u32, u32, bool)> {
645        (0..VIRTIO_GPU_MAX_SCANOUTS)
646            .map(|scanout_id| scanout_id as u32)
647            .map(|scanout_id| {
648                self.scanouts
649                    .get(&scanout_id)
650                    .map_or((0, 0, false), |scanout| {
651                        (scanout.width, scanout.height, true)
652                    })
653            })
654            .collect::<Vec<_>>()
655    }
656
657    // Connects new displays to the device.
658    fn add_displays(&mut self, displays: Vec<DisplayParameters>) -> GpuControlResult {
659        let requested_num_scanouts = self.scanouts.len() + displays.len();
660        if requested_num_scanouts > VIRTIO_GPU_MAX_SCANOUTS {
661            return GpuControlResult::TooManyDisplays {
662                allowed: VIRTIO_GPU_MAX_SCANOUTS,
663                requested: requested_num_scanouts,
664            };
665        }
666
667        let mut available_scanout_ids = (0..VIRTIO_GPU_MAX_SCANOUTS)
668            .map(|s| s as u32)
669            .collect::<Set<u32>>();
670
671        self.scanouts.keys().for_each(|scanout_id| {
672            available_scanout_ids.remove(scanout_id);
673        });
674
675        for display_params in displays.into_iter() {
676            let new_scanout_id = *available_scanout_ids.iter().next().unwrap();
677            available_scanout_ids.remove(&new_scanout_id);
678
679            self.scanouts.insert(
680                new_scanout_id,
681                VirtioGpuScanout::new_primary(new_scanout_id, display_params),
682            );
683        }
684
685        self.scanouts_updated.store(true, Ordering::Relaxed);
686
687        GpuControlResult::DisplaysUpdated
688    }
689
690    /// Returns the list of displays currently connected to the device.
691    fn list_displays(&self) -> GpuControlResult {
692        GpuControlResult::DisplayList {
693            displays: self
694                .scanouts
695                .iter()
696                .filter_map(|(scanout_id, scanout)| {
697                    scanout
698                        .display_params
699                        .as_ref()
700                        .cloned()
701                        .map(|display_params| (*scanout_id, display_params))
702                })
703                .collect(),
704        }
705    }
706
707    /// Removes the specified displays from the device.
708    fn remove_displays(&mut self, display_ids: Vec<u32>) -> GpuControlResult {
709        for display_id in display_ids {
710            if let Some(mut scanout) = self.scanouts.remove(&display_id) {
711                scanout.release_surface(&self.display);
712            } else {
713                return GpuControlResult::NoSuchDisplay { display_id };
714            }
715        }
716
717        self.scanouts_updated.store(true, Ordering::Relaxed);
718        GpuControlResult::DisplaysUpdated
719    }
720
721    fn set_display_mouse_mode(
722        &mut self,
723        display_id: u32,
724        mouse_mode: MouseMode,
725    ) -> GpuControlResult {
726        match self.scanouts.get_mut(&display_id) {
727            Some(scanout) => match scanout.set_mouse_mode(&self.display, mouse_mode) {
728                Ok(_) => GpuControlResult::DisplayMouseModeSet,
729                Err(e) => GpuControlResult::ErrString(e.to_string()),
730            },
731            None => GpuControlResult::NoSuchDisplay { display_id },
732        }
733    }
734
735    /// Performs the given command to interact with or modify the device.
736    pub fn process_gpu_control_command(&mut self, cmd: GpuControlCommand) -> GpuControlResult {
737        match cmd {
738            GpuControlCommand::AddDisplays { displays } => self.add_displays(displays),
739            GpuControlCommand::ListDisplays => self.list_displays(),
740            GpuControlCommand::RemoveDisplays { display_ids } => self.remove_displays(display_ids),
741            GpuControlCommand::SetDisplayMouseMode {
742                display_id,
743                mouse_mode,
744            } => self.set_display_mouse_mode(display_id, mouse_mode),
745        }
746    }
747
748    /// Processes the internal `display` events and returns `true` if any display was closed.
749    pub fn process_display(&mut self) -> ProcessDisplayResult {
750        let mut display = self.display.borrow_mut();
751        let result = display.dispatch_events();
752        match result {
753            Ok(_) => (),
754            Err(e) => {
755                error!("failed to dispatch events: {}", e);
756                return ProcessDisplayResult::Error(e);
757            }
758        }
759
760        for scanout in self.scanouts.values() {
761            let close_requested = scanout
762                .surface_id
763                .map(|surface_id| display.close_requested(surface_id))
764                .unwrap_or(false);
765
766            if close_requested {
767                return ProcessDisplayResult::CloseRequested;
768            }
769        }
770
771        ProcessDisplayResult::Success
772    }
773
774    /// Sets the given resource id as the source of scanout to the display.
775    pub fn set_scanout(
776        &mut self,
777        scanout_rect: virtio_gpu_rect,
778        scanout_id: u32,
779        resource_id: u32,
780        scanout_data: Option<VirtioScanoutBlobData>,
781    ) -> VirtioGpuResult {
782        self.update_scanout_resource(
783            SurfaceType::Scanout,
784            Some(scanout_rect),
785            scanout_id,
786            scanout_data,
787            resource_id,
788        )
789    }
790
791    /// If the resource is the scanout resource, flush it to the display.
792    pub fn flush_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
793        if resource_id == 0 {
794            return Ok(OkNoData);
795        }
796
797        #[cfg(windows)]
798        match self.rutabaga.resource_flush(resource_id) {
799            Ok(_) => return Ok(OkNoData),
800            Err(RutabagaError::MesaError(RutabagaUnsupported)) => {}
801            Err(e) => return Err(ErrRutabaga(e)),
802        }
803
804        let resource = self
805            .resources
806            .get_mut(&resource_id)
807            .ok_or(ErrInvalidResourceId)?;
808
809        // `resource_id` has already been verified to be non-zero
810        let resource_id = match NonZeroU32::new(resource_id) {
811            Some(id) => Some(id),
812            None => return Ok(OkNoData),
813        };
814
815        for scanout in self.scanouts.values_mut() {
816            if scanout.resource_id == resource_id {
817                scanout.flush(&self.display, resource, &mut self.rutabaga)?;
818            }
819        }
820        if self.cursor_scanout.resource_id == resource_id {
821            self.cursor_scanout
822                .flush(&self.display, resource, &mut self.rutabaga)?;
823        }
824
825        Ok(OkNoData)
826    }
827
828    /// Updates the cursor's memory to the given resource_id, and sets its position to the given
829    /// coordinates.
830    pub fn update_cursor(
831        &mut self,
832        resource_id: u32,
833        scanout_id: u32,
834        x: u32,
835        y: u32,
836    ) -> VirtioGpuResult {
837        self.update_scanout_resource(SurfaceType::Cursor, None, scanout_id, None, resource_id)?;
838
839        self.cursor_scanout.set_position(&self.display, x, y)?;
840
841        self.flush_resource(resource_id)
842    }
843
844    /// Moves the cursor's position to the given coordinates.
845    pub fn move_cursor(&mut self, _scanout_id: u32, x: u32, y: u32) -> VirtioGpuResult {
846        self.cursor_scanout.set_position(&self.display, x, y)?;
847        self.cursor_scanout.commit(&self.display)?;
848        Ok(OkNoData)
849    }
850
851    /// Returns a uuid for the resource.
852    pub fn resource_assign_uuid(&self, resource_id: u32) -> VirtioGpuResult {
853        if !self.resources.contains_key(&resource_id) {
854            return Err(ErrInvalidResourceId);
855        }
856
857        // TODO(stevensd): use real uuids once the virtio wayland protocol is updated to
858        // handle more than 32 bits. For now, the virtwl driver knows that the uuid is
859        // actually just the resource id.
860        let mut uuid: [u8; 16] = [0; 16];
861        for (idx, byte) in resource_id.to_be_bytes().iter().enumerate() {
862            uuid[12 + idx] = *byte;
863        }
864        Ok(OkResourceUuid { uuid })
865    }
866
867    /// If supported, export the resource with the given `resource_id` to a file.
868    pub fn export_resource(&mut self, resource_id: u32) -> ResourceResponse {
869        let handle = match self.rutabaga.export_blob(resource_id) {
870            Ok(handle) => {
871                let Ok(handle) = RutabagaMesaHandle::try_from(handle) else {
872                    return ResourceResponse::Invalid;
873                };
874                to_safe_descriptor(handle.os_handle)
875            }
876            Err(_) => return ResourceResponse::Invalid,
877        };
878
879        let q = match self.rutabaga.resource3d_info(resource_id) {
880            Ok(query) => query,
881            Err(_) => return ResourceResponse::Invalid,
882        };
883
884        let guest_cpu_mappable = match self.rutabaga.guest_cpu_mappable(resource_id) {
885            Ok(query) => query,
886            Err(_) => return ResourceResponse::Invalid,
887        };
888
889        ResourceResponse::Resource(ResourceInfo::Buffer(BufferInfo {
890            handle,
891            planes: [
892                PlaneInfo {
893                    offset: q.offsets[0],
894                    stride: q.strides[0],
895                },
896                PlaneInfo {
897                    offset: q.offsets[1],
898                    stride: q.strides[1],
899                },
900                PlaneInfo {
901                    offset: q.offsets[2],
902                    stride: q.strides[2],
903                },
904                PlaneInfo {
905                    offset: q.offsets[3],
906                    stride: q.strides[3],
907                },
908            ],
909            modifier: q.modifier,
910            guest_cpu_mappable,
911        }))
912    }
913
914    /// If supported, export the fence with the given `fence_id` to a file.
915    pub fn export_fence(&mut self, fence_id: u64) -> ResourceResponse {
916        match self.rutabaga.export_fence(fence_id) {
917            Ok(handle) => ResourceResponse::Resource(ResourceInfo::Fence {
918                handle: to_safe_descriptor(handle.os_handle),
919            }),
920            Err(_) => ResourceResponse::Invalid,
921        }
922    }
923
924    /// Gets rutabaga's capset information associated with `index`.
925    pub fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
926        if let Ok((capset_id, version, size)) = self.rutabaga.get_capset_info(index) {
927            Ok(OkCapsetInfo {
928                capset_id,
929                version,
930                size,
931            })
932        } else {
933            // Any capset_id > 63 is invalid according to the virtio-gpu spec, so we can
934            // intentionally poison the capset without stalling the guest kernel driver.
935            base::warn!(
936                "virtio-gpu get_capset_info(index={}) failed. intentionally poisoning response",
937                index
938            );
939            Ok(OkCapsetInfo {
940                capset_id: u32::MAX,
941                version: 0,
942                size: 0,
943            })
944        }
945    }
946
947    /// Gets a capset from rutabaga.
948    pub fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
949        let capset = self.rutabaga.get_capset(capset_id, version)?;
950        Ok(OkCapset(capset))
951    }
952
953    /// Forces rutabaga to use it's default context.
954    pub fn force_ctx_0(&self) {
955        self.rutabaga.force_ctx_0()
956    }
957
958    /// Creates a fence with the RutabagaFence that can be used to determine when the previous
959    /// command completed.
960    pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
961        self.rutabaga.create_fence(rutabaga_fence)?;
962        Ok(OkNoData)
963    }
964
965    /// Polls the Rutabaga backend.
966    pub fn event_poll(&self) {
967        self.rutabaga.event_poll();
968    }
969
970    /// Gets a pollable eventfd that signals the device to wakeup and poll the
971    /// Rutabaga backend.
972    pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
973        self.rutabaga.poll_descriptor().map(to_safe_descriptor)
974    }
975
976    /// Creates a 3D resource with the given properties and resource_id.
977    pub fn resource_create_3d(
978        &mut self,
979        resource_id: u32,
980        resource_create_3d: ResourceCreate3D,
981    ) -> VirtioGpuResult {
982        self.rutabaga
983            .resource_create_3d(resource_id, resource_create_3d)?;
984
985        let resource = VirtioGpuResource::new(
986            resource_id,
987            resource_create_3d.width,
988            resource_create_3d.height,
989            0,
990        );
991
992        // Rely on rutabaga to check for duplicate resource ids.
993        self.resources.insert(resource_id, resource);
994        Ok(self.result_from_query(resource_id))
995    }
996
997    /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
998    /// tuples in the guest's physical address space. Converts to RutabagaIovec from the memory
999    /// mapping.
1000    pub fn attach_backing(
1001        &mut self,
1002        resource_id: u32,
1003        mem: &GuestMemory,
1004        vecs: Vec<(GuestAddress, usize)>,
1005    ) -> VirtioGpuResult {
1006        let resource = self
1007            .resources
1008            .get_mut(&resource_id)
1009            .ok_or(ErrInvalidResourceId)?;
1010
1011        let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
1012        self.rutabaga.attach_backing(resource_id, rutabaga_iovecs)?;
1013        resource.backing_iovecs = Some(vecs);
1014        Ok(OkNoData)
1015    }
1016
1017    /// Detaches any previously attached iovecs from the resource.
1018    pub fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
1019        let resource = self
1020            .resources
1021            .get_mut(&resource_id)
1022            .ok_or(ErrInvalidResourceId)?;
1023
1024        self.rutabaga.detach_backing(resource_id)?;
1025        resource.backing_iovecs = None;
1026        Ok(OkNoData)
1027    }
1028
1029    /// Releases guest kernel reference on the resource.
1030    pub fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
1031        let resource = self
1032            .resources
1033            .remove(&resource_id)
1034            .ok_or(ErrInvalidResourceId)?;
1035
1036        if resource.rutabaga_external_mapping {
1037            self.rutabaga.unmap(resource_id)?;
1038        }
1039
1040        self.rutabaga.unref_resource(resource_id)?;
1041        Ok(OkNoData)
1042    }
1043
1044    /// Copies data to host resource from the attached iovecs. Can also be used to flush caches.
1045    pub fn transfer_write(
1046        &mut self,
1047        ctx_id: u32,
1048        resource_id: u32,
1049        transfer: Transfer3D,
1050    ) -> VirtioGpuResult {
1051        self.rutabaga
1052            .transfer_write(ctx_id, resource_id, transfer, None)?;
1053        Ok(OkNoData)
1054    }
1055
1056    /// Copies data from the host resource to:
1057    ///    1) To the optional volatile slice
1058    ///    2) To the host resource's attached iovecs
1059    ///
1060    /// Can also be used to invalidate caches.
1061    pub fn transfer_read(
1062        &mut self,
1063        ctx_id: u32,
1064        resource_id: u32,
1065        transfer: Transfer3D,
1066        buf: Option<VolatileSlice>,
1067    ) -> VirtioGpuResult {
1068        let buf = buf.map(|vs| {
1069            IoSliceMut::new(
1070                // SAFETY: trivially safe
1071                unsafe { std::slice::from_raw_parts_mut(vs.as_mut_ptr(), vs.size()) },
1072            )
1073        });
1074        self.rutabaga
1075            .transfer_read(ctx_id, resource_id, transfer, buf)?;
1076        Ok(OkNoData)
1077    }
1078
1079    /// Creates a blob resource using rutabaga.
1080    pub fn resource_create_blob(
1081        &mut self,
1082        ctx_id: u32,
1083        resource_id: u32,
1084        resource_create_blob: ResourceCreateBlob,
1085        vecs: Vec<(GuestAddress, usize)>,
1086        mem: &GuestMemory,
1087    ) -> VirtioGpuResult {
1088        let mut descriptor = None;
1089        let mut rutabaga_iovecs = None;
1090
1091        if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
1092            descriptor = match self.udmabuf_driver {
1093                Some(ref driver) => Some(driver.create_udmabuf(mem, &vecs[..])?),
1094                None => return Err(ErrUnspec),
1095            }
1096        } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
1097            rutabaga_iovecs =
1098                Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
1099        }
1100
1101        self.rutabaga.resource_create_blob(
1102            ctx_id,
1103            resource_id,
1104            resource_create_blob,
1105            rutabaga_iovecs,
1106            descriptor.map(|descriptor| {
1107                RutabagaMesaHandle {
1108                    os_handle: to_rutabaga_descriptor(descriptor),
1109                    handle_type: RUTABAGA_HANDLE_TYPE_MEM_DMABUF,
1110                }
1111                .into()
1112            }),
1113        )?;
1114
1115        let resource = VirtioGpuResource::new(resource_id, 0, 0, resource_create_blob.size);
1116
1117        // Rely on rutabaga to check for duplicate resource ids.
1118        self.resources.insert(resource_id, resource);
1119        Ok(self.result_from_query(resource_id))
1120    }
1121
1122    /// Uses the hypervisor to map the rutabaga blob resource.
1123    ///
1124    /// When sandboxing is disabled, external_blob is unset and opaque fds are mapped by
1125    /// rutabaga as ExternalMapping.
1126    /// When sandboxing is enabled, external_blob is set and opaque fds must be mapped in the
1127    /// hypervisor process by Vulkano using metadata provided by Rutabaga::vulkan_info().
1128    pub fn resource_map_blob(
1129        &mut self,
1130        resource_id: u32,
1131        offset: u64,
1132    ) -> anyhow::Result<GpuResponse> {
1133        let resource = self
1134            .resources
1135            .get_mut(&resource_id)
1136            .with_context(|| format!("can't find the resource with id {resource_id}"))
1137            .context(ErrInvalidResourceId)?;
1138
1139        let map_info = self
1140            .rutabaga
1141            .map_info(resource_id)
1142            .context("failed to retrieve the map info for the resource")
1143            .context(ErrUnspec)?;
1144
1145        let mut source: Option<VmMemorySource> = None;
1146        if let Ok(export) = self.rutabaga.export_blob(resource_id) {
1147            let export = RutabagaMesaHandle::try_from(export)
1148                .context("failed to retrieve the handle info")
1149                .context(ErrUnspec)?;
1150            if let Ok(vulkan_info) = self.rutabaga.vulkan_info(resource_id) {
1151                source = Some(VmMemorySource::Vulkan {
1152                    descriptor: to_safe_descriptor(export.os_handle),
1153                    handle_type: export.handle_type,
1154                    memory_idx: vulkan_info.memory_idx,
1155                    device_uuid: vulkan_info.device_id.device_uuid,
1156                    driver_uuid: vulkan_info.device_id.driver_uuid,
1157                    size: resource.size,
1158                });
1159            } else if export.handle_type != RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD {
1160                source = Some(VmMemorySource::Descriptor {
1161                    descriptor: to_safe_descriptor(export.os_handle),
1162                    offset: 0,
1163                    size: resource.size,
1164                });
1165            }
1166        }
1167
1168        // fallback to ExternalMapping via rutabaga if sandboxing (hence external_blob) and fixed
1169        // mapping are both disabled as neither is currently compatible.
1170        if source.is_none() {
1171            anyhow::ensure!(
1172                !self.external_blob,
1173                "can't fallback to external mapping with external blob enabled"
1174            );
1175            anyhow::ensure!(
1176                !self.fixed_blob_mapping,
1177                "can't fallback to external mapping with fixed blob mapping enabled"
1178            );
1179
1180            let mapping = self.rutabaga.map(resource_id).map_err(|e| {
1181                anyhow::anyhow!("failed to map via rutabaga").context(GpuResponse::ErrRutabaga(e))
1182            })?;
1183            // resources mapped via rutabaga must also be marked for unmap via rutabaga.
1184            resource.rutabaga_external_mapping = true;
1185            source = Some(VmMemorySource::ExternalMapping {
1186                ptr: mapping.ptr,
1187                size: mapping.size,
1188            });
1189        };
1190
1191        let prot = match map_info & RUTABAGA_MAP_ACCESS_MASK {
1192            RUTABAGA_MAP_ACCESS_READ => Protection::read(),
1193            RUTABAGA_MAP_ACCESS_WRITE => Protection::write(),
1194            RUTABAGA_MAP_ACCESS_RW => Protection::read_write(),
1195            access_flags => {
1196                return Err(anyhow::anyhow!(
1197                    "unrecognized access flags {:#x}",
1198                    access_flags
1199                ))
1200                .context(ErrUnspec)
1201            }
1202        };
1203
1204        let cache = if cfg!(feature = "noncoherent-dma")
1205            && map_info & RUTABAGA_MAP_CACHE_MASK != RUTABAGA_MAP_CACHE_CACHED
1206        {
1207            MemCacheType::CacheNonCoherent
1208        } else {
1209            MemCacheType::CacheCoherent
1210        };
1211
1212        self.mapper
1213            .lock()
1214            .as_mut()
1215            .expect("No backend request connection found")
1216            .add_mapping(source.unwrap(), offset, prot, cache)
1217            .context("failed to add the memory mapping")
1218            .context(ErrUnspec)?;
1219
1220        resource.shmem_offset = Some(offset);
1221        // Access flags not a part of the virtio-gpu spec.
1222        Ok(OkMapInfo {
1223            map_info: map_info & RUTABAGA_MAP_CACHE_MASK,
1224        })
1225    }
1226
1227    /// Uses the hypervisor to unmap the blob resource.
1228    pub fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult {
1229        let resource = self
1230            .resources
1231            .get_mut(&resource_id)
1232            .ok_or(ErrInvalidResourceId)?;
1233
1234        let shmem_offset = resource.shmem_offset.ok_or(ErrUnspec)?;
1235        self.mapper
1236            .lock()
1237            .as_mut()
1238            .expect("No backend request connection found")
1239            .remove_mapping(shmem_offset)
1240            .map_err(|_| ErrUnspec)?;
1241        resource.shmem_offset = None;
1242
1243        if resource.rutabaga_external_mapping {
1244            self.rutabaga.unmap(resource_id)?;
1245            resource.rutabaga_external_mapping = false;
1246        }
1247
1248        Ok(OkNoData)
1249    }
1250
1251    /// Gets the EDID for the specified scanout ID. If that scanout is not enabled, it would return
1252    /// the EDID of a default display.
1253    pub fn get_edid(&self, scanout_id: u32) -> VirtioGpuResult {
1254        let display_info = match self.scanouts.get(&scanout_id) {
1255            Some(scanout) => {
1256                // Primary scanouts should always have display params.
1257                let params = scanout.display_params.as_ref().unwrap();
1258                DisplayInfo::new(params)
1259            }
1260            None => DisplayInfo::new(&Default::default()),
1261        };
1262        EdidBytes::new(&display_info)
1263    }
1264
1265    /// Creates a rutabaga context.
1266    pub fn create_context(
1267        &mut self,
1268        ctx_id: u32,
1269        context_init: u32,
1270        context_name: Option<&str>,
1271    ) -> VirtioGpuResult {
1272        self.rutabaga
1273            .create_context(ctx_id, context_init, context_name)?;
1274        Ok(OkNoData)
1275    }
1276
1277    /// Destroys a rutabaga context.
1278    pub fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
1279        self.rutabaga.destroy_context(ctx_id)?;
1280        Ok(OkNoData)
1281    }
1282
1283    /// Attaches a resource to a rutabaga context.
1284    pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1285        self.rutabaga.context_attach_resource(ctx_id, resource_id)?;
1286        Ok(OkNoData)
1287    }
1288
1289    /// Detaches a resource from a rutabaga context.
1290    pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1291        self.rutabaga.context_detach_resource(ctx_id, resource_id)?;
1292        Ok(OkNoData)
1293    }
1294
1295    /// Submits a command buffer to a rutabaga context.
1296    pub fn submit_command(
1297        &mut self,
1298        ctx_id: u32,
1299        commands: &mut [u8],
1300        fence_ids: &[u64],
1301    ) -> VirtioGpuResult {
1302        self.rutabaga.submit_command(ctx_id, commands, fence_ids)?;
1303        Ok(OkNoData)
1304    }
1305
1306    // Non-public function -- no doc comment needed!
1307    fn result_from_query(&mut self, resource_id: u32) -> GpuResponse {
1308        match self.rutabaga.resource3d_info(resource_id) {
1309            Ok(query) => {
1310                let mut plane_info = Vec::with_capacity(4);
1311                for plane_index in 0..4 {
1312                    plane_info.push(GpuResponsePlaneInfo {
1313                        stride: query.strides[plane_index],
1314                        offset: query.offsets[plane_index],
1315                    });
1316                }
1317                let format_modifier = query.modifier;
1318                OkResourcePlaneInfo {
1319                    format_modifier,
1320                    plane_info,
1321                }
1322            }
1323            Err(_) => OkNoData,
1324        }
1325    }
1326
1327    fn update_scanout_resource(
1328        &mut self,
1329        scanout_type: SurfaceType,
1330        scanout_rect: Option<virtio_gpu_rect>,
1331        scanout_id: u32,
1332        scanout_data: Option<VirtioScanoutBlobData>,
1333        resource_id: u32,
1334    ) -> VirtioGpuResult {
1335        let scanout: &mut VirtioGpuScanout;
1336        let mut scanout_parent_surface_id = None;
1337
1338        match scanout_type {
1339            SurfaceType::Cursor => {
1340                let parent_scanout_id = scanout_id;
1341
1342                scanout_parent_surface_id = self
1343                    .scanouts
1344                    .get(&parent_scanout_id)
1345                    .ok_or(ErrInvalidScanoutId)
1346                    .map(|parent_scanout| parent_scanout.surface_id)?;
1347
1348                scanout = &mut self.cursor_scanout;
1349            }
1350            SurfaceType::Scanout => {
1351                scanout = self
1352                    .scanouts
1353                    .get_mut(&scanout_id)
1354                    .ok_or(ErrInvalidScanoutId)?;
1355            }
1356        };
1357
1358        // Virtio spec: "The driver can use resource_id = 0 to disable a scanout."
1359        if resource_id == 0 {
1360            // Ignore any initial set_scanout(..., resource_id: 0) calls.
1361            if scanout.resource_id.is_some() {
1362                scanout.release_surface(&self.display);
1363            }
1364
1365            scanout.resource_id = None;
1366            return Ok(OkNoData);
1367        }
1368
1369        let resource = self
1370            .resources
1371            .get_mut(&resource_id)
1372            .ok_or(ErrInvalidResourceId)?;
1373
1374        // Ensure scanout has a display surface.
1375        match scanout_type {
1376            SurfaceType::Cursor => {
1377                if let Some(scanout_parent_surface_id) = scanout_parent_surface_id {
1378                    scanout.create_surface(
1379                        &self.display,
1380                        Some(scanout_parent_surface_id),
1381                        scanout_rect,
1382                    )?;
1383                }
1384            }
1385            SurfaceType::Scanout => {
1386                scanout.create_surface(&self.display, None, scanout_rect)?;
1387            }
1388        }
1389
1390        let info = scanout_data.map(|scanout_data| Resource3DInfo {
1391            width: scanout_data.width,
1392            height: scanout_data.height,
1393            drm_fourcc: scanout_data.drm_format,
1394            strides: scanout_data.strides,
1395            offsets: scanout_data.offsets,
1396            modifier: 0,
1397        });
1398
1399        let _ = self.rutabaga.set_scanout(scanout_id, resource_id, info);
1400
1401        resource.scanout_data = scanout_data;
1402
1403        // `resource_id` has already been verified to be non-zero
1404        let resource_id = match NonZeroU32::new(resource_id) {
1405            Some(id) => id,
1406            None => return Ok(OkNoData),
1407        };
1408        scanout.resource_id = Some(resource_id);
1409
1410        Ok(OkNoData)
1411    }
1412
1413    pub fn suspend(&self) -> anyhow::Result<()> {
1414        self.rutabaga
1415            .suspend()
1416            .context("failed to suspend rutabaga")
1417    }
1418
1419    pub fn snapshot(&self) -> anyhow::Result<VirtioGpuSnapshot> {
1420        let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1421            tempfile::tempdir_in(dir).with_context(|| {
1422                format!(
1423                    "failed to create tempdir in {} for gpu rutabaga snapshot",
1424                    dir.display()
1425                )
1426            })?
1427        } else {
1428            tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1429        };
1430        let snapshot_directory = snapshot_directory_tempdir.path();
1431
1432        Ok(VirtioGpuSnapshot {
1433            scanouts: self
1434                .scanouts
1435                .iter()
1436                .map(|(i, s)| (*i, s.snapshot()))
1437                .collect(),
1438            scanouts_updated: self.scanouts_updated.load(Ordering::SeqCst),
1439            cursor_scanout: self.cursor_scanout.snapshot(),
1440            rutabaga: {
1441                self.rutabaga
1442                    .snapshot(snapshot_directory)
1443                    .context("failed to snapshot rutabaga")?;
1444
1445                pack_directory_to_snapshot(snapshot_directory).with_context(|| {
1446                    format!(
1447                        "failed to pack rutabaga snapshot from {}",
1448                        snapshot_directory.display()
1449                    )
1450                })?
1451            },
1452            resources: self
1453                .resources
1454                .iter()
1455                .map(|(i, r)| (*i, r.snapshot()))
1456                .collect(),
1457        })
1458    }
1459
1460    pub fn restore(&mut self, snapshot: VirtioGpuSnapshot) -> anyhow::Result<()> {
1461        self.deferred_snapshot_load = Some(snapshot);
1462        Ok(())
1463    }
1464
1465    pub fn resume(&mut self, mem: &GuestMemory) -> anyhow::Result<()> {
1466        if let Some(snapshot) = self.deferred_snapshot_load.take() {
1467            assert!(self.scanouts.keys().eq(snapshot.scanouts.keys()));
1468            for (i, s) in snapshot.scanouts.into_iter() {
1469                self.scanouts
1470                    .get_mut(&i)
1471                    .unwrap()
1472                    .restore(
1473                        s,
1474                        // Only the cursor scanout can have a parent.
1475                        None,
1476                        &self.display,
1477                    )
1478                    .context("failed to restore scanouts")?;
1479            }
1480            self.scanouts_updated
1481                .store(snapshot.scanouts_updated, Ordering::SeqCst);
1482
1483            let cursor_parent_surface_id = snapshot
1484                .cursor_scanout
1485                .parent_scanout_id
1486                .and_then(|i| self.scanouts.get(&i).unwrap().surface_id);
1487            self.cursor_scanout
1488                .restore(
1489                    snapshot.cursor_scanout,
1490                    cursor_parent_surface_id,
1491                    &self.display,
1492                )
1493                .context("failed to restore cursor scanout")?;
1494
1495            let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1496                tempfile::tempdir_in(dir).with_context(|| {
1497                    format!(
1498                        "failed to create tempdir in {} for gpu rutabaga snapshot",
1499                        dir.display()
1500                    )
1501                })?
1502            } else {
1503                tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1504            };
1505            let snapshot_directory = snapshot_directory_tempdir.path();
1506
1507            unpack_snapshot_to_directory(snapshot_directory, snapshot.rutabaga).with_context(
1508                || {
1509                    format!(
1510                        "failed to unpack rutabaga snapshot to {}",
1511                        snapshot_directory.display()
1512                    )
1513                },
1514            )?;
1515            self.rutabaga
1516                .restore(snapshot_directory)
1517                .context("failed to restore rutabaga")?;
1518
1519            for (id, s) in snapshot.resources.into_iter() {
1520                let backing_iovecs = s.backing_iovecs.clone();
1521                let shmem_offset = s.shmem_offset;
1522                self.resources.insert(id, VirtioGpuResource::restore(s));
1523                if let Some(backing_iovecs) = backing_iovecs {
1524                    self.attach_backing(id, mem, backing_iovecs)
1525                        .context("failed to restore resource backing")?;
1526                }
1527                if let Some(shmem_offset) = shmem_offset {
1528                    self.resource_map_blob(id, shmem_offset)
1529                        .context("failed to restore resource mapping")?;
1530                }
1531            }
1532        }
1533
1534        self.rutabaga.resume().context("failed to resume rutabaga")
1535    }
1536}