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::RutabagaMesaHandle;
40#[cfg(windows)]
41use rutabaga_gfx::RutabagaUnsupported;
42use rutabaga_gfx::Transfer3D;
43use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_DMABUF;
44use rutabaga_gfx::RUTABAGA_HANDLE_TYPE_MEM_OPAQUE_FD;
45use rutabaga_gfx::RUTABAGA_MAP_ACCESS_MASK;
46use rutabaga_gfx::RUTABAGA_MAP_ACCESS_READ;
47use rutabaga_gfx::RUTABAGA_MAP_ACCESS_RW;
48use rutabaga_gfx::RUTABAGA_MAP_ACCESS_WRITE;
49use rutabaga_gfx::RUTABAGA_MAP_CACHE_CACHED;
50use rutabaga_gfx::RUTABAGA_MAP_CACHE_MASK;
51use serde::Deserialize;
52use serde::Serialize;
53use sync::Mutex;
54use vm_control::gpu::DisplayMode;
55use vm_control::gpu::DisplayParameters;
56use vm_control::gpu::GpuControlCommand;
57use vm_control::gpu::GpuControlResult;
58use vm_control::gpu::MouseMode;
59use vm_control::VmMemorySource;
60use vm_memory::udmabuf::UdmabufDriver;
61use vm_memory::udmabuf::UdmabufDriverTrait;
62use vm_memory::GuestAddress;
63use vm_memory::GuestMemory;
64
65use super::protocol::virtio_gpu_rect;
66use super::protocol::GpuResponse;
67use super::protocol::GpuResponse::*;
68use super::protocol::GpuResponsePlaneInfo;
69use super::protocol::VirtioGpuResult;
70use super::protocol::VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE;
71use super::protocol::VIRTIO_GPU_BLOB_MEM_HOST3D;
72use super::VirtioScanoutBlobData;
73use crate::virtio::gpu::edid::DisplayInfo;
74use crate::virtio::gpu::edid::EdidBytes;
75use crate::virtio::gpu::snapshot::pack_directory_to_snapshot;
76use crate::virtio::gpu::snapshot::unpack_snapshot_to_directory;
77use crate::virtio::gpu::snapshot::DirectorySnapshot;
78use crate::virtio::gpu::GpuDisplayParameters;
79use crate::virtio::gpu::VIRTIO_GPU_MAX_SCANOUTS;
80use crate::virtio::resource_bridge::BufferInfo;
81use crate::virtio::resource_bridge::PlaneInfo;
82use crate::virtio::resource_bridge::ResourceInfo;
83use crate::virtio::resource_bridge::ResourceResponse;
84use crate::virtio::SharedMemoryMapper;
85
86pub fn to_rutabaga_descriptor(s: SafeDescriptor) -> RutabagaDescriptor {
87 unsafe { RutabagaDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
90}
91
92fn to_safe_descriptor(r: RutabagaDescriptor) -> SafeDescriptor {
93 unsafe { SafeDescriptor::from_raw_descriptor(r.into_raw_descriptor()) }
96}
97
98struct VirtioGpuResource {
99 resource_id: u32,
100 width: u32,
101 height: u32,
102 size: u64,
103 shmem_offset: Option<u64>,
104 scanout_data: Option<VirtioScanoutBlobData>,
105 display_import: Option<u32>,
106 rutabaga_external_mapping: bool,
107
108 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
111}
112
113#[derive(Serialize, Deserialize)]
114struct VirtioGpuResourceSnapshot {
115 resource_id: u32,
116 width: u32,
117 height: u32,
118 size: u64,
119
120 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
121 shmem_offset: Option<u64>,
122 scanout_data: Option<VirtioScanoutBlobData>,
123}
124
125impl VirtioGpuResource {
126 pub fn new(resource_id: u32, width: u32, height: u32, size: u64) -> VirtioGpuResource {
129 VirtioGpuResource {
130 resource_id,
131 width,
132 height,
133 size,
134 shmem_offset: None,
135 scanout_data: None,
136 display_import: None,
137 rutabaga_external_mapping: false,
138 backing_iovecs: None,
139 }
140 }
141
142 fn snapshot(&self) -> VirtioGpuResourceSnapshot {
143 assert!(self.display_import.is_none());
145
146 VirtioGpuResourceSnapshot {
147 resource_id: self.resource_id,
148 width: self.width,
149 height: self.height,
150 size: self.size,
151 backing_iovecs: self.backing_iovecs.clone(),
152 shmem_offset: self.shmem_offset,
153 scanout_data: self.scanout_data,
154 }
155 }
156
157 fn restore(s: VirtioGpuResourceSnapshot) -> Self {
158 let mut resource = VirtioGpuResource::new(s.resource_id, s.width, s.height, s.size);
159 resource.backing_iovecs = s.backing_iovecs;
160 resource.scanout_data = s.scanout_data;
161 resource
162 }
163}
164
165struct VirtioGpuScanout {
166 width: u32,
167 height: u32,
168 scanout_type: SurfaceType,
169 scanout_id: Option<u32>,
171 display_params: Option<GpuDisplayParameters>,
173 parent_surface_id: Option<u32>,
175
176 surface_id: Option<u32>,
177 parent_scanout_id: Option<u32>,
178
179 resource_id: Option<NonZeroU32>,
180 position: Option<(u32, u32)>,
181}
182
183#[derive(Serialize, Deserialize)]
184struct VirtioGpuScanoutSnapshot {
185 width: u32,
186 height: u32,
187 scanout_type: SurfaceType,
188 scanout_id: Option<u32>,
189 display_params: Option<GpuDisplayParameters>,
190
191 has_surface: bool,
195 parent_scanout_id: Option<u32>,
196
197 resource_id: Option<NonZeroU32>,
198 position: Option<(u32, u32)>,
199}
200
201impl VirtioGpuScanout {
202 fn new_primary(scanout_id: u32, params: GpuDisplayParameters) -> VirtioGpuScanout {
203 let (width, height) = params.get_virtual_display_size();
204 VirtioGpuScanout {
205 width,
206 height,
207 scanout_type: SurfaceType::Scanout,
208 scanout_id: Some(scanout_id),
209 display_params: Some(params),
210 parent_surface_id: None,
211 surface_id: None,
212 parent_scanout_id: None,
213 resource_id: None,
214 position: None,
215 }
216 }
217
218 fn new_cursor() -> VirtioGpuScanout {
219 VirtioGpuScanout {
222 width: 64,
223 height: 64,
224 scanout_type: SurfaceType::Cursor,
225 scanout_id: None,
226 display_params: None,
227 parent_surface_id: None,
228 surface_id: None,
229 parent_scanout_id: None,
230 resource_id: None,
231 position: None,
232 }
233 }
234
235 fn snapshot(&self) -> VirtioGpuScanoutSnapshot {
236 VirtioGpuScanoutSnapshot {
237 width: self.width,
238 height: self.height,
239 has_surface: self.surface_id.is_some(),
240 resource_id: self.resource_id,
241 scanout_type: self.scanout_type,
242 scanout_id: self.scanout_id,
243 display_params: self.display_params.clone(),
244 parent_scanout_id: self.parent_scanout_id,
245 position: self.position,
246 }
247 }
248
249 fn restore(
250 &mut self,
251 snapshot: VirtioGpuScanoutSnapshot,
252 parent_surface_id: Option<u32>,
253 display: &Rc<RefCell<GpuDisplay>>,
254 ) -> VirtioGpuResult {
255 assert_eq!(self.width, snapshot.width);
259 assert_eq!(self.height, snapshot.height);
260 assert_eq!(self.scanout_type, snapshot.scanout_type);
261 assert_eq!(self.scanout_id, snapshot.scanout_id);
262 assert_eq!(self.display_params, snapshot.display_params);
263
264 self.resource_id = snapshot.resource_id;
265 if snapshot.has_surface {
266 self.create_surface(display, parent_surface_id, None)?;
267 } else {
268 self.release_surface(display);
269 }
270 if let Some((x, y)) = snapshot.position {
271 self.set_position(display, x, y)?;
272 }
273
274 Ok(OkNoData)
275 }
276
277 fn create_surface(
278 &mut self,
279 display: &Rc<RefCell<GpuDisplay>>,
280 new_parent_surface_id: Option<u32>,
281 new_scanout_rect: Option<virtio_gpu_rect>,
282 ) -> VirtioGpuResult {
283 let mut need_to_create = false;
284
285 if self.surface_id.is_none() {
286 need_to_create = true;
287 }
288
289 if self.parent_surface_id != new_parent_surface_id {
290 self.parent_surface_id = new_parent_surface_id;
291 need_to_create = true;
292 }
293
294 if let Some(new_scanout_rect) = new_scanout_rect {
295 let new_width = new_scanout_rect.width.to_native();
304 let new_height = new_scanout_rect.height.to_native();
305 if !(self.width == new_width && self.height == new_height) {
306 self.width = new_width;
307 self.height = new_height;
308 need_to_create = true;
309 }
310 }
311
312 if !need_to_create {
313 return Ok(OkNoData);
314 }
315
316 self.release_surface(display);
317
318 let mut display = display.borrow_mut();
319
320 let display_params = match self.display_params.clone() {
321 Some(mut params) => {
322 params.mode = DisplayMode::Windowed(self.width, self.height);
326 params
327 }
328 None => {
329 DisplayParameters::default_with_mode(DisplayMode::Windowed(self.width, self.height))
330 }
331 };
332 let surface_id = display.create_surface(
333 self.parent_surface_id,
334 self.scanout_id,
335 &display_params,
336 self.scanout_type,
337 )?;
338
339 self.surface_id = Some(surface_id);
340
341 Ok(OkNoData)
342 }
343
344 fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
345 if let Some(surface_id) = self.surface_id {
346 display.borrow_mut().release_surface(surface_id);
347 }
348
349 self.surface_id = None;
350 }
351
352 fn set_mouse_mode(
353 &mut self,
354 display: &Rc<RefCell<GpuDisplay>>,
355 mouse_mode: MouseMode,
356 ) -> VirtioGpuResult {
357 if let Some(surface_id) = self.surface_id {
358 display
359 .borrow_mut()
360 .set_mouse_mode(surface_id, mouse_mode)?;
361 }
362 Ok(OkNoData)
363 }
364
365 fn set_position(
366 &mut self,
367 display: &Rc<RefCell<GpuDisplay>>,
368 x: u32,
369 y: u32,
370 ) -> VirtioGpuResult {
371 if let Some(surface_id) = self.surface_id {
372 display.borrow_mut().set_position(surface_id, x, y)?;
373 self.position = Some((x, y));
374 }
375 Ok(OkNoData)
376 }
377
378 fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
379 if let Some(surface_id) = self.surface_id {
380 display.borrow_mut().commit(surface_id)?;
381 }
382 Ok(OkNoData)
383 }
384
385 fn flush(
386 &mut self,
387 display: &Rc<RefCell<GpuDisplay>>,
388 resource: &mut VirtioGpuResource,
389 rutabaga: &mut Rutabaga,
390 ) -> VirtioGpuResult {
391 let surface_id = match self.surface_id {
392 Some(id) => id,
393 _ => return Ok(OkNoData),
394 };
395
396 if let Some(import_id) =
397 VirtioGpuScanout::import_resource_to_display(display, surface_id, resource, rutabaga)
398 {
399 display
400 .borrow_mut()
401 .flip_to(surface_id, import_id, None, None, None)
402 .map_err(|e| {
403 error!("flip_to failed: {:#}", e);
404 ErrUnspec
405 })?;
406 return Ok(OkNoData);
407 }
408
409 let mut display = display.borrow_mut();
411
412 if display.next_buffer_in_use(surface_id) {
414 return Ok(OkNoData);
415 }
416
417 let fb = display
418 .framebuffer_region(surface_id, 0, 0, self.width, self.height)
419 .ok_or(ErrUnspec)?;
420
421 let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height, 0);
422 transfer.stride = fb.stride();
423 let fb_slice = fb.as_volatile_slice();
424 let buf = IoSliceMut::new(
425 unsafe { std::slice::from_raw_parts_mut(fb_slice.as_mut_ptr(), fb_slice.size()) },
427 );
428 rutabaga.transfer_read(0, resource.resource_id, transfer, Some(buf))?;
429
430 display.flip(surface_id);
431 Ok(OkNoData)
432 }
433
434 fn import_resource_to_display(
435 display: &Rc<RefCell<GpuDisplay>>,
436 surface_id: u32,
437 resource: &mut VirtioGpuResource,
438 rutabaga: &mut Rutabaga,
439 ) -> Option<u32> {
440 if let Some(import_id) = resource.display_import {
441 return Some(import_id);
442 }
443 let blob = rutabaga.export_blob(resource.resource_id).ok()?;
444
445 let handle = match blob {
446 RutabagaHandle::AhbInfo(info) => {
447 let import_id = display
448 .borrow_mut()
449 .import_resource(
450 surface_id,
451 DisplayExternalResourceImport::AHardwareBuffer { info },
452 )
453 .ok()?;
454 resource.display_import = Some(import_id);
455 return Some(import_id);
456 }
457 other => RutabagaMesaHandle::try_from(other).ok()?,
458 };
459 let dmabuf = to_safe_descriptor(handle.os_handle);
460
461 let (width, height, format, stride, offset, modifier) = match resource.scanout_data {
462 Some(data) => (
463 data.width,
464 data.height,
465 data.drm_format,
466 data.strides[0],
467 data.offsets[0],
468 0,
469 ),
470 None => {
471 let query = rutabaga.resource3d_info(resource.resource_id).ok()?;
472 (
473 resource.width,
474 resource.height,
475 query.drm_fourcc,
476 query.strides[0],
477 query.offsets[0],
478 query.modifier,
479 )
480 }
481 };
482
483 let import_id = display
484 .borrow_mut()
485 .import_resource(
486 surface_id,
487 DisplayExternalResourceImport::Dmabuf {
488 descriptor: &dmabuf,
489 offset,
490 stride,
491 modifiers: modifier,
492 width,
493 height,
494 fourcc: format,
495 },
496 )
497 .ok()?;
498 resource.display_import = Some(import_id);
499 Some(import_id)
500 }
501}
502
503pub struct VirtioGpu {
505 display: Rc<RefCell<GpuDisplay>>,
506 scanouts: Map<u32, VirtioGpuScanout>,
507 scanouts_updated: Arc<AtomicBool>,
508 cursor_scanout: VirtioGpuScanout,
509 mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
510 rutabaga: Rutabaga,
511 resources: Map<u32, VirtioGpuResource>,
512 external_blob: bool,
513 fixed_blob_mapping: bool,
514 udmabuf_driver: Option<UdmabufDriver>,
515 snapshot_scratch_directory: Option<PathBuf>,
516 deferred_snapshot_load: Option<VirtioGpuSnapshot>,
517}
518
519#[derive(Serialize, Deserialize)]
531pub struct VirtioGpuSnapshot {
532 scanouts: Map<u32, VirtioGpuScanoutSnapshot>,
533 scanouts_updated: bool,
534 cursor_scanout: VirtioGpuScanoutSnapshot,
535 rutabaga: DirectorySnapshot,
536 resources: Map<u32, VirtioGpuResourceSnapshot>,
537}
538
539#[derive(Serialize, Deserialize)]
540struct RutabagaResourceSnapshotSerializable {
541 resource_id: u32,
542
543 width: u32,
544 height: u32,
545 host_mem_size: usize,
546
547 backing_iovecs: Option<Vec<(GuestAddress, usize)>>,
548 component_mask: u8,
549 size: u64,
550}
551
552fn sglist_to_rutabaga_iovecs(
553 vecs: &[(GuestAddress, usize)],
554 mem: &GuestMemory,
555) -> Result<Vec<RutabagaIovec>, ()> {
556 if vecs
557 .iter()
558 .any(|&(addr, len)| mem.get_slice_at_addr(addr, len).is_err())
559 {
560 return Err(());
561 }
562
563 let mut rutabaga_iovecs: Vec<RutabagaIovec> = Vec::new();
564 for &(addr, len) in vecs {
565 let slice = mem.get_slice_at_addr(addr, len).unwrap();
566 rutabaga_iovecs.push(RutabagaIovec {
567 base: slice.as_mut_ptr() as *mut c_void,
568 len,
569 });
570 }
571 Ok(rutabaga_iovecs)
572}
573
574pub enum ProcessDisplayResult {
575 Success,
576 CloseRequested,
577 Error(GpuDisplayError),
578}
579
580impl VirtioGpu {
581 pub fn new(
583 display: GpuDisplay,
584 display_params: Vec<GpuDisplayParameters>,
585 display_event: Arc<AtomicBool>,
586 rutabaga: Rutabaga,
587 mapper: Arc<Mutex<Option<Box<dyn SharedMemoryMapper>>>>,
588 external_blob: bool,
589 fixed_blob_mapping: bool,
590 udmabuf: bool,
591 snapshot_scratch_directory: Option<PathBuf>,
592 ) -> Option<VirtioGpu> {
593 let mut udmabuf_driver = None;
594 if udmabuf {
595 udmabuf_driver = Some(
596 UdmabufDriver::new()
597 .map_err(|e| error!("failed to initialize udmabuf: {}", e))
598 .ok()?,
599 );
600 }
601
602 let scanouts = display_params
603 .iter()
604 .enumerate()
605 .map(|(display_index, display_param)| {
606 (
607 display_index as u32,
608 VirtioGpuScanout::new_primary(display_index as u32, display_param.clone()),
609 )
610 })
611 .collect::<Map<_, _>>();
612 let cursor_scanout = VirtioGpuScanout::new_cursor();
613
614 Some(VirtioGpu {
615 display: Rc::new(RefCell::new(display)),
616 scanouts,
617 scanouts_updated: display_event,
618 cursor_scanout,
619 mapper,
620 rutabaga,
621 resources: Default::default(),
622 external_blob,
623 fixed_blob_mapping,
624 udmabuf_driver,
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::MesaError(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) = RutabagaMesaHandle::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 = match self.rutabaga.guest_cpu_mappable(resource_id) {
885 Ok(query) => query,
886 Err(_) => return ResourceResponse::Invalid,
887 };
888
889 ResourceResponse::Resource(ResourceInfo::Buffer(BufferInfo {
890 handle,
891 planes: [
892 PlaneInfo {
893 offset: q.offsets[0],
894 stride: q.strides[0],
895 },
896 PlaneInfo {
897 offset: q.offsets[1],
898 stride: q.strides[1],
899 },
900 PlaneInfo {
901 offset: q.offsets[2],
902 stride: q.strides[2],
903 },
904 PlaneInfo {
905 offset: q.offsets[3],
906 stride: q.strides[3],
907 },
908 ],
909 modifier: q.modifier,
910 guest_cpu_mappable,
911 }))
912 }
913
914 pub fn export_fence(&mut self, fence_id: u64) -> ResourceResponse {
916 match self.rutabaga.export_fence(fence_id) {
917 Ok(handle) => ResourceResponse::Resource(ResourceInfo::Fence {
918 handle: to_safe_descriptor(handle.os_handle),
919 }),
920 Err(_) => ResourceResponse::Invalid,
921 }
922 }
923
924 pub fn get_capset_info(&self, index: u32) -> VirtioGpuResult {
926 if let Ok((capset_id, version, size)) = self.rutabaga.get_capset_info(index) {
927 Ok(OkCapsetInfo {
928 capset_id,
929 version,
930 size,
931 })
932 } else {
933 base::warn!(
936 "virtio-gpu get_capset_info(index={}) failed. intentionally poisoning response",
937 index
938 );
939 Ok(OkCapsetInfo {
940 capset_id: u32::MAX,
941 version: 0,
942 size: 0,
943 })
944 }
945 }
946
947 pub fn get_capset(&self, capset_id: u32, version: u32) -> VirtioGpuResult {
949 let capset = self.rutabaga.get_capset(capset_id, version)?;
950 Ok(OkCapset(capset))
951 }
952
953 pub fn force_ctx_0(&self) {
955 self.rutabaga.force_ctx_0()
956 }
957
958 pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
961 self.rutabaga.create_fence(rutabaga_fence)?;
962 Ok(OkNoData)
963 }
964
965 pub fn event_poll(&self) {
967 self.rutabaga.event_poll();
968 }
969
970 pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
973 self.rutabaga.poll_descriptor().map(to_safe_descriptor)
974 }
975
976 pub fn resource_create_3d(
978 &mut self,
979 resource_id: u32,
980 resource_create_3d: ResourceCreate3D,
981 ) -> VirtioGpuResult {
982 self.rutabaga
983 .resource_create_3d(resource_id, resource_create_3d)?;
984
985 let resource = VirtioGpuResource::new(
986 resource_id,
987 resource_create_3d.width,
988 resource_create_3d.height,
989 0,
990 );
991
992 self.resources.insert(resource_id, resource);
994 Ok(self.result_from_query(resource_id))
995 }
996
997 pub fn attach_backing(
1001 &mut self,
1002 resource_id: u32,
1003 mem: &GuestMemory,
1004 vecs: Vec<(GuestAddress, usize)>,
1005 ) -> VirtioGpuResult {
1006 let resource = self
1007 .resources
1008 .get_mut(&resource_id)
1009 .ok_or(ErrInvalidResourceId)?;
1010
1011 let rutabaga_iovecs = sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?;
1012 self.rutabaga.attach_backing(resource_id, rutabaga_iovecs)?;
1013 resource.backing_iovecs = Some(vecs);
1014 Ok(OkNoData)
1015 }
1016
1017 pub fn detach_backing(&mut self, resource_id: u32) -> VirtioGpuResult {
1019 let resource = self
1020 .resources
1021 .get_mut(&resource_id)
1022 .ok_or(ErrInvalidResourceId)?;
1023
1024 self.rutabaga.detach_backing(resource_id)?;
1025 resource.backing_iovecs = None;
1026 Ok(OkNoData)
1027 }
1028
1029 pub fn unref_resource(&mut self, resource_id: u32) -> VirtioGpuResult {
1031 let resource = self
1032 .resources
1033 .remove(&resource_id)
1034 .ok_or(ErrInvalidResourceId)?;
1035
1036 if resource.rutabaga_external_mapping {
1037 self.rutabaga.unmap(resource_id)?;
1038 }
1039
1040 self.rutabaga.unref_resource(resource_id)?;
1041 Ok(OkNoData)
1042 }
1043
1044 pub fn transfer_write(
1046 &mut self,
1047 ctx_id: u32,
1048 resource_id: u32,
1049 transfer: Transfer3D,
1050 ) -> VirtioGpuResult {
1051 self.rutabaga
1052 .transfer_write(ctx_id, resource_id, transfer, None)?;
1053 Ok(OkNoData)
1054 }
1055
1056 pub fn transfer_read(
1062 &mut self,
1063 ctx_id: u32,
1064 resource_id: u32,
1065 transfer: Transfer3D,
1066 buf: Option<VolatileSlice>,
1067 ) -> VirtioGpuResult {
1068 let buf = buf.map(|vs| {
1069 IoSliceMut::new(
1070 unsafe { std::slice::from_raw_parts_mut(vs.as_mut_ptr(), vs.size()) },
1072 )
1073 });
1074 self.rutabaga
1075 .transfer_read(ctx_id, resource_id, transfer, buf)?;
1076 Ok(OkNoData)
1077 }
1078
1079 pub fn resource_create_blob(
1081 &mut self,
1082 ctx_id: u32,
1083 resource_id: u32,
1084 resource_create_blob: ResourceCreateBlob,
1085 vecs: Vec<(GuestAddress, usize)>,
1086 mem: &GuestMemory,
1087 ) -> VirtioGpuResult {
1088 let mut descriptor = None;
1089 let mut rutabaga_iovecs = None;
1090
1091 if resource_create_blob.blob_flags & VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE != 0 {
1092 descriptor = match self.udmabuf_driver {
1093 Some(ref driver) => Some(driver.create_udmabuf(mem, &vecs[..])?),
1094 None => return Err(ErrUnspec),
1095 }
1096 } else if resource_create_blob.blob_mem != VIRTIO_GPU_BLOB_MEM_HOST3D {
1097 rutabaga_iovecs =
1098 Some(sglist_to_rutabaga_iovecs(&vecs[..], mem).map_err(|_| ErrUnspec)?);
1099 }
1100
1101 self.rutabaga.resource_create_blob(
1102 ctx_id,
1103 resource_id,
1104 resource_create_blob,
1105 rutabaga_iovecs,
1106 descriptor.map(|descriptor| {
1107 RutabagaMesaHandle {
1108 os_handle: to_rutabaga_descriptor(descriptor),
1109 handle_type: RUTABAGA_HANDLE_TYPE_MEM_DMABUF,
1110 }
1111 .into()
1112 }),
1113 )?;
1114
1115 let resource = VirtioGpuResource::new(resource_id, 0, 0, resource_create_blob.size);
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 = RutabagaMesaHandle::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}