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