devices/virtio/gpu/
virtio_gpu.rs

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