devices/virtio/gpu/
virtio_gpu.rs

1// Copyright 2020 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::cell::RefCell;
6use std::collections::BTreeMap as Map;
7use std::collections::BTreeSet as Set;
8use std::io::IoSliceMut;
9use std::num::NonZeroU32;
10use std::path::PathBuf;
11use std::rc::Rc;
12use std::result::Result;
13use std::sync::atomic::AtomicBool;
14use std::sync::atomic::Ordering;
15use std::sync::Arc;
16
17use anyhow::Context;
18use base::error;
19use base::FromRawDescriptor;
20use base::IntoRawDescriptor;
21use base::Protection;
22use base::SafeDescriptor;
23use base::VolatileSlice;
24use gpu_display::*;
25use hypervisor::MemCacheType;
26use libc::c_void;
27use rutabaga_gfx::Resource3DInfo;
28use rutabaga_gfx::ResourceCreate3D;
29use rutabaga_gfx::ResourceCreateBlob;
30use rutabaga_gfx::Rutabaga;
31use rutabaga_gfx::RutabagaDescriptor;
32#[cfg(windows)]
33use rutabaga_gfx::RutabagaError;
34use rutabaga_gfx::RutabagaFence;
35use rutabaga_gfx::RutabagaFromRawDescriptor;
36use rutabaga_gfx::RutabagaHandle;
37use rutabaga_gfx::RutabagaIntoRawDescriptor;
38use rutabaga_gfx::RutabagaIovec;
39use rutabaga_gfx::RutabagaMesaHandle;
40#[cfg(windows)]
41use rutabaga_gfx::RutabagaUnsupported;
42use rutabaga_gfx::Transfer3D;
43use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_DMABUF;
44use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD;
45use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
46use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
47use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
48use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
49use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
50use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
51use serde::Deserialize;
52use serde::Serialize;
53use sync::Mutex;
54use vm_control::gpu::DisplayMode;
55use vm_control::gpu::DisplayParameters;
56use vm_control::gpu::GpuControlCommand;
57use vm_control::gpu::GpuControlResult;
58use vm_control::gpu::MouseMode;
59use vm_control::VmMemorySource;
60use vm_memory::udmabuf::UdmabufDriver;
61use vm_memory::udmabuf::UdmabufDriverTrait;
62use vm_memory::GuestAddress;
63use vm_memory::GuestMemory;
64
65use super::protocol::virtio_gpu_rect;
66use super::protocol::GpuResponse;
67use super::protocol::GpuResponse::*;
68use super::protocol::GpuResponsePlaneInfo;
69use super::protocol::VirtioGpuResult;
70use super::protocol::VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE;
71use super::protocol::VIRTIO_GPU_BLOB_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 => RutabagaMesaHandle::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::MesaError(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) = RutabagaMesaHandle::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 resource.rutabaga_external_mapping {
1058            self.rutabaga.unmap(resource_id)?;
1059        }
1060
1061        self.rutabaga.unref_resource(resource_id)?;
1062        Ok(OkNoData)
1063    }
1064
1065    /// Copies data to host resource from the attached iovecs. Can also be used to flush caches.
1066    pub fn transfer_write(
1067        &mut self,
1068        ctx_id: u32,
1069        resource_id: u32,
1070        transfer: Transfer3D,
1071    ) -> VirtioGpuResult {
1072        self.rutabaga
1073            .transfer_write(ctx_id, resource_id, transfer, None)?;
1074        Ok(OkNoData)
1075    }
1076
1077    /// Copies data from the host resource to:
1078    ///    1) To the optional volatile slice
1079    ///    2) To the host resource's attached iovecs
1080    ///
1081    /// Can also be used to invalidate caches.
1082    pub fn transfer_read(
1083        &mut self,
1084        ctx_id: u32,
1085        resource_id: u32,
1086        transfer: Transfer3D,
1087        buf: Option<VolatileSlice>,
1088    ) -> VirtioGpuResult {
1089        let buf = buf.map(|vs| {
1090            IoSliceMut::new(
1091                // SAFETY: trivially safe
1092                unsafe { std::slice::from_raw_parts_mut(vs.as_mut_ptr(), vs.size()) },
1093            )
1094        });
1095        self.rutabaga
1096            .transfer_read(ctx_id, resource_id, transfer, buf)?;
1097        Ok(OkNoData)
1098    }
1099
1100    /// Creates a blob resource using rutabaga.
1101    pub fn resource_create_blob(
1102        &mut self,
1103        ctx_id: u32,
1104        resource_id: u32,
1105        resource_create_blob: ResourceCreateBlob,
1106        vecs: Vec<(GuestAddress, usize)>,
1107        mem: &GuestMemory,
1108    ) -> VirtioGpuResult {
1109        let mut descriptor = None;
1110        let mut rutabaga_iovecs = None;
1111
1112        if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
1113            descriptor = match self.udmabuf_driver {
1114                Some(ref driver) => Some(driver.create_udmabuf(mem, &vecs[..])?),
1115                None => return Err(ErrUnspec),
1116            }
1117        } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
1118            rutabaga_iovecs =
1119                Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
1120        }
1121
1122        self.rutabaga.resource_create_blob(
1123            ctx_id,
1124            resource_id,
1125            resource_create_blob,
1126            rutabaga_iovecs,
1127            descriptor.map(|descriptor| {
1128                RutabagaMesaHandle {
1129                    os_handle: to_rutabaga_descriptor(descriptor),
1130                    handle_type: RUTABAGA_HANDLE_TYPE_MEM_DMABUF,
1131                }
1132                .into()
1133            }),
1134        )?;
1135
1136        let guest_cpu_mappable =
1137            (resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE) != 0;
1138        let resource = VirtioGpuResource::new(
1139            resource_id,
1140            0,
1141            0,
1142            resource_create_blob.size,
1143            guest_cpu_mappable,
1144        );
1145
1146        // Rely on rutabaga to check for duplicate resource ids.
1147        self.resources.insert(resource_id, resource);
1148        Ok(self.result_from_query(resource_id))
1149    }
1150
1151    /// Uses the hypervisor to map the rutabaga blob resource.
1152    ///
1153    /// When sandboxing is disabled, external_blob is unset and opaque fds are mapped by
1154    /// rutabaga as ExternalMapping.
1155    /// When sandboxing is enabled, external_blob is set and opaque fds must be mapped in the
1156    /// hypervisor process by Vulkano using metadata provided by Rutabaga::vulkan_info().
1157    pub fn resource_map_blob(
1158        &mut self,
1159        resource_id: u32,
1160        offset: u64,
1161    ) -> anyhow::Result<GpuResponse> {
1162        let resource = self
1163            .resources
1164            .get_mut(&resource_id)
1165            .with_context(|| format!("can't find the resource with id {resource_id}"))
1166            .context(ErrInvalidResourceId)?;
1167
1168        let map_info = self
1169            .rutabaga
1170            .map_info(resource_id)
1171            .context("failed to retrieve the map info for the resource")
1172            .context(ErrUnspec)?;
1173
1174        let mut source: Option<VmMemorySource> = None;
1175        if let Ok(export) = self.rutabaga.export_blob(resource_id) {
1176            let export = RutabagaMesaHandle::try_from(export)
1177                .context("failed to retrieve the handle info")
1178                .context(ErrUnspec)?;
1179            if let Ok(vulkan_info) = self.rutabaga.vulkan_info(resource_id) {
1180                source = Some(VmMemorySource::Vulkan {
1181                    descriptor: to_safe_descriptor(export.os_handle),
1182                    handle_type: export.handle_type,
1183                    memory_idx: vulkan_info.memory_idx,
1184                    device_uuid: vulkan_info.device_id.device_uuid,
1185                    driver_uuid: vulkan_info.device_id.driver_uuid,
1186                    size: resource.size,
1187                });
1188            } else if export.handle_type != RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD {
1189                source = Some(VmMemorySource::Descriptor {
1190                    descriptor: to_safe_descriptor(export.os_handle),
1191                    offset: 0,
1192                    size: resource.size,
1193                });
1194            }
1195        }
1196
1197        // fallback to ExternalMapping via rutabaga if sandboxing (hence external_blob) and fixed
1198        // mapping are both disabled as neither is currently compatible.
1199        if source.is_none() {
1200            anyhow::ensure!(
1201                !self.external_blob,
1202                "can't fallback to external mapping with external blob enabled"
1203            );
1204            anyhow::ensure!(
1205                !self.fixed_blob_mapping,
1206                "can't fallback to external mapping with fixed blob mapping enabled"
1207            );
1208
1209            let mapping = self.rutabaga.map(resource_id).map_err(|e| {
1210                anyhow::anyhow!("failed to map via rutabaga").context(GpuResponse::ErrRutabaga(e))
1211            })?;
1212            // resources mapped via rutabaga must also be marked for unmap via rutabaga.
1213            resource.rutabaga_external_mapping = true;
1214            source = Some(VmMemorySource::ExternalMapping {
1215                ptr: mapping.ptr,
1216                size: mapping.size,
1217            });
1218        };
1219
1220        let prot = match map_info & RUTABAGA_MAP_ACCESS_MASK {
1221            RUTABAGA_MAP_ACCESS_READ => Protection::read(),
1222            RUTABAGA_MAP_ACCESS_WRITE => Protection::write(),
1223            RUTABAGA_MAP_ACCESS_RW => Protection::read_write(),
1224            access_flags => {
1225                return Err(anyhow::anyhow!(
1226                    "unrecognized access flags {:#x}",
1227                    access_flags
1228                ))
1229                .context(ErrUnspec)
1230            }
1231        };
1232
1233        let cache = if cfg!(feature = "noncoherent-dma")
1234            && map_info & RUTABAGA_MAP_CACHE_MASK != RUTABAGA_MAP_CACHE_CACHED
1235        {
1236            MemCacheType::CacheNonCoherent
1237        } else {
1238            MemCacheType::CacheCoherent
1239        };
1240
1241        self.mapper
1242            .lock()
1243            .as_mut()
1244            .expect("No backend request connection found")
1245            .add_mapping(source.unwrap(), offset, prot, cache)
1246            .context("failed to add the memory mapping")
1247            .context(ErrUnspec)?;
1248
1249        resource.shmem_offset = Some(offset);
1250        // Access flags not a part of the virtio-gpu spec.
1251        Ok(OkMapInfo {
1252            map_info: map_info & RUTABAGA_MAP_CACHE_MASK,
1253        })
1254    }
1255
1256    /// Uses the hypervisor to unmap the blob resource.
1257    pub fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult {
1258        let resource = self
1259            .resources
1260            .get_mut(&resource_id)
1261            .ok_or(ErrInvalidResourceId)?;
1262
1263        let shmem_offset = resource.shmem_offset.ok_or(ErrUnspec)?;
1264        self.mapper
1265            .lock()
1266            .as_mut()
1267            .expect("No backend request connection found")
1268            .remove_mapping(shmem_offset)
1269            .map_err(|_| ErrUnspec)?;
1270        resource.shmem_offset = None;
1271
1272        if resource.rutabaga_external_mapping {
1273            self.rutabaga.unmap(resource_id)?;
1274            resource.rutabaga_external_mapping = false;
1275        }
1276
1277        Ok(OkNoData)
1278    }
1279
1280    /// Gets the EDID for the specified scanout ID. If that scanout is not enabled, it would return
1281    /// the EDID of a default display.
1282    pub fn get_edid(&self, scanout_id: u32) -> VirtioGpuResult {
1283        let display_info = match self.scanouts.get(&scanout_id) {
1284            Some(scanout) => {
1285                // Primary scanouts should always have display params.
1286                let params = scanout.display_params.as_ref().unwrap();
1287                DisplayInfo::new(params)
1288            }
1289            None => DisplayInfo::new(&Default::default()),
1290        };
1291        EdidBytes::new(&display_info)
1292    }
1293
1294    /// Creates a rutabaga context.
1295    pub fn create_context(
1296        &mut self,
1297        ctx_id: u32,
1298        context_init: u32,
1299        context_name: Option<&str>,
1300    ) -> VirtioGpuResult {
1301        self.rutabaga
1302            .create_context(ctx_id, context_init, context_name)?;
1303        Ok(OkNoData)
1304    }
1305
1306    /// Destroys a rutabaga context.
1307    pub fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
1308        self.rutabaga.destroy_context(ctx_id)?;
1309        Ok(OkNoData)
1310    }
1311
1312    /// Attaches a resource to a rutabaga context.
1313    pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1314        self.rutabaga.context_attach_resource(ctx_id, resource_id)?;
1315        Ok(OkNoData)
1316    }
1317
1318    /// Detaches a resource from a rutabaga context.
1319    pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1320        self.rutabaga.context_detach_resource(ctx_id, resource_id)?;
1321        Ok(OkNoData)
1322    }
1323
1324    /// Submits a command buffer to a rutabaga context.
1325    pub fn submit_command(
1326        &mut self,
1327        ctx_id: u32,
1328        commands: &mut [u8],
1329        fence_ids: &[u64],
1330    ) -> VirtioGpuResult {
1331        self.rutabaga.submit_command(ctx_id, commands, fence_ids)?;
1332        Ok(OkNoData)
1333    }
1334
1335    // Non-public function -- no doc comment needed!
1336    fn result_from_query(&mut self, resource_id: u32) -> GpuResponse {
1337        match self.rutabaga.resource3d_info(resource_id) {
1338            Ok(query) => {
1339                let mut plane_info = Vec::with_capacity(4);
1340                for plane_index in 0..4 {
1341                    plane_info.push(GpuResponsePlaneInfo {
1342                        stride: query.strides[plane_index],
1343                        offset: query.offsets[plane_index],
1344                    });
1345                }
1346                let format_modifier = query.modifier;
1347                OkResourcePlaneInfo {
1348                    format_modifier,
1349                    plane_info,
1350                }
1351            }
1352            Err(_) => OkNoData,
1353        }
1354    }
1355
1356    fn update_scanout_resource(
1357        &mut self,
1358        scanout_type: SurfaceType,
1359        scanout_rect: Option<virtio_gpu_rect>,
1360        scanout_id: u32,
1361        scanout_data: Option<VirtioScanoutBlobData>,
1362        resource_id: u32,
1363    ) -> VirtioGpuResult {
1364        let scanout: &mut VirtioGpuScanout;
1365        let mut scanout_parent_surface_id = None;
1366
1367        match scanout_type {
1368            SurfaceType::Cursor => {
1369                let parent_scanout_id = scanout_id;
1370
1371                scanout_parent_surface_id = self
1372                    .scanouts
1373                    .get(&parent_scanout_id)
1374                    .ok_or(ErrInvalidScanoutId)
1375                    .map(|parent_scanout| parent_scanout.surface_id)?;
1376
1377                scanout = &mut self.cursor_scanout;
1378            }
1379            SurfaceType::Scanout => {
1380                scanout = self
1381                    .scanouts
1382                    .get_mut(&scanout_id)
1383                    .ok_or(ErrInvalidScanoutId)?;
1384            }
1385        };
1386
1387        // Virtio spec: "The driver can use resource_id = 0 to disable a scanout."
1388        if resource_id == 0 {
1389            // Ignore any initial set_scanout(..., resource_id: 0) calls.
1390            if scanout.resource_id.is_some() {
1391                scanout.release_surface(&self.display);
1392            }
1393
1394            scanout.resource_id = None;
1395            return Ok(OkNoData);
1396        }
1397
1398        let resource = self
1399            .resources
1400            .get_mut(&resource_id)
1401            .ok_or(ErrInvalidResourceId)?;
1402
1403        // Ensure scanout has a display surface.
1404        match scanout_type {
1405            SurfaceType::Cursor => {
1406                if let Some(scanout_parent_surface_id) = scanout_parent_surface_id {
1407                    scanout.create_surface(
1408                        &self.display,
1409                        Some(scanout_parent_surface_id),
1410                        scanout_rect,
1411                    )?;
1412                }
1413            }
1414            SurfaceType::Scanout => {
1415                scanout.create_surface(&self.display, None, scanout_rect)?;
1416            }
1417        }
1418
1419        let info = scanout_data.map(|scanout_data| Resource3DInfo {
1420            width: scanout_data.width,
1421            height: scanout_data.height,
1422            drm_fourcc: scanout_data.drm_format,
1423            strides: scanout_data.strides,
1424            offsets: scanout_data.offsets,
1425            modifier: 0,
1426        });
1427
1428        let _ = self.rutabaga.set_scanout(scanout_id, resource_id, info);
1429
1430        resource.scanout_data = scanout_data;
1431
1432        // `resource_id` has already been verified to be non-zero
1433        let resource_id = match NonZeroU32::new(resource_id) {
1434            Some(id) => id,
1435            None => return Ok(OkNoData),
1436        };
1437        scanout.resource_id = Some(resource_id);
1438
1439        Ok(OkNoData)
1440    }
1441
1442    pub fn suspend(&self) -> anyhow::Result<()> {
1443        self.rutabaga
1444            .suspend()
1445            .context("failed to suspend rutabaga")
1446    }
1447
1448    pub fn snapshot(&self) -> anyhow::Result<VirtioGpuSnapshot> {
1449        let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1450            tempfile::tempdir_in(dir).with_context(|| {
1451                format!(
1452                    "failed to create tempdir in {} for gpu rutabaga snapshot",
1453                    dir.display()
1454                )
1455            })?
1456        } else {
1457            tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1458        };
1459        let snapshot_directory = snapshot_directory_tempdir.path();
1460
1461        Ok(VirtioGpuSnapshot {
1462            scanouts: self
1463                .scanouts
1464                .iter()
1465                .map(|(i, s)| (*i, s.snapshot()))
1466                .collect(),
1467            scanouts_updated: self.scanouts_updated.load(Ordering::SeqCst),
1468            cursor_scanout: self.cursor_scanout.snapshot(),
1469            rutabaga: {
1470                self.rutabaga
1471                    .snapshot(snapshot_directory)
1472                    .context("failed to snapshot rutabaga")?;
1473
1474                pack_directory_to_snapshot(snapshot_directory).with_context(|| {
1475                    format!(
1476                        "failed to pack rutabaga snapshot from {}",
1477                        snapshot_directory.display()
1478                    )
1479                })?
1480            },
1481            resources: self
1482                .resources
1483                .iter()
1484                .map(|(i, r)| (*i, r.snapshot()))
1485                .collect(),
1486        })
1487    }
1488
1489    pub fn restore(&mut self, snapshot: VirtioGpuSnapshot) -> anyhow::Result<()> {
1490        self.deferred_snapshot_load = Some(snapshot);
1491        Ok(())
1492    }
1493
1494    pub fn resume(&mut self, mem: &GuestMemory) -> anyhow::Result<()> {
1495        if let Some(snapshot) = self.deferred_snapshot_load.take() {
1496            assert!(self.scanouts.keys().eq(snapshot.scanouts.keys()));
1497            for (i, s) in snapshot.scanouts.into_iter() {
1498                self.scanouts
1499                    .get_mut(&i)
1500                    .unwrap()
1501                    .restore(
1502                        s,
1503                        // Only the cursor scanout can have a parent.
1504                        None,
1505                        &self.display,
1506                    )
1507                    .context("failed to restore scanouts")?;
1508            }
1509            self.scanouts_updated
1510                .store(snapshot.scanouts_updated, Ordering::SeqCst);
1511
1512            let cursor_parent_surface_id = snapshot
1513                .cursor_scanout
1514                .parent_scanout_id
1515                .and_then(|i| self.scanouts.get(&i).unwrap().surface_id);
1516            self.cursor_scanout
1517                .restore(
1518                    snapshot.cursor_scanout,
1519                    cursor_parent_surface_id,
1520                    &self.display,
1521                )
1522                .context("failed to restore cursor scanout")?;
1523
1524            let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1525                tempfile::tempdir_in(dir).with_context(|| {
1526                    format!(
1527                        "failed to create tempdir in {} for gpu rutabaga snapshot",
1528                        dir.display()
1529                    )
1530                })?
1531            } else {
1532                tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1533            };
1534            let snapshot_directory = snapshot_directory_tempdir.path();
1535
1536            unpack_snapshot_to_directory(snapshot_directory, snapshot.rutabaga).with_context(
1537                || {
1538                    format!(
1539                        "failed to unpack rutabaga snapshot to {}",
1540                        snapshot_directory.display()
1541                    )
1542                },
1543            )?;
1544            self.rutabaga
1545                .restore(snapshot_directory)
1546                .context("failed to restore rutabaga")?;
1547
1548            for (id, s) in snapshot.resources.into_iter() {
1549                let backing_iovecs = s.backing_iovecs.clone();
1550                let shmem_offset = s.shmem_offset;
1551                self.resources.insert(id, VirtioGpuResource::restore(s));
1552                if let Some(backing_iovecs) = backing_iovecs {
1553                    self.attach_backing(id, mem, backing_iovecs)
1554                        .context("failed to restore resource backing")?;
1555                }
1556                if let Some(shmem_offset) = shmem_offset {
1557                    self.resource_map_blob(id, shmem_offset)
1558                        .context("failed to restore resource mapping")?;
1559                }
1560            }
1561        }
1562
1563        self.rutabaga.resume().context("failed to resume rutabaga")
1564    }
1565}