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