1use 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 unsafe { RutabagaDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
91}
92
93fn to_safe_descriptor(r: RutabagaDescriptor) -> SafeDescriptor {
94 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: Map<u32, u32>,
108 rutabaga_external_mapping: bool,
109 guest_cpu_mappable: bool,
110
111 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 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 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 scanout_id: Option<u32>,
189 display_params: Option<GpuDisplayParameters>,
191 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 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 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 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 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 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 let mut display = display.borrow_mut();
429
430 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 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
521pub 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#[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 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 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 pub fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
657 &self.display
658 }
659
660 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn force_ctx_0(&self) {
976 self.rutabaga.force_ctx_0()
977 }
978
979 pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
982 self.rutabaga.create_fence(rutabaga_fence)?;
983 Ok(OkNoData)
984 }
985
986 pub fn event_poll(&self) {
988 self.rutabaga.event_poll();
989 }
990
991 pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
994 self.rutabaga.poll_descriptor().map(to_safe_descriptor)
995 }
996
997 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 self.resources.insert(resource_id, resource);
1016 Ok(self.result_from_query(resource_id))
1017 }
1018
1019 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 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 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 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 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 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 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 self.resources.insert(resource_id, resource);
1162 Ok(self.result_from_query(resource_id))
1163 }
1164
1165 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 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 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 Ok(OkMapInfo {
1266 map_info: map_info & RUTABAGA_MAP_CACHE_MASK,
1267 })
1268 }
1269
1270 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 pub fn get_edid(&self, scanout_id: u32) -> VirtioGpuResult {
1297 let display_info = match self.scanouts.get(&scanout_id) {
1298 Some(scanout) => {
1299 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 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 pub fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
1322 self.rutabaga.destroy_context(ctx_id)?;
1323 Ok(OkNoData)
1324 }
1325
1326 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 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 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 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 if resource_id == 0 {
1403 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 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 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 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 false,
1613 false,
1614 false,
1615 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 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 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 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 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}