devices/virtio/
media.rs

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")]
11pub mod decoder_adapter;
12
13use std::collections::BTreeMap;
14use std::os::fd::AsRawFd;
15use std::os::fd::BorrowedFd;
16use std::path::Path;
17use std::path::PathBuf;
18use std::rc::Rc;
19use std::sync::Arc;
20
21use anyhow::Context;
22use base::error;
23use base::Descriptor;
24use base::Event;
25use base::EventToken;
26use base::EventType;
27use base::MappedRegion;
28use base::MemoryMappingArena;
29use base::Protection;
30use base::WaitContext;
31use base::WorkerThread;
32use resources::address_allocator::AddressAllocator;
33use resources::AddressRange;
34use resources::Alloc;
35use sync::Mutex;
36use virtio_media::io::WriteToDescriptorChain;
37use virtio_media::poll::SessionPoller;
38use virtio_media::protocol::SgEntry;
39use virtio_media::protocol::V4l2Event;
40use virtio_media::protocol::VirtioMediaDeviceConfig;
41use virtio_media::GuestMemoryRange;
42use virtio_media::VirtioMediaDevice;
43use virtio_media::VirtioMediaDeviceRunner;
44use virtio_media::VirtioMediaEventQueue;
45use virtio_media::VirtioMediaGuestMemoryMapper;
46use virtio_media::VirtioMediaHostMemoryMapper;
47use vm_control::VmMemorySource;
48use vm_memory::GuestAddress;
49use vm_memory::GuestMemory;
50
51use crate::virtio::copy_config;
52use crate::virtio::device_constants::media::QUEUE_SIZES;
53#[cfg(feature = "video-decoder")]
54use crate::virtio::device_constants::video::VideoBackendType;
55use crate::virtio::DeviceType;
56use crate::virtio::Interrupt;
57use crate::virtio::Queue;
58use crate::virtio::Reader;
59use crate::virtio::SharedMemoryMapper;
60use crate::virtio::SharedMemoryRegion;
61use crate::virtio::VirtioDevice;
62use crate::virtio::Writer;
63
64/// Structure supporting the implementation of `VirtioMediaEventQueue` for sending events to the
65/// driver.
66struct EventQueue(Queue);
67
68impl 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)]
108struct ArcedMemoryMapper(Arc<Mutex<Box<dyn SharedMemoryMapper>>>);
109
110impl From<Box<dyn SharedMemoryMapper>> for ArcedMemoryMapper {
111    fn from(mapper: Box<dyn SharedMemoryMapper>) -> Self {
112        Self(Arc::new(Mutex::new(mapper)))
113    }
114}
115
116impl 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`.
138struct HostMemoryMapper<M: SharedMemoryMapper> {
139    /// Mapper.
140    shm_mapper: M,
141    /// Address allocator for the mapper.
142    allocator: AddressAllocator,
143}
144
145impl<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.
194struct GuestMemoryMapping {
195    arena: MemoryMappingArena,
196    start_offset: usize,
197}
198
199impl 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
259impl 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.
277struct 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
289impl 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
311impl 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.
323impl 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.
344enum GuestMemoryChunk {
345    Mapping(GuestMemoryMapping),
346    Shadow(GuestMemoryShadowMapping),
347}
348
349impl 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.
370struct GuestMemoryMapper(GuestMemory);
371
372impl 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)]
391enum Token {
392    CommandQueue,
393    V4l2Session(u32),
394    Kill,
395}
396
397/// Newtype to implement `SessionPoller` on `Rc<WaitContext<Token>>`.
398#[derive(Clone)]
399struct WaitContextPoller(Rc<WaitContext<Token>>);
400
401impl 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.
418struct Worker<D: VirtioMediaDevice<Reader, Writer>> {
419    runner: VirtioMediaDeviceRunner<Reader, Writer, D, WaitContextPoller>,
420    cmd_queue: Queue,
421    wait_ctx: Rc<WaitContext<Token>>,
422}
423
424impl<D> Worker<D>
425where
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.
498struct 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
519impl<D, F> CrosvmVirtioMediaDevice<D, F>
520where
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
535const HOST_MAPPER_RANGE: u64 = 1 << 32;
536
537impl<D, F> VirtioDevice for CrosvmVirtioMediaDevice<D, F>
538where
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.
649pub 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 | 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"))]
675pub 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")]
724pub 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}