Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/media.rs
5394 views
1
// Copyright 2024 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
//! Support for virtio-media devices in crosvm.
6
//!
7
//! This module provides implementation for the virtio-media traits required to make virtio-media
8
//! devices operate under crosvm. Sub-modules then integrate these devices with crosvm.
9
10
#[cfg(feature = "video-decoder")]
11
pub mod decoder_adapter;
12
13
use std::collections::BTreeMap;
14
use std::os::fd::AsRawFd;
15
use std::os::fd::BorrowedFd;
16
use std::path::Path;
17
use std::path::PathBuf;
18
use std::rc::Rc;
19
use std::sync::Arc;
20
21
use anyhow::Context;
22
use base::error;
23
use base::Descriptor;
24
use base::Event;
25
use base::EventToken;
26
use base::EventType;
27
use base::MappedRegion;
28
use base::MemoryMappingArena;
29
use base::Protection;
30
use base::WaitContext;
31
use base::WorkerThread;
32
use resources::address_allocator::AddressAllocator;
33
use resources::AddressRange;
34
use resources::Alloc;
35
use sync::Mutex;
36
use virtio_media::io::WriteToDescriptorChain;
37
use virtio_media::poll::SessionPoller;
38
use virtio_media::protocol::SgEntry;
39
use virtio_media::protocol::V4l2Event;
40
use virtio_media::protocol::VirtioMediaDeviceConfig;
41
use virtio_media::GuestMemoryRange;
42
use virtio_media::VirtioMediaDevice;
43
use virtio_media::VirtioMediaDeviceRunner;
44
use virtio_media::VirtioMediaEventQueue;
45
use virtio_media::VirtioMediaGuestMemoryMapper;
46
use virtio_media::VirtioMediaHostMemoryMapper;
47
use vm_control::VmMemorySource;
48
use vm_memory::GuestAddress;
49
use vm_memory::GuestMemory;
50
51
use crate::virtio::copy_config;
52
use crate::virtio::device_constants::media::QUEUE_SIZES;
53
#[cfg(feature = "video-decoder")]
54
use crate::virtio::device_constants::video::VideoBackendType;
55
use crate::virtio::DeviceType;
56
use crate::virtio::Interrupt;
57
use crate::virtio::Queue;
58
use crate::virtio::Reader;
59
use crate::virtio::SharedMemoryMapper;
60
use crate::virtio::SharedMemoryRegion;
61
use crate::virtio::VirtioDevice;
62
use crate::virtio::Writer;
63
64
/// Structure supporting the implementation of `VirtioMediaEventQueue` for sending events to the
65
/// driver.
66
struct EventQueue(Queue);
67
68
impl VirtioMediaEventQueue for EventQueue {
69
/// Wait until an event descriptor becomes available and send `event` to the guest.
70
fn send_event(&mut self, event: V4l2Event) {
71
let mut desc;
72
73
loop {
74
match self.0.pop() {
75
Some(d) => {
76
desc = d;
77
break;
78
}
79
None => {
80
if let Err(e) = self.0.event().wait() {
81
error!("could not obtain a descriptor to send event to: {:#}", e);
82
return;
83
}
84
}
85
}
86
}
87
88
if let Err(e) = match event {
89
V4l2Event::Error(event) => WriteToDescriptorChain::write_obj(&mut desc.writer, event),
90
V4l2Event::DequeueBuffer(event) => {
91
WriteToDescriptorChain::write_obj(&mut desc.writer, event)
92
}
93
V4l2Event::Event(event) => WriteToDescriptorChain::write_obj(&mut desc.writer, event),
94
} {
95
error!("failed to write event: {}", e);
96
}
97
98
self.0.add_used(desc);
99
self.0.trigger_interrupt();
100
}
101
}
102
103
/// A `SharedMemoryMapper` behind an `Arc`, allowing it to be shared.
104
///
105
/// This is required by the fact that devices can be activated several times, but the mapper is
106
/// only provided once. This might be a defect of the `VirtioDevice` interface.
107
#[derive(Clone)]
108
struct ArcedMemoryMapper(Arc<Mutex<Box<dyn SharedMemoryMapper>>>);
109
110
impl From<Box<dyn SharedMemoryMapper>> for ArcedMemoryMapper {
111
fn from(mapper: Box<dyn SharedMemoryMapper>) -> Self {
112
Self(Arc::new(Mutex::new(mapper)))
113
}
114
}
115
116
impl SharedMemoryMapper for ArcedMemoryMapper {
117
fn add_mapping(
118
&mut self,
119
source: VmMemorySource,
120
offset: u64,
121
prot: Protection,
122
cache: hypervisor::MemCacheType,
123
) -> anyhow::Result<()> {
124
self.0.lock().add_mapping(source, offset, prot, cache)
125
}
126
127
fn remove_mapping(&mut self, offset: u64) -> anyhow::Result<()> {
128
self.0.lock().remove_mapping(offset)
129
}
130
131
fn as_raw_descriptor(&self) -> Option<base::RawDescriptor> {
132
self.0.lock().as_raw_descriptor()
133
}
134
}
135
136
/// Provides the ability to map host memory into the guest physical address space. Used to
137
/// implement `VirtioMediaHostMemoryMapper`.
138
struct HostMemoryMapper<M: SharedMemoryMapper> {
139
/// Mapper.
140
shm_mapper: M,
141
/// Address allocator for the mapper.
142
allocator: AddressAllocator,
143
}
144
145
impl<M: SharedMemoryMapper> VirtioMediaHostMemoryMapper for HostMemoryMapper<M> {
146
fn add_mapping(
147
&mut self,
148
buffer: BorrowedFd,
149
length: u64,
150
offset: u64,
151
rw: bool,
152
) -> Result<u64, i32> {
153
// TODO: technically `offset` can be used twice if a buffer is deleted and some other takes
154
// its place...
155
let shm_offset = self
156
.allocator
157
.allocate(length, Alloc::FileBacked(offset), "".into())
158
.map_err(|_| libc::ENOMEM)?;
159
160
match self.shm_mapper.add_mapping(
161
VmMemorySource::Descriptor {
162
descriptor: buffer.try_clone_to_owned().map_err(|_| libc::EIO)?.into(),
163
offset: 0,
164
size: length,
165
},
166
shm_offset,
167
if rw {
168
Protection::read_write()
169
} else {
170
Protection::read()
171
},
172
hypervisor::MemCacheType::CacheCoherent,
173
) {
174
Ok(()) => Ok(shm_offset),
175
Err(e) => {
176
base::error!("failed to map memory buffer: {:#}", e);
177
Err(libc::EINVAL)
178
}
179
}
180
}
181
182
fn remove_mapping(&mut self, offset: u64) -> Result<(), i32> {
183
let _ = self.allocator.release_containing(offset);
184
185
self.shm_mapper
186
.remove_mapping(offset)
187
.map_err(|_| libc::EINVAL)
188
}
189
}
190
191
/// Direct linear mapping of sparse guest memory.
192
///
193
/// A re-mapping of sparse guest memory into an arena that is linear to the host.
194
struct GuestMemoryMapping {
195
arena: MemoryMappingArena,
196
start_offset: usize,
197
}
198
199
impl GuestMemoryMapping {
200
fn new(mem: &GuestMemory, sgs: &[SgEntry]) -> anyhow::Result<Self> {
201
let page_size = base::pagesize() as u64;
202
let page_mask = page_size - 1;
203
204
// Validate the SGs.
205
//
206
// We can only map full pages and need to maintain a linear area. This means that the
207
// following invariants must be withheld:
208
//
209
// - For all entries but the first, the start offset within the page must be 0.
210
// - For all entries but the last, `start + len` must be a multiple of page size.
211
for sg in sgs.iter().skip(1) {
212
if sg.start & page_mask != 0 {
213
anyhow::bail!("non-initial SG entry start offset is not 0");
214
}
215
}
216
for sg in sgs.iter().take(sgs.len() - 1) {
217
if (sg.start + sg.len as u64) & page_mask != 0 {
218
anyhow::bail!("non-terminal SG entry with start + len != page_size");
219
}
220
}
221
222
// Compute the arena size.
223
let arena_size = sgs
224
.iter()
225
.fold(0, |size, sg| size + (sg.start & page_mask) + sg.len as u64)
226
// Align to page size if the last entry did not cover a full page.
227
.next_multiple_of(page_size);
228
let mut arena = MemoryMappingArena::new(arena_size as usize)?;
229
230
// Map all SG entries.
231
let mut pos = 0;
232
for region in sgs {
233
// Address of the first page of the region.
234
let region_first_page = region.start & !page_mask;
235
let len = region.start - region_first_page + region.len as u64;
236
// Make sure to map whole pages (only necessary for the last entry).
237
let len = len.next_multiple_of(page_size) as usize;
238
// TODO: find the offset from the region, this assumes a single
239
// region starting at address 0.
240
let fd = mem.offset_region(region_first_page)?;
241
// Always map whole pages
242
arena.add_fd_offset(pos, len, fd, region_first_page)?;
243
244
pos += len;
245
}
246
247
let start_offset = sgs
248
.first()
249
.map(|region| region.start & page_mask)
250
.unwrap_or(0) as usize;
251
252
Ok(GuestMemoryMapping {
253
arena,
254
start_offset,
255
})
256
}
257
}
258
259
impl GuestMemoryRange for GuestMemoryMapping {
260
fn as_ptr(&self) -> *const u8 {
261
// SAFETY: the arena has a valid pointer that covers `start_offset + len`.
262
unsafe { self.arena.as_ptr().add(self.start_offset) }
263
}
264
265
fn as_mut_ptr(&mut self) -> *mut u8 {
266
// SAFETY: the arena has a valid pointer that covers `start_offset + len`.
267
unsafe { self.arena.as_ptr().add(self.start_offset) }
268
}
269
}
270
271
/// Copy of sparse guest memory that is written back upon destruction.
272
///
273
/// Contrary to `GuestMemoryMapping` which re-maps guest memory to make it appear linear to the
274
/// host, this copies the sparse guest memory into a linear vector that is copied back upon
275
/// destruction. Doing so can be faster than a costly mapping operation if the guest area is small
276
/// enough.
277
struct GuestMemoryShadowMapping {
278
/// Sparse data copied from the guest.
279
data: Vec<u8>,
280
/// Guest memory to read from.
281
mem: GuestMemory,
282
/// SG entries describing the sparse guest area.
283
sgs: Vec<SgEntry>,
284
/// Whether the data has potentially been modified and requires to be written back to the
285
/// guest.
286
dirty: bool,
287
}
288
289
impl GuestMemoryShadowMapping {
290
fn new(mem: &GuestMemory, sgs: Vec<SgEntry>) -> anyhow::Result<Self> {
291
let total_size = sgs.iter().fold(0, |total, sg| total + sg.len as usize);
292
let mut data = vec![0u8; total_size];
293
let mut pos = 0;
294
for sg in &sgs {
295
mem.read_exact_at_addr(
296
&mut data[pos..pos + sg.len as usize],
297
GuestAddress(sg.start),
298
)?;
299
pos += sg.len as usize;
300
}
301
302
Ok(Self {
303
data,
304
mem: mem.clone(),
305
sgs,
306
dirty: false,
307
})
308
}
309
}
310
311
impl GuestMemoryRange for GuestMemoryShadowMapping {
312
fn as_ptr(&self) -> *const u8 {
313
self.data.as_ptr()
314
}
315
316
fn as_mut_ptr(&mut self) -> *mut u8 {
317
self.dirty = true;
318
self.data.as_mut_ptr()
319
}
320
}
321
322
/// Write the potentially modified shadow buffer back into the guest memory.
323
impl Drop for GuestMemoryShadowMapping {
324
fn drop(&mut self) {
325
// No need to copy back if no modification has been done.
326
if !self.dirty {
327
return;
328
}
329
330
let mut pos = 0;
331
for sg in &self.sgs {
332
if let Err(e) = self.mem.write_all_at_addr(
333
&self.data[pos..pos + sg.len as usize],
334
GuestAddress(sg.start),
335
) {
336
base::error!("failed to write back guest memory shadow mapping: {:#}", e);
337
}
338
pos += sg.len as usize;
339
}
340
}
341
}
342
343
/// A chunk of guest memory which can be either directly mapped, or copied into a shadow buffer.
344
enum GuestMemoryChunk {
345
Mapping(GuestMemoryMapping),
346
Shadow(GuestMemoryShadowMapping),
347
}
348
349
impl GuestMemoryRange for GuestMemoryChunk {
350
fn as_ptr(&self) -> *const u8 {
351
match self {
352
GuestMemoryChunk::Mapping(m) => m.as_ptr(),
353
GuestMemoryChunk::Shadow(s) => s.as_ptr(),
354
}
355
}
356
357
fn as_mut_ptr(&mut self) -> *mut u8 {
358
match self {
359
GuestMemoryChunk::Mapping(m) => m.as_mut_ptr(),
360
GuestMemoryChunk::Shadow(s) => s.as_mut_ptr(),
361
}
362
}
363
}
364
365
/// Newtype to implement `VirtioMediaGuestMemoryMapper` on `GuestMemory`.
366
///
367
/// Whether to use a direct mapping or to copy the guest data into a shadow buffer is decided by
368
/// the size of the guest mapping. If it is below `MAPPING_THRESHOLD`, a shadow buffer is used ;
369
/// otherwise the area is mapped.
370
struct GuestMemoryMapper(GuestMemory);
371
372
impl VirtioMediaGuestMemoryMapper for GuestMemoryMapper {
373
type GuestMemoryMapping = GuestMemoryChunk;
374
375
fn new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping> {
376
/// Threshold at which we perform a direct mapping of the guest memory into the host.
377
/// Anything below that is copied into a shadow buffer and synced back to the guest when
378
/// the memory chunk is destroyed.
379
const MAPPING_THRESHOLD: usize = 0x400;
380
let total_size = sgs.iter().fold(0, |total, sg| total + sg.len as usize);
381
382
if total_size >= MAPPING_THRESHOLD {
383
GuestMemoryMapping::new(&self.0, &sgs).map(GuestMemoryChunk::Mapping)
384
} else {
385
GuestMemoryShadowMapping::new(&self.0, sgs).map(GuestMemoryChunk::Shadow)
386
}
387
}
388
}
389
390
#[derive(EventToken, Debug)]
391
enum Token {
392
CommandQueue,
393
V4l2Session(u32),
394
Kill,
395
}
396
397
/// Newtype to implement `SessionPoller` on `Rc<WaitContext<Token>>`.
398
#[derive(Clone)]
399
struct WaitContextPoller(Rc<WaitContext<Token>>);
400
401
impl SessionPoller for WaitContextPoller {
402
fn add_session(&self, session: BorrowedFd, session_id: u32) -> Result<(), i32> {
403
self.0
404
.add_for_event(
405
&Descriptor(session.as_raw_fd()),
406
EventType::Read,
407
Token::V4l2Session(session_id),
408
)
409
.map_err(|e| e.errno())
410
}
411
412
fn remove_session(&self, session: BorrowedFd) {
413
let _ = self.0.delete(&Descriptor(session.as_raw_fd()));
414
}
415
}
416
417
/// Worker to operate a virtio-media device inside a worker thread.
418
struct Worker<D: VirtioMediaDevice<Reader, Writer>> {
419
runner: VirtioMediaDeviceRunner<Reader, Writer, D, WaitContextPoller>,
420
cmd_queue: Queue,
421
wait_ctx: Rc<WaitContext<Token>>,
422
}
423
424
impl<D> Worker<D>
425
where
426
D: VirtioMediaDevice<Reader, Writer>,
427
{
428
/// Create a new worker instance for `device`.
429
fn new(
430
device: D,
431
cmd_queue: Queue,
432
kill_evt: Event,
433
wait_ctx: Rc<WaitContext<Token>>,
434
) -> anyhow::Result<Self> {
435
wait_ctx
436
.add_many(&[
437
(cmd_queue.event(), Token::CommandQueue),
438
(&kill_evt, Token::Kill),
439
])
440
.context("when adding worker events to wait context")?;
441
442
Ok(Self {
443
runner: VirtioMediaDeviceRunner::new(device, WaitContextPoller(Rc::clone(&wait_ctx))),
444
cmd_queue,
445
wait_ctx,
446
})
447
}
448
449
fn run(&mut self) -> anyhow::Result<()> {
450
loop {
451
let wait_events = self.wait_ctx.wait().context("Wait error")?;
452
453
for wait_event in wait_events.iter() {
454
match wait_event.token {
455
Token::CommandQueue => {
456
let _ = self.cmd_queue.event().wait();
457
while let Some(mut desc) = self.cmd_queue.pop() {
458
self.runner
459
.handle_command(&mut desc.reader, &mut desc.writer);
460
// Return the descriptor to the guest.
461
self.cmd_queue.add_used(desc);
462
self.cmd_queue.trigger_interrupt();
463
}
464
}
465
Token::Kill => {
466
return Ok(());
467
}
468
Token::V4l2Session(session_id) => {
469
let session = match self.runner.sessions.get_mut(&session_id) {
470
Some(session) => session,
471
None => {
472
base::error!(
473
"received event for non-registered session {}",
474
session_id
475
);
476
continue;
477
}
478
};
479
480
if let Err(e) = self.runner.device.process_events(session) {
481
base::error!(
482
"error while processing events for session {}: {:#}",
483
session_id,
484
e
485
);
486
if let Some(session) = self.runner.sessions.remove(&session_id) {
487
self.runner.device.close_session(session);
488
}
489
}
490
}
491
}
492
}
493
}
494
}
495
}
496
497
/// Implements the required traits to operate a [`VirtioMediaDevice`] under crosvm.
498
struct CrosvmVirtioMediaDevice<
499
D: VirtioMediaDevice<Reader, Writer>,
500
F: Fn(EventQueue, GuestMemoryMapper, HostMemoryMapper<ArcedMemoryMapper>) -> anyhow::Result<D>,
501
> {
502
/// Closure to create the device once all its resources are acquired.
503
create_device: F,
504
/// Virtio configuration area.
505
config: VirtioMediaDeviceConfig,
506
507
/// Virtio device features.
508
base_features: u64,
509
/// Mapper to make host video buffers visible to the guest.
510
///
511
/// We unfortunately need to put it behind a `Arc` because the mapper is only passed once,
512
/// whereas the device can be activated several times, so we need to keep a reference to it
513
/// even after it is passed to the device.
514
shm_mapper: Option<ArcedMemoryMapper>,
515
/// Worker thread for the device.
516
worker_thread: Option<WorkerThread<()>>,
517
}
518
519
impl<D, F> CrosvmVirtioMediaDevice<D, F>
520
where
521
D: VirtioMediaDevice<Reader, Writer>,
522
F: Fn(EventQueue, GuestMemoryMapper, HostMemoryMapper<ArcedMemoryMapper>) -> anyhow::Result<D>,
523
{
524
fn new(base_features: u64, config: VirtioMediaDeviceConfig, create_device: F) -> Self {
525
Self {
526
base_features,
527
config,
528
shm_mapper: None,
529
create_device,
530
worker_thread: None,
531
}
532
}
533
}
534
535
const HOST_MAPPER_RANGE: u64 = 1 << 32;
536
537
impl<D, F> VirtioDevice for CrosvmVirtioMediaDevice<D, F>
538
where
539
D: VirtioMediaDevice<Reader, Writer> + Send + 'static,
540
F: Fn(EventQueue, GuestMemoryMapper, HostMemoryMapper<ArcedMemoryMapper>) -> anyhow::Result<D>
541
+ Send,
542
{
543
fn keep_rds(&self) -> Vec<base::RawDescriptor> {
544
let mut keep_rds = Vec::new();
545
546
if let Some(fd) = self.shm_mapper.as_ref().and_then(|m| m.as_raw_descriptor()) {
547
keep_rds.push(fd);
548
}
549
550
keep_rds
551
}
552
553
fn device_type(&self) -> DeviceType {
554
DeviceType::Media
555
}
556
557
fn queue_max_sizes(&self) -> &[u16] {
558
QUEUE_SIZES
559
}
560
561
fn features(&self) -> u64 {
562
self.base_features
563
}
564
565
fn read_config(&self, offset: u64, data: &mut [u8]) {
566
copy_config(data, 0, self.config.as_ref(), offset);
567
}
568
569
fn activate(
570
&mut self,
571
mem: vm_memory::GuestMemory,
572
_interrupt: Interrupt,
573
mut queues: BTreeMap<usize, Queue>,
574
) -> anyhow::Result<()> {
575
if queues.len() != QUEUE_SIZES.len() {
576
anyhow::bail!(
577
"wrong number of queues are passed: expected {}, actual {}",
578
queues.len(),
579
QUEUE_SIZES.len()
580
);
581
}
582
583
let cmd_queue = queues.remove(&0).context("missing queue 0")?;
584
let event_queue = EventQueue(queues.remove(&1).context("missing queue 1")?);
585
586
let shm_mapper = self
587
.shm_mapper
588
.clone()
589
.context("shared memory mapper was not specified")?;
590
591
let wait_ctx = WaitContext::new()?;
592
let device = (self.create_device)(
593
event_queue,
594
GuestMemoryMapper(mem),
595
HostMemoryMapper {
596
shm_mapper,
597
allocator: AddressAllocator::new(
598
AddressRange::from_start_and_end(0, HOST_MAPPER_RANGE - 1),
599
Some(base::pagesize() as u64),
600
None,
601
)?,
602
},
603
)?;
604
605
let worker_thread = WorkerThread::start("v_media_worker", move |e| {
606
let wait_ctx = Rc::new(wait_ctx);
607
let mut worker = match Worker::new(device, cmd_queue, e, wait_ctx) {
608
Ok(worker) => worker,
609
Err(e) => {
610
error!("failed to create virtio-media worker: {:#}", e);
611
return;
612
}
613
};
614
if let Err(e) = worker.run() {
615
error!("virtio_media worker exited with error: {:#}", e);
616
}
617
});
618
619
self.worker_thread = Some(worker_thread);
620
Ok(())
621
}
622
623
fn reset(&mut self) -> anyhow::Result<()> {
624
if let Some(worker_thread) = self.worker_thread.take() {
625
worker_thread.stop();
626
}
627
628
Ok(())
629
}
630
631
fn get_shared_memory_region(&self) -> Option<SharedMemoryRegion> {
632
Some(SharedMemoryRegion {
633
id: 0,
634
// We need a 32-bit address space as m2m devices start their CAPTURE buffers' offsets
635
// at 2GB.
636
length: HOST_MAPPER_RANGE,
637
})
638
}
639
640
fn set_shared_memory_mapper(&mut self, mapper: Box<dyn SharedMemoryMapper>) {
641
self.shm_mapper = Some(ArcedMemoryMapper::from(mapper));
642
}
643
}
644
645
/// Create a simple media capture device.
646
///
647
/// This device can only generate a fixed pattern at a fixed resolution, and should only be used
648
/// for checking that the virtio-media pipeline is working properly.
649
pub fn create_virtio_media_simple_capture_device(features: u64) -> Box<dyn VirtioDevice> {
650
use virtio_media::devices::SimpleCaptureDevice;
651
use virtio_media::v4l2r::ioctl::Capabilities;
652
653
let mut card = [0u8; 32];
654
let card_name = "simple_device";
655
card[0..card_name.len()].copy_from_slice(card_name.as_bytes());
656
657
let device = CrosvmVirtioMediaDevice::new(
658
features,
659
VirtioMediaDeviceConfig {
660
device_caps: (Capabilities::VIDEO_CAPTURE_MPLANE | Capabilities::STREAMING).bits(),
661
// VFL_TYPE_VIDEO
662
device_type: 0,
663
card,
664
},
665
|event_queue, _, host_mapper| Ok(SimpleCaptureDevice::new(event_queue, host_mapper)),
666
);
667
668
Box::new(device)
669
}
670
671
/// Create a proxy device for a host V4L2 device.
672
///
673
/// Since V4L2 is a Linux-specific API, this is only available on Linux targets.
674
#[cfg(any(target_os = "android", target_os = "linux"))]
675
pub fn create_virtio_media_v4l2_proxy_device<P: AsRef<Path>>(
676
features: u64,
677
device_path: P,
678
) -> anyhow::Result<Box<dyn VirtioDevice>> {
679
use virtio_media::devices::V4l2ProxyDevice;
680
use virtio_media::v4l2r;
681
use virtio_media::v4l2r::ioctl::Capabilities;
682
683
let device = v4l2r::device::Device::open(
684
device_path.as_ref(),
685
v4l2r::device::DeviceConfig::new().non_blocking_dqbuf(),
686
)?;
687
let mut device_caps = device.caps().device_caps();
688
689
// We are only exposing one device worth of capabilities.
690
device_caps.remove(Capabilities::DEVICE_CAPS);
691
692
// Read-write is not supported by design.
693
device_caps.remove(Capabilities::READWRITE);
694
695
let mut config = VirtioMediaDeviceConfig {
696
device_caps: device_caps.bits(),
697
// VFL_TYPE_VIDEO
698
device_type: 0,
699
card: Default::default(),
700
};
701
let card = &device.caps().card;
702
let name_slice = &card.as_bytes()[0..std::cmp::min(card.len(), config.card.len())];
703
config.card.as_mut_slice()[0..name_slice.len()].copy_from_slice(name_slice);
704
let device_path = PathBuf::from(device_path.as_ref());
705
706
let device = CrosvmVirtioMediaDevice::new(
707
features,
708
config,
709
move |event_queue, guest_mapper, host_mapper| {
710
let device =
711
V4l2ProxyDevice::new(device_path.clone(), event_queue, guest_mapper, host_mapper);
712
713
Ok(device)
714
},
715
);
716
717
Ok(Box::new(device))
718
}
719
720
/// Create a decoder adapter device.
721
///
722
/// This is a regular virtio-media decoder device leveraging the virtio-video decoder backends.
723
#[cfg(feature = "video-decoder")]
724
pub fn create_virtio_media_decoder_adapter_device(
725
features: u64,
726
_gpu_tube: base::Tube,
727
backend: VideoBackendType,
728
) -> anyhow::Result<Box<dyn VirtioDevice>> {
729
use decoder_adapter::VirtioVideoAdapter;
730
use virtio_media::devices::video_decoder::VideoDecoder;
731
use virtio_media::v4l2r::ioctl::Capabilities;
732
733
#[cfg(feature = "ffmpeg")]
734
use crate::virtio::video::decoder::backend::ffmpeg::FfmpegDecoder;
735
#[cfg(feature = "vaapi")]
736
use crate::virtio::video::decoder::backend::vaapi::VaapiDecoder;
737
#[cfg(feature = "libvda")]
738
use crate::virtio::video::decoder::backend::vda::LibvdaDecoder;
739
use crate::virtio::video::decoder::DecoderBackend;
740
741
let mut card = [0u8; 32];
742
let card_name = format!("{backend:?} decoder adapter").to_lowercase();
743
card[0..card_name.len()].copy_from_slice(card_name.as_bytes());
744
let config = VirtioMediaDeviceConfig {
745
device_caps: (Capabilities::VIDEO_M2M_MPLANE | Capabilities::STREAMING).bits(),
746
// VFL_TYPE_VIDEO
747
device_type: 0,
748
card,
749
};
750
751
let create_device = move |event_queue, _, host_mapper: HostMemoryMapper<ArcedMemoryMapper>| {
752
let backend = match backend {
753
#[cfg(feature = "libvda")]
754
VideoBackendType::Libvda => {
755
LibvdaDecoder::new(libvda::decode::VdaImplType::Gavda)?.into_trait_object()
756
}
757
#[cfg(feature = "libvda")]
758
VideoBackendType::LibvdaVd => {
759
LibvdaDecoder::new(libvda::decode::VdaImplType::Gavd)?.into_trait_object()
760
}
761
#[cfg(feature = "vaapi")]
762
VideoBackendType::Vaapi => VaapiDecoder::new()?.into_trait_object(),
763
#[cfg(feature = "ffmpeg")]
764
VideoBackendType::Ffmpeg => FfmpegDecoder::new().into_trait_object(),
765
};
766
767
let adapter = VirtioVideoAdapter::new(backend);
768
let decoder = VideoDecoder::new(adapter, event_queue, host_mapper);
769
770
Ok(decoder)
771
};
772
773
Ok(Box::new(CrosvmVirtioMediaDevice::new(
774
features,
775
config,
776
create_device,
777
)))
778
}
779
780