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::RutabagaMagmaHandle;
40#[cfg(windows)]
41use rutabaga_gfx::RutabagaUnsupported;
42use rutabaga_gfx::Transfer3D;
43use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD;
44use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
45use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
46use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
47use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
48use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
49use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
50use serde::Deserialize;
51use serde::Serialize;
52use sync::Mutex;
53use vm_control::gpu::DisplayMode;
54use vm_control::gpu::DisplayParameters;
55use vm_control::gpu::GpuControlCommand;
56use vm_control::gpu::GpuControlResult;
57use vm_control::gpu::MouseMode;
58use vm_control::VmMemorySource;
59use vm_memory::GuestAddress;
60use vm_memory::GuestMemory;
61
62use super::protocol::virtio_gpu_rect;
63use super::protocol::GpuResponse;
64use super::protocol::GpuResponse::*;
65use super::protocol::GpuResponsePlaneInfo;
66use super::protocol::VirtioGpuResult;
67use super::protocol::VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE;
68use super::protocol::VIRTIO_GPU_BLOB_MEM_HOST3D;
69use super::VirtioScanoutBlobData;
70use crate::virtio::gpu::edid::DisplayInfo;
71use crate::virtio::gpu::edid::EdidBytes;
72use crate::virtio::gpu::snapshot::pack_directory_to_snapshot;
73use crate::virtio::gpu::snapshot::unpack_snapshot_to_directory;
74use crate::virtio::gpu::snapshot::DirectorySnapshot;
75use crate::virtio::gpu::GpuDisplayParameters;
76use crate::virtio::gpu::VIRTIO_GPU_MAX_SCANOUTS;
77use crate::virtio::resource_bridge::BufferInfo;
78use crate::virtio::resource_bridge::PlaneInfo;
79use crate::virtio::resource_bridge::ResourceInfo;
80use crate::virtio::resource_bridge::ResourceResponse;
81use crate::virtio::SharedMemoryMapper;
82
83pub fn to_rutabaga_descriptor(s: SafeDescriptor) -> RutabagaDescriptor {
84    // SAFETY:
85    // Safe because we own the SafeDescriptor at this point.
86    unsafe { RutabagaDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
87}
88
89fn to_safe_descriptor(r: RutabagaDescriptor) -> SafeDescriptor {
90    // SAFETY:
91    // Safe because we own the SafeDescriptor at this point.
92    unsafe { SafeDescriptor::from_raw_descriptor(r.into_raw_descriptor()) }
93}
94
95struct VirtioGpuResource {
96    resource_id: u32,
97    width: u32,
98    height: u32,
99    size: u64,
100    shmem_offset: Option<u64>,
101    scanout_data: Option<VirtioScanoutBlobData>,
102    display_import: Option<u32>,
103    rutabaga_external_mapping: bool,
104    guest_cpu_mappable: bool,
105
106    // Only saved for snapshotting, so that we can re-attach backing iovecs with the correct new
107    // host addresses.
108    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
109}
110
111#[derive(Serialize, Deserialize)]
112struct VirtioGpuResourceSnapshot {
113    resource_id: u32,
114    width: u32,
115    height: u32,
116    size: u64,
117
118    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
119    shmem_offset: Option<u64>,
120    scanout_data: Option<VirtioScanoutBlobData>,
121    guest_cpu_mappable: bool,
122}
123
124impl VirtioGpuResource {
125    /// Creates a new VirtioGpuResource with the given metadata.  Width and height are used by the
126    /// display, while size is useful for hypervisor mapping.
127    pub fn new(
128        resource_id: u32,
129        width: u32,
130        height: u32,
131        size: u64,
132        guest_cpu_mappable: bool,
133    ) -> VirtioGpuResource {
134        VirtioGpuResource {
135            resource_id,
136            width,
137            height,
138            size,
139            shmem_offset: None,
140            scanout_data: None,
141            display_import: None,
142            rutabaga_external_mapping: false,
143            guest_cpu_mappable,
144            backing_iovecs: None,
145        }
146    }
147
148    fn snapshot(&self) -> VirtioGpuResourceSnapshot {
149        // Only the 2D backend is fully supported and it doesn't use these fields. 3D is WIP.
150        assert!(self.display_import.is_none());
151
152        VirtioGpuResourceSnapshot {
153            resource_id: self.resource_id,
154            width: self.width,
155            height: self.height,
156            size: self.size,
157            backing_iovecs: self.backing_iovecs.clone(),
158            shmem_offset: self.shmem_offset,
159            scanout_data: self.scanout_data,
160            guest_cpu_mappable: self.guest_cpu_mappable,
161        }
162    }
163
164    fn restore(s: VirtioGpuResourceSnapshot) -> Self {
165        let mut resource = VirtioGpuResource::new(
166            s.resource_id,
167            s.width,
168            s.height,
169            s.size,
170            s.guest_cpu_mappable,
171        );
172        resource.backing_iovecs = s.backing_iovecs;
173        resource.scanout_data = s.scanout_data;
174        resource
175    }
176}
177
178struct VirtioGpuScanout {
179    width: u32,
180    height: u32,
181    scanout_type: SurfaceType,
182    // If this scanout is a primary scanout, the scanout id.
183    scanout_id: Option<u32>,
184    // If this scanout is a primary scanout, the display properties.
185    display_params: Option<GpuDisplayParameters>,
186    // If this scanout is a cursor scanout, the scanout that this is cursor is overlayed onto.
187    parent_surface_id: Option<u32>,
188
189    surface_id: Option<u32>,
190    parent_scanout_id: Option<u32>,
191
192    resource_id: Option<NonZeroU32>,
193    position: Option<(u32, u32)>,
194}
195
196#[derive(Serialize, Deserialize)]
197struct VirtioGpuScanoutSnapshot {
198    width: u32,
199    height: u32,
200    scanout_type: SurfaceType,
201    scanout_id: Option<u32>,
202    display_params: Option<GpuDisplayParameters>,
203
204    // The surface IDs aren't guest visible. Instead of storing them and then having to fix up
205    // `gpu_display` internals, we'll allocate new ones on restore. So, we just need to store
206    // whether a surface was allocated and the parent's scanout ID.
207    has_surface: bool,
208    parent_scanout_id: Option<u32>,
209
210    resource_id: Option<NonZeroU32>,
211    position: Option<(u32, u32)>,
212}
213
214impl VirtioGpuScanout {
215    fn new_primary(scanout_id: u32, params: GpuDisplayParameters) -> VirtioGpuScanout {
216        let (width, height) = params.get_virtual_display_size();
217        VirtioGpuScanout {
218            width,
219            height,
220            scanout_type: SurfaceType::Scanout,
221            scanout_id: Some(scanout_id),
222            display_params: Some(params),
223            parent_surface_id: None,
224            surface_id: None,
225            parent_scanout_id: None,
226            resource_id: None,
227            position: None,
228        }
229    }
230
231    fn new_cursor() -> VirtioGpuScanout {
232        // Per virtio spec: "The mouse cursor image is a normal resource, except that it must be
233        // 64x64 in size."
234        VirtioGpuScanout {
235            width: 64,
236            height: 64,
237            scanout_type: SurfaceType::Cursor,
238            scanout_id: None,
239            display_params: None,
240            parent_surface_id: None,
241            surface_id: None,
242            parent_scanout_id: None,
243            resource_id: None,
244            position: None,
245        }
246    }
247
248    fn snapshot(&self) -> VirtioGpuScanoutSnapshot {
249        VirtioGpuScanoutSnapshot {
250            width: self.width,
251            height: self.height,
252            has_surface: self.surface_id.is_some(),
253            resource_id: self.resource_id,
254            scanout_type: self.scanout_type,
255            scanout_id: self.scanout_id,
256            display_params: self.display_params.clone(),
257            parent_scanout_id: self.parent_scanout_id,
258            position: self.position,
259        }
260    }
261
262    fn restore(
263        &mut self,
264        snapshot: VirtioGpuScanoutSnapshot,
265        parent_surface_id: Option<u32>,
266        display: &Rc<RefCell<GpuDisplay>>,
267    ) -> VirtioGpuResult {
268        // Scanouts are mainly controlled by the host, we just need to make sure it looks same,
269        // restore the resource_id association, and create a surface in the display.
270
271        assert_eq!(self.width, snapshot.width);
272        assert_eq!(self.height, snapshot.height);
273        assert_eq!(self.scanout_type, snapshot.scanout_type);
274        assert_eq!(self.scanout_id, snapshot.scanout_id);
275        assert_eq!(self.display_params, snapshot.display_params);
276
277        self.resource_id = snapshot.resource_id;
278        if snapshot.has_surface {
279            self.create_surface(display, parent_surface_id, None)?;
280        } else {
281            self.release_surface(display);
282        }
283        if let Some((x, y)) = snapshot.position {
284            self.set_position(display, x, y)?;
285        }
286
287        Ok(OkNoData)
288    }
289
290    fn create_surface(
291        &mut self,
292        display: &Rc<RefCell<GpuDisplay>>,
293        new_parent_surface_id: Option<u32>,
294        new_scanout_rect: Option<virtio_gpu_rect>,
295    ) -> VirtioGpuResult {
296        let mut need_to_create = false;
297
298        if self.surface_id.is_none() {
299            need_to_create = true;
300        }
301
302        if self.parent_surface_id != new_parent_surface_id {
303            self.parent_surface_id = new_parent_surface_id;
304            need_to_create = true;
305        }
306
307        if let Some(new_scanout_rect) = new_scanout_rect {
308            // The guest may request a new scanout size when modesetting happens (i.e. display
309            // resolution change). Detect when that happens and re-allocate a surface with the new
310            // size.
311            //
312            // Note that we do NOT update |self.display_params|, which is sourced from user input
313            // (initial display parameters), and (as of the time of writing) only matters to EDID
314            // information. EDID info shall remain the same for a given display even if the active
315            // resolution has changed.
316            let new_width = new_scanout_rect.width.to_native();
317            let new_height = new_scanout_rect.height.to_native();
318            if !(self.width == new_width && self.height == new_height) {
319                self.width = new_width;
320                self.height = new_height;
321                need_to_create = true;
322            }
323        }
324
325        if !need_to_create {
326            return Ok(OkNoData);
327        }
328
329        self.release_surface(display);
330
331        let mut display = display.borrow_mut();
332
333        let display_params = match self.display_params.clone() {
334            Some(mut params) => {
335                // The sizes in |self.display_params| doesn't necessarily match the requested
336                // surface size (see above note about when guest modesetting happens). Always
337                // override display mode to match the requested size.
338                params.mode = DisplayMode::Windowed(self.width, self.height);
339                params
340            }
341            None => {
342                DisplayParameters::default_with_mode(DisplayMode::Windowed(self.width, self.height))
343            }
344        };
345        let surface_id = display.create_surface(
346            self.parent_surface_id,
347            self.scanout_id,
348            &display_params,
349            self.scanout_type,
350        )?;
351
352        self.surface_id = Some(surface_id);
353
354        Ok(OkNoData)
355    }
356
357    fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
358        if let Some(surface_id) = self.surface_id {
359            display.borrow_mut().release_surface(surface_id);
360        }
361
362        self.surface_id = None;
363    }
364
365    fn set_mouse_mode(
366        &mut self,
367        display: &Rc<RefCell<GpuDisplay>>,
368        mouse_mode: MouseMode,
369    ) -> VirtioGpuResult {
370        if let Some(surface_id) = self.surface_id {
371            display
372                .borrow_mut()
373                .set_mouse_mode(surface_id, mouse_mode)?;
374        }
375        Ok(OkNoData)
376    }
377
378    fn set_position(
379        &mut self,
380        display: &Rc<RefCell<GpuDisplay>>,
381        x: u32,
382        y: u32,
383    ) -> VirtioGpuResult {
384        if let Some(surface_id) = self.surface_id {
385            display.borrow_mut().set_position(surface_id, x, y)?;
386            self.position = Some((x, y));
387        }
388        Ok(OkNoData)
389    }
390
391    fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
392        if let Some(surface_id) = self.surface_id {
393            display.borrow_mut().commit(surface_id)?;
394        }
395        Ok(OkNoData)
396    }
397
398    fn flush(
399        &mut self,
400        display: &Rc<RefCell<GpuDisplay>>,
401        resource: &mut VirtioGpuResource,
402        rutabaga: &mut Rutabaga,
403    ) -> VirtioGpuResult {
404        let surface_id = match self.surface_id {
405            Some(id) => id,
406            _ => return Ok(OkNoData),
407        };
408
409        if let Some(import_id) =
410            VirtioGpuScanout::import_resource_to_display(display, surface_id, resource, rutabaga)
411        {
412            display
413                .borrow_mut()
414                .flip_to(surface_id, import_id, None, None, None)
415                .map_err(|e| {
416                    error!("flip_to failed: {:#}", e);
417                    ErrUnspec
418                })?;
419            return Ok(OkNoData);
420        }
421
422        // Import failed, fall back to a copy.
423        let mut display = display.borrow_mut();
424
425        // Prevent overwriting a buffer that is currently being used by the compositor.
426        if display.next_buffer_in_use(surface_id) {
427            return Ok(OkNoData);
428        }
429
430        let fb = display
431            .framebuffer_region(surface_id, 0, 0, self.width, self.height)
432            .ok_or(ErrUnspec)?;
433
434        let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height, 0);
435        transfer.stride = fb.stride();
436        let fb_slice = fb.as_volatile_slice();
437        let buf = IoSliceMut::new(
438            // SAFETY: trivially safe
439            unsafe { std::slice::from_raw_parts_mut(fb_slice.as_mut_ptr(), fb_slice.size()) },
440        );
441        rutabaga.transfer_read(0, resource.resource_id, transfer, Some(buf))?;
442
443        display.flip(surface_id);
444        Ok(OkNoData)
445    }
446
447    fn import_resource_to_display(
448        display: &Rc<RefCell<GpuDisplay>>,
449        surface_id: u32,
450        resource: &mut VirtioGpuResource,
451        rutabaga: &mut Rutabaga,
452    ) -> Option<u32> {
453        if let Some(import_id) = resource.display_import {
454            return Some(import_id);
455        }
456        let blob = rutabaga.export_blob(resource.resource_id).ok()?;
457
458        let handle = match blob {
459            RutabagaHandle::AhbInfo(info) => {
460                let import_id = display
461                    .borrow_mut()
462                    .import_resource(
463                        surface_id,
464                        DisplayExternalResourceImport::AHardwareBuffer { info },
465                    )
466                    .ok()?;
467                resource.display_import = Some(import_id);
468                return Some(import_id);
469            }
470            other => RutabagaMagmaHandle::try_from(other).ok()?,
471        };
472        let dmabuf = to_safe_descriptor(handle.os_handle);
473
474        let (width, height, format, stride, offset, modifier) = match resource.scanout_data {
475            Some(data) => (
476                data.width,
477                data.height,
478                data.drm_format,
479                data.strides[0],
480                data.offsets[0],
481                0,
482            ),
483            None => {
484                let query = rutabaga.resource3d_info(resource.resource_id).ok()?;
485                (
486                    resource.width,
487                    resource.height,
488                    query.drm_fourcc,
489                    query.strides[0],
490                    query.offsets[0],
491                    query.modifier,
492                )
493            }
494        };
495
496        let import_id = display
497            .borrow_mut()
498            .import_resource(
499                surface_id,
500                DisplayExternalResourceImport::Dmabuf {
501                    descriptor: &dmabuf,
502                    offset,
503                    stride,
504                    modifiers: modifier,
505                    width,
506                    height,
507                    fourcc: format,
508                },
509            )
510            .ok()?;
511        resource.display_import = Some(import_id);
512        Some(import_id)
513    }
514}
515
516/// Handles functionality related to displays, input events and hypervisor memory management.
517pub struct VirtioGpu {
518    display: Rc<RefCell<GpuDisplay>>,
519    scanouts: Map<u32, VirtioGpuScanout>,
520    scanouts_updated: Arc<AtomicBool>,
521    cursor_scanout: VirtioGpuScanout,
522    mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
523    rutabaga: Rutabaga,
524    resources: Map<u32, VirtioGpuResource>,
525    external_blob: bool,
526    fixed_blob_mapping: bool,
527    snapshot_scratch_directory: Option<PathBuf>,
528    deferred_snapshot_load: Option<VirtioGpuSnapshot>,
529}
530
531// Only the 2D mode is supported. Notes on `VirtioGpu` fields:
532//
533//   * display: re-initialized from scratch using the scanout snapshots
534//   * scanouts: snapshot'd
535//   * scanouts_updated: snapshot'd
536//   * cursor_scanout: snapshot'd
537//   * mapper: not needed for 2d mode
538//   * rutabaga: re-initialized from scatch using the resource snapshots
539//   * resources: snapshot'd
540//   * external_blob: not needed for 2d mode
541#[derive(Serialize, Deserialize)]
542pub struct VirtioGpuSnapshot {
543    scanouts: Map<u32, VirtioGpuScanoutSnapshot>,
544    scanouts_updated: bool,
545    cursor_scanout: VirtioGpuScanoutSnapshot,
546    rutabaga: DirectorySnapshot,
547    resources: Map<u32, VirtioGpuResourceSnapshot>,
548}
549
550#[derive(Serialize, Deserialize)]
551struct RutabagaResourceSnapshotSerializable {
552    resource_id: u32,
553
554    width: u32,
555    height: u32,
556    host_mem_size: usize,
557
558    backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
559    component_mask: u8,
560    size: u64,
561}
562
563fn sglist_to_rutabaga_iovecs(
564    vecs: &[(GuestAddress, usize)],
565    mem: &GuestMemory,
566) -> Result<Vec<RutabagaIovec>, ()> {
567    if vecs
568        .iter()
569        .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
570    {
571        return Err(());
572    }
573
574    let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
575    for &(addr, len) in vecs {
576        let slice = mem.get_slice_at_addr(addr, len).unwrap();
577        rutabaga_iovecs.push(RutabagaIovec {
578            base: slice.as_mut_ptr() as *mut c_void,
579            len,
580        });
581    }
582    Ok(rutabaga_iovecs)
583}
584
585pub enum ProcessDisplayResult {
586    Success,
587    CloseRequested,
588    Error(GpuDisplayError),
589}
590
591impl VirtioGpu {
592    /// Creates a new instance of the VirtioGpu state tracker.
593    pub fn new(
594        display: GpuDisplay,
595        display_params: Vec<GpuDisplayParameters>,
596        display_event: Arc<AtomicBool>,
597        rutabaga: Rutabaga,
598        mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
599        external_blob: bool,
600        fixed_blob_mapping: bool,
601        snapshot_scratch_directory: Option<PathBuf>,
602    ) -> Option<VirtioGpu> {
603        let scanouts = display_params
604            .iter()
605            .enumerate()
606            .map(|(display_index, display_param)| {
607                (
608                    display_index as u32,
609                    VirtioGpuScanout::new_primary(display_index as u32, display_param.clone()),
610                )
611            })
612            .collect::<Map<_, _>>();
613        let cursor_scanout = VirtioGpuScanout::new_cursor();
614
615        Some(VirtioGpu {
616            display: Rc::new(RefCell::new(display)),
617            scanouts,
618            scanouts_updated: display_event,
619            cursor_scanout,
620            mapper,
621            rutabaga,
622            resources: Default::default(),
623            external_blob,
624            fixed_blob_mapping,
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::MagmaGpuError(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) = RutabagaMagmaHandle::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        // Use tracked `guest_cpu_mappable` from `VirtioGpuResource` because `rutabaga` has
885        // deprecated and unimplemented the `guest_cpu_mappable` method.
886        let guest_cpu_mappable = self
887            .resources
888            .get(&resource_id)
889            .map(|r| r.guest_cpu_mappable)
890            .unwrap_or(false);
891
892        ResourceResponse::Resource(ResourceInfo::Buffer(BufferInfo {
893            handle,
894            planes: [
895                PlaneInfo {
896                    offset: q.offsets[0],
897                    stride: q.strides[0],
898                },
899                PlaneInfo {
900                    offset: q.offsets[1],
901                    stride: q.strides[1],
902                },
903                PlaneInfo {
904                    offset: q.offsets[2],
905                    stride: q.strides[2],
906                },
907                PlaneInfo {
908                    offset: q.offsets[3],
909                    stride: q.strides[3],
910                },
911            ],
912            modifier: q.modifier,
913            guest_cpu_mappable,
914        }))
915    }
916
917    /// If supported, export the fence with the given `fence_id` to a file.
918    pub fn export_fence(&mut self, fence_id: u64) -> ResourceResponse {
919        match self.rutabaga.export_fence(fence_id) {
920            Ok(handle) => ResourceResponse::Resource(ResourceInfo::Fence {
921                handle: to_safe_descriptor(handle.os_handle),
922            }),
923            Err(_) => ResourceResponse::Invalid,
924        }
925    }
926
927    /// Gets rutabaga's capset information associated with `index`.
928    pub fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
929        if let Ok((capset_id, version, size)) = self.rutabaga.get_capset_info(index) {
930            Ok(OkCapsetInfo {
931                capset_id,
932                version,
933                size,
934            })
935        } else {
936            // Any capset_id > 63 is invalid according to the virtio-gpu spec, so we can
937            // intentionally poison the capset without stalling the guest kernel driver.
938            base::warn!(
939                "virtio-gpu get_capset_info(index={}) failed. intentionally poisoning response",
940                index
941            );
942            Ok(OkCapsetInfo {
943                capset_id: u32::MAX,
944                version: 0,
945                size: 0,
946            })
947        }
948    }
949
950    /// Gets a capset from rutabaga.
951    pub fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
952        let capset = self.rutabaga.get_capset(capset_id, version)?;
953        Ok(OkCapset(capset))
954    }
955
956    /// Forces rutabaga to use it's default context.
957    pub fn force_ctx_0(&self) {
958        self.rutabaga.force_ctx_0()
959    }
960
961    /// Creates a fence with the RutabagaFence that can be used to determine when the previous
962    /// command completed.
963    pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
964        self.rutabaga.create_fence(rutabaga_fence)?;
965        Ok(OkNoData)
966    }
967
968    /// Polls the Rutabaga backend.
969    pub fn event_poll(&self) {
970        self.rutabaga.event_poll();
971    }
972
973    /// Gets a pollable eventfd that signals the device to wakeup and poll the
974    /// Rutabaga backend.
975    pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
976        self.rutabaga.poll_descriptor().map(to_safe_descriptor)
977    }
978
979    /// Creates a 3D resource with the given properties and resource_id.
980    pub fn resource_create_3d(
981        &mut self,
982        resource_id: u32,
983        resource_create_3d: ResourceCreate3D,
984    ) -> VirtioGpuResult {
985        self.rutabaga
986            .resource_create_3d(resource_id, resource_create_3d)?;
987
988        let resource = VirtioGpuResource::new(
989            resource_id,
990            resource_create_3d.width,
991            resource_create_3d.height,
992            0,
993            false,
994        );
995
996        // Rely on rutabaga to check for duplicate resource ids.
997        self.resources.insert(resource_id, resource);
998        Ok(self.result_from_query(resource_id))
999    }
1000
1001    /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
1002    /// tuples in the guest's physical address space. Converts to RutabagaIovec from the memory
1003    /// mapping.
1004    pub fn attach_backing(
1005        &mut self,
1006        resource_id: u32,
1007        mem: &GuestMemory,
1008        vecs: Vec<(GuestAddress, usize)>,
1009    ) -> VirtioGpuResult {
1010        let resource = self
1011            .resources
1012            .get_mut(&resource_id)
1013            .ok_or(ErrInvalidResourceId)?;
1014
1015        let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
1016        self.rutabaga.attach_backing(resource_id, rutabaga_iovecs)?;
1017        resource.backing_iovecs = Some(vecs);
1018        Ok(OkNoData)
1019    }
1020
1021    /// Detaches any previously attached iovecs from the resource.
1022    pub fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
1023        let resource = self
1024            .resources
1025            .get_mut(&resource_id)
1026            .ok_or(ErrInvalidResourceId)?;
1027
1028        self.rutabaga.detach_backing(resource_id)?;
1029        resource.backing_iovecs = None;
1030        Ok(OkNoData)
1031    }
1032
1033    /// Releases guest kernel reference on the resource.
1034    pub fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
1035        let resource = self
1036            .resources
1037            .remove(&resource_id)
1038            .ok_or(ErrInvalidResourceId)?;
1039
1040        if resource.rutabaga_external_mapping {
1041            self.rutabaga.unmap(resource_id)?;
1042        }
1043
1044        self.rutabaga.unref_resource(resource_id)?;
1045        Ok(OkNoData)
1046    }
1047
1048    /// Copies data to host resource from the attached iovecs. Can also be used to flush caches.
1049    pub fn transfer_write(
1050        &mut self,
1051        ctx_id: u32,
1052        resource_id: u32,
1053        transfer: Transfer3D,
1054    ) -> VirtioGpuResult {
1055        self.rutabaga
1056            .transfer_write(ctx_id, resource_id, transfer, None)?;
1057        Ok(OkNoData)
1058    }
1059
1060    /// Copies data from the host resource to:
1061    ///    1) To the optional volatile slice
1062    ///    2) To the host resource's attached iovecs
1063    ///
1064    /// Can also be used to invalidate caches.
1065    pub fn transfer_read(
1066        &mut self,
1067        ctx_id: u32,
1068        resource_id: u32,
1069        transfer: Transfer3D,
1070        buf: Option<VolatileSlice>,
1071    ) -> VirtioGpuResult {
1072        let buf = buf.map(|vs| {
1073            IoSliceMut::new(
1074                // SAFETY: trivially safe
1075                unsafe { std::slice::from_raw_parts_mut(vs.as_mut_ptr(), vs.size()) },
1076            )
1077        });
1078        self.rutabaga
1079            .transfer_read(ctx_id, resource_id, transfer, buf)?;
1080        Ok(OkNoData)
1081    }
1082
1083    /// Creates a blob resource using rutabaga.
1084    pub fn resource_create_blob(
1085        &mut self,
1086        ctx_id: u32,
1087        resource_id: u32,
1088        resource_create_blob: ResourceCreateBlob,
1089        vecs: Vec<(GuestAddress, usize)>,
1090        mem: &GuestMemory,
1091    ) -> VirtioGpuResult {
1092        let mut rutabaga_iovecs = None;
1093
1094        if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
1095            rutabaga_iovecs =
1096                Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
1097        }
1098
1099        self.rutabaga.resource_create_blob(
1100            ctx_id,
1101            resource_id,
1102            resource_create_blob,
1103            rutabaga_iovecs,
1104            None,
1105        )?;
1106
1107        let guest_cpu_mappable =
1108            (resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE) != 0;
1109        let resource = VirtioGpuResource::new(
1110            resource_id,
1111            0,
1112            0,
1113            resource_create_blob.size,
1114            guest_cpu_mappable,
1115        );
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 = RutabagaMagmaHandle::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}