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