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_OPAQUE_FD;
44use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
45use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
46use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
47use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
48use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
49use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
50use serde::Deserialize;
51use serde::Serialize;
52use sync::Mutex;
53use vm_control::gpu::DisplayMode;
54use vm_control::gpu::DisplayParameters;
55use vm_control::gpu::GpuControlCommand;
56use vm_control::gpu::GpuControlResult;
57use vm_control::gpu::MouseMode;
58use vm_control::VmMemorySource;
59use vm_memory::GuestAddress;
60use vm_memory::GuestMemory;
61
62use super::protocol::virtio_gpu_rect;
63use super::protocol::GpuResponse;
64use super::protocol::GpuResponse::*;
65use super::protocol::GpuResponsePlaneInfo;
66use super::protocol::VirtioGpuResult;
67use super::protocol::VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE;
68use super::protocol::VIRTIO_GPU_BLOB_MEM_HOST3D;
69use super::VirtioScanoutBlobData;
70use crate::virtio::gpu::edid::DisplayInfo;
71use crate::virtio::gpu::edid::EdidBytes;
72use crate::virtio::gpu::snapshot::pack_directory_to_snapshot;
73use crate::virtio::gpu::snapshot::unpack_snapshot_to_directory;
74use crate::virtio::gpu::snapshot::DirectorySnapshot;
75use crate::virtio::gpu::GpuDisplayParameters;
76use crate::virtio::gpu::VIRTIO_GPU_MAX_SCANOUTS;
77use crate::virtio::resource_bridge::BufferInfo;
78use crate::virtio::resource_bridge::PlaneInfo;
79use crate::virtio::resource_bridge::ResourceInfo;
80use crate::virtio::resource_bridge::ResourceResponse;
81use crate::virtio::SharedMemoryMapper;
82
83pub fn to_rutabaga_descriptor(s: SafeDescriptor) -> RutabagaDescriptor {
84 unsafe { RutabagaDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
87}
88
89fn to_safe_descriptor(r: RutabagaDescriptor) -> SafeDescriptor {
90 unsafe { SafeDescriptor::from_raw_descriptor(r.into_raw_descriptor()) }
93}
94
95struct VirtioGpuResource {
96 resource_id: u32,
97 width: u32,
98 height: u32,
99 size: u64,
100 shmem_offset: Option<u64>,
101 scanout_data: Option<VirtioScanoutBlobData>,
102 display_import: Option<u32>,
103 rutabaga_external_mapping: bool,
104 guest_cpu_mappable: bool,
105
106 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
109}
110
111#[derive(Serialize, Deserialize)]
112struct VirtioGpuResourceSnapshot {
113 resource_id: u32,
114 width: u32,
115 height: u32,
116 size: u64,
117
118 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
119 shmem_offset: Option<u64>,
120 scanout_data: Option<VirtioScanoutBlobData>,
121 guest_cpu_mappable: bool,
122}
123
124impl VirtioGpuResource {
125 pub fn new(
128 resource_id: u32,
129 width: u32,
130 height: u32,
131 size: u64,
132 guest_cpu_mappable: bool,
133 ) -> VirtioGpuResource {
134 VirtioGpuResource {
135 resource_id,
136 width,
137 height,
138 size,
139 shmem_offset: None,
140 scanout_data: None,
141 display_import: None,
142 rutabaga_external_mapping: false,
143 guest_cpu_mappable,
144 backing_iovecs: None,
145 }
146 }
147
148 fn snapshot(&self) -> VirtioGpuResourceSnapshot {
149 assert!(self.display_import.is_none());
151
152 VirtioGpuResourceSnapshot {
153 resource_id: self.resource_id,
154 width: self.width,
155 height: self.height,
156 size: self.size,
157 backing_iovecs: self.backing_iovecs.clone(),
158 shmem_offset: self.shmem_offset,
159 scanout_data: self.scanout_data,
160 guest_cpu_mappable: self.guest_cpu_mappable,
161 }
162 }
163
164 fn restore(s: VirtioGpuResourceSnapshot) -> Self {
165 let mut resource = VirtioGpuResource::new(
166 s.resource_id,
167 s.width,
168 s.height,
169 s.size,
170 s.guest_cpu_mappable,
171 );
172 resource.backing_iovecs = s.backing_iovecs;
173 resource.scanout_data = s.scanout_data;
174 resource
175 }
176}
177
178struct VirtioGpuScanout {
179 width: u32,
180 height: u32,
181 scanout_type: SurfaceType,
182 scanout_id: Option<u32>,
184 display_params: Option<GpuDisplayParameters>,
186 parent_surface_id: Option<u32>,
188
189 surface_id: Option<u32>,
190 parent_scanout_id: Option<u32>,
191
192 resource_id: Option<NonZeroU32>,
193 position: Option<(u32, u32)>,
194}
195
196#[derive(Serialize, Deserialize)]
197struct VirtioGpuScanoutSnapshot {
198 width: u32,
199 height: u32,
200 scanout_type: SurfaceType,
201 scanout_id: Option<u32>,
202 display_params: Option<GpuDisplayParameters>,
203
204 has_surface: bool,
208 parent_scanout_id: Option<u32>,
209
210 resource_id: Option<NonZeroU32>,
211 position: Option<(u32, u32)>,
212}
213
214impl VirtioGpuScanout {
215 fn new_primary(scanout_id: u32, params: GpuDisplayParameters) -> VirtioGpuScanout {
216 let (width, height) = params.get_virtual_display_size();
217 VirtioGpuScanout {
218 width,
219 height,
220 scanout_type: SurfaceType::Scanout,
221 scanout_id: Some(scanout_id),
222 display_params: Some(params),
223 parent_surface_id: None,
224 surface_id: None,
225 parent_scanout_id: None,
226 resource_id: None,
227 position: None,
228 }
229 }
230
231 fn new_cursor() -> VirtioGpuScanout {
232 VirtioGpuScanout {
235 width: 64,
236 height: 64,
237 scanout_type: SurfaceType::Cursor,
238 scanout_id: None,
239 display_params: None,
240 parent_surface_id: None,
241 surface_id: None,
242 parent_scanout_id: None,
243 resource_id: None,
244 position: None,
245 }
246 }
247
248 fn snapshot(&self) -> VirtioGpuScanoutSnapshot {
249 VirtioGpuScanoutSnapshot {
250 width: self.width,
251 height: self.height,
252 has_surface: self.surface_id.is_some(),
253 resource_id: self.resource_id,
254 scanout_type: self.scanout_type,
255 scanout_id: self.scanout_id,
256 display_params: self.display_params.clone(),
257 parent_scanout_id: self.parent_scanout_id,
258 position: self.position,
259 }
260 }
261
262 fn restore(
263 &mut self,
264 snapshot: VirtioGpuScanoutSnapshot,
265 parent_surface_id: Option<u32>,
266 display: &Rc<RefCell<GpuDisplay>>,
267 ) -> VirtioGpuResult {
268 assert_eq!(self.width, snapshot.width);
272 assert_eq!(self.height, snapshot.height);
273 assert_eq!(self.scanout_type, snapshot.scanout_type);
274 assert_eq!(self.scanout_id, snapshot.scanout_id);
275 assert_eq!(self.display_params, snapshot.display_params);
276
277 self.resource_id = snapshot.resource_id;
278 if snapshot.has_surface {
279 self.create_surface(display, parent_surface_id, None)?;
280 } else {
281 self.release_surface(display);
282 }
283 if let Some((x, y)) = snapshot.position {
284 self.set_position(display, x, y)?;
285 }
286
287 Ok(OkNoData)
288 }
289
290 fn create_surface(
291 &mut self,
292 display: &Rc<RefCell<GpuDisplay>>,
293 new_parent_surface_id: Option<u32>,
294 new_scanout_rect: Option<virtio_gpu_rect>,
295 ) -> VirtioGpuResult {
296 let mut need_to_create = false;
297
298 if self.surface_id.is_none() {
299 need_to_create = true;
300 }
301
302 if self.parent_surface_id != new_parent_surface_id {
303 self.parent_surface_id = new_parent_surface_id;
304 need_to_create = true;
305 }
306
307 if let Some(new_scanout_rect) = new_scanout_rect {
308 let new_width = new_scanout_rect.width.to_native();
317 let new_height = new_scanout_rect.height.to_native();
318 if !(self.width == new_width && self.height == new_height) {
319 self.width = new_width;
320 self.height = new_height;
321 need_to_create = true;
322 }
323 }
324
325 if !need_to_create {
326 return Ok(OkNoData);
327 }
328
329 self.release_surface(display);
330
331 let mut display = display.borrow_mut();
332
333 let display_params = match self.display_params.clone() {
334 Some(mut params) => {
335 params.mode = DisplayMode::Windowed(self.width, self.height);
339 params
340 }
341 None => {
342 DisplayParameters::default_with_mode(DisplayMode::Windowed(self.width, self.height))
343 }
344 };
345 let surface_id = display.create_surface(
346 self.parent_surface_id,
347 self.scanout_id,
348 &display_params,
349 self.scanout_type,
350 )?;
351
352 self.surface_id = Some(surface_id);
353
354 Ok(OkNoData)
355 }
356
357 fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
358 if let Some(surface_id) = self.surface_id {
359 display.borrow_mut().release_surface(surface_id);
360 }
361
362 self.surface_id = None;
363 }
364
365 fn set_mouse_mode(
366 &mut self,
367 display: &Rc<RefCell<GpuDisplay>>,
368 mouse_mode: MouseMode,
369 ) -> VirtioGpuResult {
370 if let Some(surface_id) = self.surface_id {
371 display
372 .borrow_mut()
373 .set_mouse_mode(surface_id, mouse_mode)?;
374 }
375 Ok(OkNoData)
376 }
377
378 fn set_position(
379 &mut self,
380 display: &Rc<RefCell<GpuDisplay>>,
381 x: u32,
382 y: u32,
383 ) -> VirtioGpuResult {
384 if let Some(surface_id) = self.surface_id {
385 display.borrow_mut().set_position(surface_id, x, y)?;
386 self.position = Some((x, y));
387 }
388 Ok(OkNoData)
389 }
390
391 fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
392 if let Some(surface_id) = self.surface_id {
393 display.borrow_mut().commit(surface_id)?;
394 }
395 Ok(OkNoData)
396 }
397
398 fn flush(
399 &mut self,
400 display: &Rc<RefCell<GpuDisplay>>,
401 resource: &mut VirtioGpuResource,
402 rutabaga: &mut Rutabaga,
403 ) -> VirtioGpuResult {
404 let surface_id = match self.surface_id {
405 Some(id) => id,
406 _ => return Ok(OkNoData),
407 };
408
409 if let Some(import_id) =
410 VirtioGpuScanout::import_resource_to_display(display, surface_id, resource, rutabaga)
411 {
412 display
413 .borrow_mut()
414 .flip_to(surface_id, import_id, None, None, None)
415 .map_err(|e| {
416 error!("flip_to failed: {:#}", e);
417 ErrUnspec
418 })?;
419 return Ok(OkNoData);
420 }
421
422 let mut display = display.borrow_mut();
424
425 if display.next_buffer_in_use(surface_id) {
427 return Ok(OkNoData);
428 }
429
430 let fb = display
431 .framebuffer_region(surface_id, 0, 0, self.width, self.height)
432 .ok_or(ErrUnspec)?;
433
434 let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height, 0);
435 transfer.stride = fb.stride();
436 let fb_slice = fb.as_volatile_slice();
437 let buf = IoSliceMut::new(
438 unsafe { std::slice::from_raw_parts_mut(fb_slice.as_mut_ptr(), fb_slice.size()) },
440 );
441 rutabaga.transfer_read(0, resource.resource_id, transfer, Some(buf))?;
442
443 display.flip(surface_id);
444 Ok(OkNoData)
445 }
446
447 fn import_resource_to_display(
448 display: &Rc<RefCell<GpuDisplay>>,
449 surface_id: u32,
450 resource: &mut VirtioGpuResource,
451 rutabaga: &mut Rutabaga,
452 ) -> Option<u32> {
453 if let Some(import_id) = resource.display_import {
454 return Some(import_id);
455 }
456 let blob = rutabaga.export_blob(resource.resource_id).ok()?;
457
458 let handle = match blob {
459 RutabagaHandle::AhbInfo(info) => {
460 let import_id = display
461 .borrow_mut()
462 .import_resource(
463 surface_id,
464 DisplayExternalResourceImport::AHardwareBuffer { info },
465 )
466 .ok()?;
467 resource.display_import = Some(import_id);
468 return Some(import_id);
469 }
470 other => RutabagaMagmaHandle::try_from(other).ok()?,
471 };
472 let dmabuf = to_safe_descriptor(handle.os_handle);
473
474 let (width, height, format, stride, offset, modifier) = match resource.scanout_data {
475 Some(data) => (
476 data.width,
477 data.height,
478 data.drm_format,
479 data.strides[0],
480 data.offsets[0],
481 0,
482 ),
483 None => {
484 let query = rutabaga.resource3d_info(resource.resource_id).ok()?;
485 (
486 resource.width,
487 resource.height,
488 query.drm_fourcc,
489 query.strides[0],
490 query.offsets[0],
491 query.modifier,
492 )
493 }
494 };
495
496 let import_id = display
497 .borrow_mut()
498 .import_resource(
499 surface_id,
500 DisplayExternalResourceImport::Dmabuf {
501 descriptor: &dmabuf,
502 offset,
503 stride,
504 modifiers: modifier,
505 width,
506 height,
507 fourcc: format,
508 },
509 )
510 .ok()?;
511 resource.display_import = Some(import_id);
512 Some(import_id)
513 }
514}
515
516pub struct VirtioGpu {
518 display: Rc<RefCell<GpuDisplay>>,
519 scanouts: Map<u32, VirtioGpuScanout>,
520 scanouts_updated: Arc<AtomicBool>,
521 cursor_scanout: VirtioGpuScanout,
522 mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
523 rutabaga: Rutabaga,
524 resources: Map<u32, VirtioGpuResource>,
525 external_blob: bool,
526 fixed_blob_mapping: bool,
527 snapshot_scratch_directory: Option<PathBuf>,
528 deferred_snapshot_load: Option<VirtioGpuSnapshot>,
529}
530
531#[derive(Serialize, Deserialize)]
542pub struct VirtioGpuSnapshot {
543 scanouts: Map<u32, VirtioGpuScanoutSnapshot>,
544 scanouts_updated: bool,
545 cursor_scanout: VirtioGpuScanoutSnapshot,
546 rutabaga: DirectorySnapshot,
547 resources: Map<u32, VirtioGpuResourceSnapshot>,
548}
549
550#[derive(Serialize, Deserialize)]
551struct RutabagaResourceSnapshotSerializable {
552 resource_id: u32,
553
554 width: u32,
555 height: u32,
556 host_mem_size: usize,
557
558 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
559 component_mask: u8,
560 size: u64,
561}
562
563fn sglist_to_rutabaga_iovecs(
564 vecs: &[(GuestAddress, usize)],
565 mem: &GuestMemory,
566) -> Result<Vec<RutabagaIovec>, ()> {
567 if vecs
568 .iter()
569 .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
570 {
571 return Err(());
572 }
573
574 let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
575 for &(addr, len) in vecs {
576 let slice = mem.get_slice_at_addr(addr, len).unwrap();
577 rutabaga_iovecs.push(RutabagaIovec {
578 base: slice.as_mut_ptr() as *mut c_void,
579 len,
580 });
581 }
582 Ok(rutabaga_iovecs)
583}
584
585pub enum ProcessDisplayResult {
586 Success,
587 CloseRequested,
588 Error(GpuDisplayError),
589}
590
591impl VirtioGpu {
592 pub fn new(
594 display: GpuDisplay,
595 display_params: Vec<GpuDisplayParameters>,
596 display_event: Arc<AtomicBool>,
597 rutabaga: Rutabaga,
598 mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
599 external_blob: bool,
600 fixed_blob_mapping: bool,
601 snapshot_scratch_directory: Option<PathBuf>,
602 ) -> Option<VirtioGpu> {
603 let scanouts = display_params
604 .iter()
605 .enumerate()
606 .map(|(display_index, display_param)| {
607 (
608 display_index as u32,
609 VirtioGpuScanout::new_primary(display_index as u32, display_param.clone()),
610 )
611 })
612 .collect::<Map<_, _>>();
613 let cursor_scanout = VirtioGpuScanout::new_cursor();
614
615 Some(VirtioGpu {
616 display: Rc::new(RefCell::new(display)),
617 scanouts,
618 scanouts_updated: display_event,
619 cursor_scanout,
620 mapper,
621 rutabaga,
622 resources: Default::default(),
623 external_blob,
624 fixed_blob_mapping,
625 deferred_snapshot_load: None,
626 snapshot_scratch_directory,
627 })
628 }
629
630 pub fn import_event_device(&mut self, event_device: EventDevice) -> VirtioGpuResult {
632 let mut display = self.display.borrow_mut();
633 let _event_device_id = display.import_event_device(event_device)?;
634 Ok(OkNoData)
635 }
636
637 pub fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
639 &self.display
640 }
641
642 pub fn display_info(&self) -> Vec<(u32, u32, bool)> {
645 (0..VIRTIO_GPU_MAX_SCANOUTS)
646 .map(|scanout_id| scanout_id as u32)
647 .map(|scanout_id| {
648 self.scanouts
649 .get(&scanout_id)
650 .map_or((0, 0, false), |scanout| {
651 (scanout.width, scanout.height, true)
652 })
653 })
654 .collect::<Vec<_>>()
655 }
656
657 fn add_displays(&mut self, displays: Vec<DisplayParameters>) -> GpuControlResult {
659 let requested_num_scanouts = self.scanouts.len() + displays.len();
660 if requested_num_scanouts > VIRTIO_GPU_MAX_SCANOUTS {
661 return GpuControlResult::TooManyDisplays {
662 allowed: VIRTIO_GPU_MAX_SCANOUTS,
663 requested: requested_num_scanouts,
664 };
665 }
666
667 let mut available_scanout_ids = (0..VIRTIO_GPU_MAX_SCANOUTS)
668 .map(|s| s as u32)
669 .collect::<Set<u32>>();
670
671 self.scanouts.keys().for_each(|scanout_id| {
672 available_scanout_ids.remove(scanout_id);
673 });
674
675 for display_params in displays.into_iter() {
676 let new_scanout_id = *available_scanout_ids.iter().next().unwrap();
677 available_scanout_ids.remove(&new_scanout_id);
678
679 self.scanouts.insert(
680 new_scanout_id,
681 VirtioGpuScanout::new_primary(new_scanout_id, display_params),
682 );
683 }
684
685 self.scanouts_updated.store(true, Ordering::Relaxed);
686
687 GpuControlResult::DisplaysUpdated
688 }
689
690 fn list_displays(&self) -> GpuControlResult {
692 GpuControlResult::DisplayList {
693 displays: self
694 .scanouts
695 .iter()
696 .filter_map(|(scanout_id, scanout)| {
697 scanout
698 .display_params
699 .as_ref()
700 .cloned()
701 .map(|display_params| (*scanout_id, display_params))
702 })
703 .collect(),
704 }
705 }
706
707 fn remove_displays(&mut self, display_ids: Vec<u32>) -> GpuControlResult {
709 for display_id in display_ids {
710 if let Some(mut scanout) = self.scanouts.remove(&display_id) {
711 scanout.release_surface(&self.display);
712 } else {
713 return GpuControlResult::NoSuchDisplay { display_id };
714 }
715 }
716
717 self.scanouts_updated.store(true, Ordering::Relaxed);
718 GpuControlResult::DisplaysUpdated
719 }
720
721 fn set_display_mouse_mode(
722 &mut self,
723 display_id: u32,
724 mouse_mode: MouseMode,
725 ) -> GpuControlResult {
726 match self.scanouts.get_mut(&display_id) {
727 Some(scanout) => match scanout.set_mouse_mode(&self.display, mouse_mode) {
728 Ok(_) => GpuControlResult::DisplayMouseModeSet,
729 Err(e) => GpuControlResult::ErrString(e.to_string()),
730 },
731 None => GpuControlResult::NoSuchDisplay { display_id },
732 }
733 }
734
735 pub fn process_gpu_control_command(&mut self, cmd: GpuControlCommand) -> GpuControlResult {
737 match cmd {
738 GpuControlCommand::AddDisplays { displays } => self.add_displays(displays),
739 GpuControlCommand::ListDisplays => self.list_displays(),
740 GpuControlCommand::RemoveDisplays { display_ids } => self.remove_displays(display_ids),
741 GpuControlCommand::SetDisplayMouseMode {
742 display_id,
743 mouse_mode,
744 } => self.set_display_mouse_mode(display_id, mouse_mode),
745 }
746 }
747
748 pub fn process_display(&mut self) -> ProcessDisplayResult {
750 let mut display = self.display.borrow_mut();
751 let result = display.dispatch_events();
752 match result {
753 Ok(_) => (),
754 Err(e) => {
755 error!("failed to dispatch events: {}", e);
756 return ProcessDisplayResult::Error(e);
757 }
758 }
759
760 for scanout in self.scanouts.values() {
761 let close_requested = scanout
762 .surface_id
763 .map(|surface_id| display.close_requested(surface_id))
764 .unwrap_or(false);
765
766 if close_requested {
767 return ProcessDisplayResult::CloseRequested;
768 }
769 }
770
771 ProcessDisplayResult::Success
772 }
773
774 pub fn set_scanout(
776 &mut self,
777 scanout_rect: virtio_gpu_rect,
778 scanout_id: u32,
779 resource_id: u32,
780 scanout_data: Option<VirtioScanoutBlobData>,
781 ) -> VirtioGpuResult {
782 self.update_scanout_resource(
783 SurfaceType::Scanout,
784 Some(scanout_rect),
785 scanout_id,
786 scanout_data,
787 resource_id,
788 )
789 }
790
791 pub fn flush_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
793 if resource_id == 0 {
794 return Ok(OkNoData);
795 }
796
797 #[cfg(windows)]
798 match self.rutabaga.resource_flush(resource_id) {
799 Ok(_) => return Ok(OkNoData),
800 Err(RutabagaError::MagmaGpuError(RutabagaUnsupported)) => {}
801 Err(e) => return Err(ErrRutabaga(e)),
802 }
803
804 let resource = self
805 .resources
806 .get_mut(&resource_id)
807 .ok_or(ErrInvalidResourceId)?;
808
809 let resource_id = match NonZeroU32::new(resource_id) {
811 Some(id) => Some(id),
812 None => return Ok(OkNoData),
813 };
814
815 for scanout in self.scanouts.values_mut() {
816 if scanout.resource_id == resource_id {
817 scanout.flush(&self.display, resource, &mut self.rutabaga)?;
818 }
819 }
820 if self.cursor_scanout.resource_id == resource_id {
821 self.cursor_scanout
822 .flush(&self.display, resource, &mut self.rutabaga)?;
823 }
824
825 Ok(OkNoData)
826 }
827
828 pub fn update_cursor(
831 &mut self,
832 resource_id: u32,
833 scanout_id: u32,
834 x: u32,
835 y: u32,
836 ) -> VirtioGpuResult {
837 self.update_scanout_resource(SurfaceType::Cursor, None, scanout_id, None, resource_id)?;
838
839 self.cursor_scanout.set_position(&self.display, x, y)?;
840
841 self.flush_resource(resource_id)
842 }
843
844 pub fn move_cursor(&mut self, _scanout_id: u32, x: u32, y: u32) -> VirtioGpuResult {
846 self.cursor_scanout.set_position(&self.display, x, y)?;
847 self.cursor_scanout.commit(&self.display)?;
848 Ok(OkNoData)
849 }
850
851 pub fn resource_assign_uuid(&self, resource_id: u32) -> VirtioGpuResult {
853 if !self.resources.contains_key(&resource_id) {
854 return Err(ErrInvalidResourceId);
855 }
856
857 let mut uuid: [u8; 16] = [0; 16];
861 for (idx, byte) in resource_id.to_be_bytes().iter().enumerate() {
862 uuid[12 + idx] = *byte;
863 }
864 Ok(OkResourceUuid { uuid })
865 }
866
867 pub fn export_resource(&mut self, resource_id: u32) -> ResourceResponse {
869 let handle = match self.rutabaga.export_blob(resource_id) {
870 Ok(handle) => {
871 let Ok(handle) = RutabagaMagmaHandle::try_from(handle) else {
872 return ResourceResponse::Invalid;
873 };
874 to_safe_descriptor(handle.os_handle)
875 }
876 Err(_) => return ResourceResponse::Invalid,
877 };
878
879 let q = match self.rutabaga.resource3d_info(resource_id) {
880 Ok(query) => query,
881 Err(_) => return ResourceResponse::Invalid,
882 };
883
884 let guest_cpu_mappable = self
887 .resources
888 .get(&resource_id)
889 .map(|r| r.guest_cpu_mappable)
890 .unwrap_or(false);
891
892 ResourceResponse::Resource(ResourceInfo::Buffer(BufferInfo {
893 handle,
894 planes: [
895 PlaneInfo {
896 offset: q.offsets[0],
897 stride: q.strides[0],
898 },
899 PlaneInfo {
900 offset: q.offsets[1],
901 stride: q.strides[1],
902 },
903 PlaneInfo {
904 offset: q.offsets[2],
905 stride: q.strides[2],
906 },
907 PlaneInfo {
908 offset: q.offsets[3],
909 stride: q.strides[3],
910 },
911 ],
912 modifier: q.modifier,
913 guest_cpu_mappable,
914 }))
915 }
916
917 pub fn export_fence(&mut self, fence_id: u64) -> ResourceResponse {
919 match self.rutabaga.export_fence(fence_id) {
920 Ok(handle) => ResourceResponse::Resource(ResourceInfo::Fence {
921 handle: to_safe_descriptor(handle.os_handle),
922 }),
923 Err(_) => ResourceResponse::Invalid,
924 }
925 }
926
927 pub fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
929 if let Ok((capset_id, version, size)) = self.rutabaga.get_capset_info(index) {
930 Ok(OkCapsetInfo {
931 capset_id,
932 version,
933 size,
934 })
935 } else {
936 base::warn!(
939 "virtio-gpu get_capset_info(index={}) failed. intentionally poisoning response",
940 index
941 );
942 Ok(OkCapsetInfo {
943 capset_id: u32::MAX,
944 version: 0,
945 size: 0,
946 })
947 }
948 }
949
950 pub fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
952 let capset = self.rutabaga.get_capset(capset_id, version)?;
953 Ok(OkCapset(capset))
954 }
955
956 pub fn force_ctx_0(&self) {
958 self.rutabaga.force_ctx_0()
959 }
960
961 pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
964 self.rutabaga.create_fence(rutabaga_fence)?;
965 Ok(OkNoData)
966 }
967
968 pub fn event_poll(&self) {
970 self.rutabaga.event_poll();
971 }
972
973 pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
976 self.rutabaga.poll_descriptor().map(to_safe_descriptor)
977 }
978
979 pub fn resource_create_3d(
981 &mut self,
982 resource_id: u32,
983 resource_create_3d: ResourceCreate3D,
984 ) -> VirtioGpuResult {
985 self.rutabaga
986 .resource_create_3d(resource_id, resource_create_3d)?;
987
988 let resource = VirtioGpuResource::new(
989 resource_id,
990 resource_create_3d.width,
991 resource_create_3d.height,
992 0,
993 false,
994 );
995
996 self.resources.insert(resource_id, resource);
998 Ok(self.result_from_query(resource_id))
999 }
1000
1001 pub fn attach_backing(
1005 &mut self,
1006 resource_id: u32,
1007 mem: &GuestMemory,
1008 vecs: Vec<(GuestAddress, usize)>,
1009 ) -> VirtioGpuResult {
1010 let resource = self
1011 .resources
1012 .get_mut(&resource_id)
1013 .ok_or(ErrInvalidResourceId)?;
1014
1015 let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
1016 self.rutabaga.attach_backing(resource_id, rutabaga_iovecs)?;
1017 resource.backing_iovecs = Some(vecs);
1018 Ok(OkNoData)
1019 }
1020
1021 pub fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
1023 let resource = self
1024 .resources
1025 .get_mut(&resource_id)
1026 .ok_or(ErrInvalidResourceId)?;
1027
1028 self.rutabaga.detach_backing(resource_id)?;
1029 resource.backing_iovecs = None;
1030 Ok(OkNoData)
1031 }
1032
1033 pub fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
1035 let resource = self
1036 .resources
1037 .remove(&resource_id)
1038 .ok_or(ErrInvalidResourceId)?;
1039
1040 if resource.rutabaga_external_mapping {
1041 self.rutabaga.unmap(resource_id)?;
1042 }
1043
1044 self.rutabaga.unref_resource(resource_id)?;
1045 Ok(OkNoData)
1046 }
1047
1048 pub fn transfer_write(
1050 &mut self,
1051 ctx_id: u32,
1052 resource_id: u32,
1053 transfer: Transfer3D,
1054 ) -> VirtioGpuResult {
1055 self.rutabaga
1056 .transfer_write(ctx_id, resource_id, transfer, None)?;
1057 Ok(OkNoData)
1058 }
1059
1060 pub fn transfer_read(
1066 &mut self,
1067 ctx_id: u32,
1068 resource_id: u32,
1069 transfer: Transfer3D,
1070 buf: Option<VolatileSlice>,
1071 ) -> VirtioGpuResult {
1072 let buf = buf.map(|vs| {
1073 IoSliceMut::new(
1074 unsafe { std::slice::from_raw_parts_mut(vs.as_mut_ptr(), vs.size()) },
1076 )
1077 });
1078 self.rutabaga
1079 .transfer_read(ctx_id, resource_id, transfer, buf)?;
1080 Ok(OkNoData)
1081 }
1082
1083 pub fn resource_create_blob(
1085 &mut self,
1086 ctx_id: u32,
1087 resource_id: u32,
1088 resource_create_blob: ResourceCreateBlob,
1089 vecs: Vec<(GuestAddress, usize)>,
1090 mem: &GuestMemory,
1091 ) -> VirtioGpuResult {
1092 let mut rutabaga_iovecs = None;
1093
1094 if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
1095 rutabaga_iovecs =
1096 Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
1097 }
1098
1099 self.rutabaga.resource_create_blob(
1100 ctx_id,
1101 resource_id,
1102 resource_create_blob,
1103 rutabaga_iovecs,
1104 None,
1105 )?;
1106
1107 let guest_cpu_mappable =
1108 (resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE) != 0;
1109 let resource = VirtioGpuResource::new(
1110 resource_id,
1111 0,
1112 0,
1113 resource_create_blob.size,
1114 guest_cpu_mappable,
1115 );
1116
1117 self.resources.insert(resource_id, resource);
1119 Ok(self.result_from_query(resource_id))
1120 }
1121
1122 pub fn resource_map_blob(
1129 &mut self,
1130 resource_id: u32,
1131 offset: u64,
1132 ) -> anyhow::Result<GpuResponse> {
1133 let resource = self
1134 .resources
1135 .get_mut(&resource_id)
1136 .with_context(|| format!("can't find the resource with id {resource_id}"))
1137 .context(ErrInvalidResourceId)?;
1138
1139 let map_info = self
1140 .rutabaga
1141 .map_info(resource_id)
1142 .context("failed to retrieve the map info for the resource")
1143 .context(ErrUnspec)?;
1144
1145 let mut source: Option<VmMemorySource> = None;
1146 if let Ok(export) = self.rutabaga.export_blob(resource_id) {
1147 let export = RutabagaMagmaHandle::try_from(export)
1148 .context("failed to retrieve the handle info")
1149 .context(ErrUnspec)?;
1150 if let Ok(vulkan_info) = self.rutabaga.vulkan_info(resource_id) {
1151 source = Some(VmMemorySource::Vulkan {
1152 descriptor: to_safe_descriptor(export.os_handle),
1153 handle_type: export.handle_type,
1154 memory_idx: vulkan_info.memory_idx,
1155 device_uuid: vulkan_info.device_id.device_uuid,
1156 driver_uuid: vulkan_info.device_id.driver_uuid,
1157 size: resource.size,
1158 });
1159 } else if export.handle_type != RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD {
1160 source = Some(VmMemorySource::Descriptor {
1161 descriptor: to_safe_descriptor(export.os_handle),
1162 offset: 0,
1163 size: resource.size,
1164 });
1165 }
1166 }
1167
1168 if source.is_none() {
1171 anyhow::ensure!(
1172 !self.external_blob,
1173 "can't fallback to external mapping with external blob enabled"
1174 );
1175 anyhow::ensure!(
1176 !self.fixed_blob_mapping,
1177 "can't fallback to external mapping with fixed blob mapping enabled"
1178 );
1179
1180 let mapping = self.rutabaga.map(resource_id).map_err(|e| {
1181 anyhow::anyhow!("failed to map via rutabaga").context(GpuResponse::ErrRutabaga(e))
1182 })?;
1183 resource.rutabaga_external_mapping = true;
1185 source = Some(VmMemorySource::ExternalMapping {
1186 ptr: mapping.ptr,
1187 size: mapping.size,
1188 });
1189 };
1190
1191 let prot = match map_info & RUTABAGA_MAP_ACCESS_MASK {
1192 RUTABAGA_MAP_ACCESS_READ => Protection::read(),
1193 RUTABAGA_MAP_ACCESS_WRITE => Protection::write(),
1194 RUTABAGA_MAP_ACCESS_RW => Protection::read_write(),
1195 access_flags => {
1196 return Err(anyhow::anyhow!(
1197 "unrecognized access flags {:#x}",
1198 access_flags
1199 ))
1200 .context(ErrUnspec)
1201 }
1202 };
1203
1204 let cache = if cfg!(feature = "noncoherent-dma")
1205 && map_info & RUTABAGA_MAP_CACHE_MASK != RUTABAGA_MAP_CACHE_CACHED
1206 {
1207 MemCacheType::CacheNonCoherent
1208 } else {
1209 MemCacheType::CacheCoherent
1210 };
1211
1212 self.mapper
1213 .lock()
1214 .as_mut()
1215 .expect("No backend request connection found")
1216 .add_mapping(source.unwrap(), offset, prot, cache)
1217 .context("failed to add the memory mapping")
1218 .context(ErrUnspec)?;
1219
1220 resource.shmem_offset = Some(offset);
1221 Ok(OkMapInfo {
1223 map_info: map_info & RUTABAGA_MAP_CACHE_MASK,
1224 })
1225 }
1226
1227 pub fn resource_unmap_blob(&mut self, resource_id: u32) -> VirtioGpuResult {
1229 let resource = self
1230 .resources
1231 .get_mut(&resource_id)
1232 .ok_or(ErrInvalidResourceId)?;
1233
1234 let shmem_offset = resource.shmem_offset.ok_or(ErrUnspec)?;
1235 self.mapper
1236 .lock()
1237 .as_mut()
1238 .expect("No backend request connection found")
1239 .remove_mapping(shmem_offset)
1240 .map_err(|_| ErrUnspec)?;
1241 resource.shmem_offset = None;
1242
1243 if resource.rutabaga_external_mapping {
1244 self.rutabaga.unmap(resource_id)?;
1245 resource.rutabaga_external_mapping = false;
1246 }
1247
1248 Ok(OkNoData)
1249 }
1250
1251 pub fn get_edid(&self, scanout_id: u32) -> VirtioGpuResult {
1254 let display_info = match self.scanouts.get(&scanout_id) {
1255 Some(scanout) => {
1256 let params = scanout.display_params.as_ref().unwrap();
1258 DisplayInfo::new(params)
1259 }
1260 None => DisplayInfo::new(&Default::default()),
1261 };
1262 EdidBytes::new(&display_info)
1263 }
1264
1265 pub fn create_context(
1267 &mut self,
1268 ctx_id: u32,
1269 context_init: u32,
1270 context_name: Option<&str>,
1271 ) -> VirtioGpuResult {
1272 self.rutabaga
1273 .create_context(ctx_id, context_init, context_name)?;
1274 Ok(OkNoData)
1275 }
1276
1277 pub fn destroy_context(&mut self, ctx_id: u32) -> VirtioGpuResult {
1279 self.rutabaga.destroy_context(ctx_id)?;
1280 Ok(OkNoData)
1281 }
1282
1283 pub fn context_attach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1285 self.rutabaga.context_attach_resource(ctx_id, resource_id)?;
1286 Ok(OkNoData)
1287 }
1288
1289 pub fn context_detach_resource(&mut self, ctx_id: u32, resource_id: u32) -> VirtioGpuResult {
1291 self.rutabaga.context_detach_resource(ctx_id, resource_id)?;
1292 Ok(OkNoData)
1293 }
1294
1295 pub fn submit_command(
1297 &mut self,
1298 ctx_id: u32,
1299 commands: &mut [u8],
1300 fence_ids: &[u64],
1301 ) -> VirtioGpuResult {
1302 self.rutabaga.submit_command(ctx_id, commands, fence_ids)?;
1303 Ok(OkNoData)
1304 }
1305
1306 fn result_from_query(&mut self, resource_id: u32) -> GpuResponse {
1308 match self.rutabaga.resource3d_info(resource_id) {
1309 Ok(query) => {
1310 let mut plane_info = Vec::with_capacity(4);
1311 for plane_index in 0..4 {
1312 plane_info.push(GpuResponsePlaneInfo {
1313 stride: query.strides[plane_index],
1314 offset: query.offsets[plane_index],
1315 });
1316 }
1317 let format_modifier = query.modifier;
1318 OkResourcePlaneInfo {
1319 format_modifier,
1320 plane_info,
1321 }
1322 }
1323 Err(_) => OkNoData,
1324 }
1325 }
1326
1327 fn update_scanout_resource(
1328 &mut self,
1329 scanout_type: SurfaceType,
1330 scanout_rect: Option<virtio_gpu_rect>,
1331 scanout_id: u32,
1332 scanout_data: Option<VirtioScanoutBlobData>,
1333 resource_id: u32,
1334 ) -> VirtioGpuResult {
1335 let scanout: &mut VirtioGpuScanout;
1336 let mut scanout_parent_surface_id = None;
1337
1338 match scanout_type {
1339 SurfaceType::Cursor => {
1340 let parent_scanout_id = scanout_id;
1341
1342 scanout_parent_surface_id = self
1343 .scanouts
1344 .get(&parent_scanout_id)
1345 .ok_or(ErrInvalidScanoutId)
1346 .map(|parent_scanout| parent_scanout.surface_id)?;
1347
1348 scanout = &mut self.cursor_scanout;
1349 }
1350 SurfaceType::Scanout => {
1351 scanout = self
1352 .scanouts
1353 .get_mut(&scanout_id)
1354 .ok_or(ErrInvalidScanoutId)?;
1355 }
1356 };
1357
1358 if resource_id == 0 {
1360 if scanout.resource_id.is_some() {
1362 scanout.release_surface(&self.display);
1363 }
1364
1365 scanout.resource_id = None;
1366 return Ok(OkNoData);
1367 }
1368
1369 let resource = self
1370 .resources
1371 .get_mut(&resource_id)
1372 .ok_or(ErrInvalidResourceId)?;
1373
1374 match scanout_type {
1376 SurfaceType::Cursor => {
1377 if let Some(scanout_parent_surface_id) = scanout_parent_surface_id {
1378 scanout.create_surface(
1379 &self.display,
1380 Some(scanout_parent_surface_id),
1381 scanout_rect,
1382 )?;
1383 }
1384 }
1385 SurfaceType::Scanout => {
1386 scanout.create_surface(&self.display, None, scanout_rect)?;
1387 }
1388 }
1389
1390 let info = scanout_data.map(|scanout_data| Resource3DInfo {
1391 width: scanout_data.width,
1392 height: scanout_data.height,
1393 drm_fourcc: scanout_data.drm_format,
1394 strides: scanout_data.strides,
1395 offsets: scanout_data.offsets,
1396 modifier: 0,
1397 });
1398
1399 let _ = self.rutabaga.set_scanout(scanout_id, resource_id, info);
1400
1401 resource.scanout_data = scanout_data;
1402
1403 let resource_id = match NonZeroU32::new(resource_id) {
1405 Some(id) => id,
1406 None => return Ok(OkNoData),
1407 };
1408 scanout.resource_id = Some(resource_id);
1409
1410 Ok(OkNoData)
1411 }
1412
1413 pub fn suspend(&self) -> anyhow::Result<()> {
1414 self.rutabaga
1415 .suspend()
1416 .context("failed to suspend rutabaga")
1417 }
1418
1419 pub fn snapshot(&self) -> anyhow::Result<VirtioGpuSnapshot> {
1420 let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1421 tempfile::tempdir_in(dir).with_context(|| {
1422 format!(
1423 "failed to create tempdir in {} for gpu rutabaga snapshot",
1424 dir.display()
1425 )
1426 })?
1427 } else {
1428 tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1429 };
1430 let snapshot_directory = snapshot_directory_tempdir.path();
1431
1432 Ok(VirtioGpuSnapshot {
1433 scanouts: self
1434 .scanouts
1435 .iter()
1436 .map(|(i, s)| (*i, s.snapshot()))
1437 .collect(),
1438 scanouts_updated: self.scanouts_updated.load(Ordering::SeqCst),
1439 cursor_scanout: self.cursor_scanout.snapshot(),
1440 rutabaga: {
1441 self.rutabaga
1442 .snapshot(snapshot_directory)
1443 .context("failed to snapshot rutabaga")?;
1444
1445 pack_directory_to_snapshot(snapshot_directory).with_context(|| {
1446 format!(
1447 "failed to pack rutabaga snapshot from {}",
1448 snapshot_directory.display()
1449 )
1450 })?
1451 },
1452 resources: self
1453 .resources
1454 .iter()
1455 .map(|(i, r)| (*i, r.snapshot()))
1456 .collect(),
1457 })
1458 }
1459
1460 pub fn restore(&mut self, snapshot: VirtioGpuSnapshot) -> anyhow::Result<()> {
1461 self.deferred_snapshot_load = Some(snapshot);
1462 Ok(())
1463 }
1464
1465 pub fn resume(&mut self, mem: &GuestMemory) -> anyhow::Result<()> {
1466 if let Some(snapshot) = self.deferred_snapshot_load.take() {
1467 assert!(self.scanouts.keys().eq(snapshot.scanouts.keys()));
1468 for (i, s) in snapshot.scanouts.into_iter() {
1469 self.scanouts
1470 .get_mut(&i)
1471 .unwrap()
1472 .restore(
1473 s,
1474 None,
1476 &self.display,
1477 )
1478 .context("failed to restore scanouts")?;
1479 }
1480 self.scanouts_updated
1481 .store(snapshot.scanouts_updated, Ordering::SeqCst);
1482
1483 let cursor_parent_surface_id = snapshot
1484 .cursor_scanout
1485 .parent_scanout_id
1486 .and_then(|i| self.scanouts.get(&i).unwrap().surface_id);
1487 self.cursor_scanout
1488 .restore(
1489 snapshot.cursor_scanout,
1490 cursor_parent_surface_id,
1491 &self.display,
1492 )
1493 .context("failed to restore cursor scanout")?;
1494
1495 let snapshot_directory_tempdir = if let Some(dir) = &self.snapshot_scratch_directory {
1496 tempfile::tempdir_in(dir).with_context(|| {
1497 format!(
1498 "failed to create tempdir in {} for gpu rutabaga snapshot",
1499 dir.display()
1500 )
1501 })?
1502 } else {
1503 tempfile::tempdir().context("failed to create tempdir for gpu rutabaga snapshot")?
1504 };
1505 let snapshot_directory = snapshot_directory_tempdir.path();
1506
1507 unpack_snapshot_to_directory(snapshot_directory, snapshot.rutabaga).with_context(
1508 || {
1509 format!(
1510 "failed to unpack rutabaga snapshot to {}",
1511 snapshot_directory.display()
1512 )
1513 },
1514 )?;
1515 self.rutabaga
1516 .restore(snapshot_directory)
1517 .context("failed to restore rutabaga")?;
1518
1519 for (id, s) in snapshot.resources.into_iter() {
1520 let backing_iovecs = s.backing_iovecs.clone();
1521 let shmem_offset = s.shmem_offset;
1522 self.resources.insert(id, VirtioGpuResource::restore(s));
1523 if let Some(backing_iovecs) = backing_iovecs {
1524 self.attach_backing(id, mem, backing_iovecs)
1525 .context("failed to restore resource backing")?;
1526 }
1527 if let Some(shmem_offset) = shmem_offset {
1528 self.resource_map_blob(id, shmem_offset)
1529 .context("failed to restore resource mapping")?;
1530 }
1531 }
1532 }
1533
1534 self.rutabaga.resume().context("failed to resume rutabaga")
1535 }
1536}