Skip to main content

virtio_media/devices/
simple_device.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//! Simple example virtio-media CAPTURE device with no dependency.
6//!
7//! This module illustrates how to write a device for virtio-media. It exposes a capture device
8//! that generates a RGB pattern on the buffers queued by the guest.
9
10use std::collections::VecDeque;
11use std::io::BufWriter;
12use std::io::Result as IoResult;
13use std::io::Seek;
14use std::io::SeekFrom;
15use std::io::Write;
16use std::os::fd::AsFd;
17use std::os::fd::BorrowedFd;
18
19use v4l2r::bindings;
20use v4l2r::bindings::v4l2_fmtdesc;
21use v4l2r::bindings::v4l2_format;
22use v4l2r::bindings::v4l2_requestbuffers;
23use v4l2r::ioctl::BufferCapabilities;
24use v4l2r::ioctl::BufferField;
25use v4l2r::ioctl::BufferFlags;
26use v4l2r::ioctl::MemoryConsistency;
27use v4l2r::ioctl::V4l2Buffer;
28use v4l2r::ioctl::V4l2PlanesWithBackingMut;
29use v4l2r::memory::MemoryType;
30use v4l2r::PixelFormat;
31use v4l2r::QueueType;
32
33use crate::ioctl::virtio_media_dispatch_ioctl;
34use crate::ioctl::IoctlResult;
35use crate::ioctl::VirtioMediaIoctlHandler;
36use crate::memfd::MemFdBuffer;
37use crate::mmap::MmapMappingManager;
38use crate::protocol::DequeueBufferEvent;
39use crate::protocol::SgEntry;
40use crate::protocol::V4l2Event;
41use crate::protocol::V4l2Ioctl;
42use crate::protocol::VIRTIO_MEDIA_MMAP_FLAG_RW;
43use crate::ReadFromDescriptorChain;
44use crate::VirtioMediaDevice;
45use crate::VirtioMediaDeviceSession;
46use crate::VirtioMediaEventQueue;
47use crate::VirtioMediaHostMemoryMapper;
48use crate::WriteToDescriptorChain;
49
50/// Current status of a buffer.
51#[derive(Debug, PartialEq, Eq)]
52enum BufferState {
53    /// Buffer has just been created (or streamed off) and not been used yet.
54    New,
55    /// Buffer has been QBUF'd by the driver but not yet processed.
56    Incoming,
57    /// Buffer has been processed and is ready for dequeue.
58    Outgoing {
59        /// Sequence of the generated frame.
60        sequence: u32,
61    },
62}
63
64/// Information about a single buffer.
65struct Buffer {
66    /// Current state of the buffer.
67    state: BufferState,
68    /// V4L2 representation of this buffer to be sent to the guest when requested.
69    v4l2_buffer: V4l2Buffer,
70    /// Backing storage for the buffer.
71    fd: MemFdBuffer,
72    /// Offset that can be used to map the buffer.
73    ///
74    /// Cached from `v4l2_buffer` to avoid doing a match.
75    offset: u32,
76}
77
78impl Buffer {
79    /// Update the state of the buffer as well as its V4L2 representation.
80    fn set_state(&mut self, state: BufferState) {
81        let mut flags = self.v4l2_buffer.flags();
82        match state {
83            BufferState::New => {
84                *self.v4l2_buffer.get_first_plane_mut().bytesused = 0;
85                flags -= BufferFlags::QUEUED;
86            }
87            BufferState::Incoming => {
88                *self.v4l2_buffer.get_first_plane_mut().bytesused = 0;
89                flags |= BufferFlags::QUEUED;
90            }
91            BufferState::Outgoing { sequence } => {
92                *self.v4l2_buffer.get_first_plane_mut().bytesused = BUFFER_SIZE;
93                self.v4l2_buffer.set_sequence(sequence);
94                self.v4l2_buffer.set_timestamp(bindings::timeval {
95                    tv_sec: (sequence + 1) as bindings::__time_t / 1000,
96                    tv_usec: (sequence + 1) as bindings::__time_t % 1000,
97                });
98                flags -= BufferFlags::QUEUED;
99            }
100        }
101
102        self.v4l2_buffer.set_flags(flags);
103        self.state = state;
104    }
105}
106
107/// Session data of [`SimpleCaptureDevice`].
108pub struct SimpleCaptureDeviceSession {
109    /// Id of the session.
110    id: u32,
111    /// Current iteration of the pattern generation cycle.
112    iteration: u64,
113    /// Buffers currently allocated for this session.
114    buffers: Vec<Buffer>,
115    /// FIFO of queued buffers awaiting processing.
116    queued_buffers: VecDeque<usize>,
117    /// Is the session currently streaming?
118    streaming: bool,
119}
120
121impl VirtioMediaDeviceSession for SimpleCaptureDeviceSession {
122    fn poll_fd(&self) -> Option<BorrowedFd<'_>> {
123        None
124    }
125}
126
127impl SimpleCaptureDeviceSession {
128    /// Generate the data pattern on all queued buffers and send the corresponding
129    /// [`DequeueBufferEvent`] to the driver.
130    fn process_queued_buffers<Q: VirtioMediaEventQueue>(
131        &mut self,
132        evt_queue: &mut Q,
133    ) -> IoctlResult<()> {
134        while let Some(buf_id) = self.queued_buffers.pop_front() {
135            let buffer = self.buffers.get_mut(buf_id).ok_or(libc::EIO)?;
136            let sequence = self.iteration as u32;
137
138            buffer
139                .fd
140                .as_file()
141                .seek(SeekFrom::Start(0))
142                .map_err(|_| libc::EIO)?;
143            let mut writer = BufWriter::new(buffer.fd.as_file());
144            let y = (sequence % 256) as u8;
145            let u = ((sequence + 64) % 256) as u8;
146            let v = ((sequence + 128) % 256) as u8;
147
148            for _ in 0..(WIDTH * HEIGHT) {
149                writer.write_all(&[y]).map_err(|_| libc::EIO)?;
150            }
151            for _ in 0..(WIDTH * HEIGHT / 4) {
152                writer.write_all(&[u]).map_err(|_| libc::EIO)?;
153            }
154            for _ in 0..(WIDTH * HEIGHT / 4) {
155                writer.write_all(&[v]).map_err(|_| libc::EIO)?;
156            }
157            drop(writer);
158
159            *buffer.v4l2_buffer.get_first_plane_mut().bytesused = BUFFER_SIZE;
160            buffer.set_state(BufferState::Outgoing { sequence });
161            // TODO: should we set the DONE flag here?
162            self.iteration += 1;
163
164            let v4l2_buffer = buffer.v4l2_buffer.clone();
165
166            evt_queue.send_event(V4l2Event::DequeueBuffer(DequeueBufferEvent::new(
167                self.id,
168                v4l2_buffer,
169            )));
170        }
171
172        Ok(())
173    }
174}
175
176/// A simplistic video capture device, used to demonstrate how device code can be written, or for
177/// testing VMMs and guests without dedicated hardware support.
178///
179/// This device supports a single pixel format (`RGB3`) and a single resolution, and generates
180/// frames of varying uniform color. The only buffer type supported is `MMAP`
181pub struct SimpleCaptureDevice<Q: VirtioMediaEventQueue, HM: VirtioMediaHostMemoryMapper> {
182    /// Queue used to send events to the guest.
183    evt_queue: Q,
184    /// Host MMAP mapping manager.
185    mmap_manager: MmapMappingManager<HM>,
186    /// ID of the session with allocated buffers, if any.
187    ///
188    /// v4l2-compliance checks that only a single session can have allocated buffers at a given
189    /// time, since that's how actual hardware works - no two sessions can access a camera at the
190    /// same time. It will fails if we allow simultaneous sessions to be active, so we need this
191    /// artificial limitation to make it pass fully.
192    active_session: Option<u32>,
193}
194
195impl<Q, HM> SimpleCaptureDevice<Q, HM>
196where
197    Q: VirtioMediaEventQueue,
198    HM: VirtioMediaHostMemoryMapper,
199{
200    pub fn new(evt_queue: Q, mapper: HM) -> Self {
201        Self {
202            evt_queue,
203            mmap_manager: MmapMappingManager::from(mapper),
204            active_session: None,
205        }
206    }
207}
208
209impl<Q, HM, Reader, Writer> VirtioMediaDevice<Reader, Writer> for SimpleCaptureDevice<Q, HM>
210where
211    Q: VirtioMediaEventQueue,
212    HM: VirtioMediaHostMemoryMapper,
213    Reader: ReadFromDescriptorChain,
214    Writer: WriteToDescriptorChain,
215{
216    type Session = SimpleCaptureDeviceSession;
217
218    fn new_session(&mut self, session_id: u32) -> Result<Self::Session, i32> {
219        Ok(SimpleCaptureDeviceSession {
220            id: session_id,
221            iteration: 0,
222            buffers: Default::default(),
223            queued_buffers: Default::default(),
224            streaming: false,
225        })
226    }
227
228    fn close_session(&mut self, session: Self::Session) {
229        if self.active_session == Some(session.id) {
230            self.active_session = None;
231        }
232
233        for buffer in &session.buffers {
234            self.mmap_manager.unregister_buffer(buffer.offset);
235        }
236    }
237
238    fn do_ioctl(
239        &mut self,
240        session: &mut Self::Session,
241        ioctl: V4l2Ioctl,
242        reader: &mut Reader,
243        writer: &mut Writer,
244    ) -> IoResult<()> {
245        virtio_media_dispatch_ioctl(self, session, ioctl, reader, writer)
246    }
247
248    fn do_mmap(
249        &mut self,
250        session: &mut Self::Session,
251        flags: u32,
252        offset: u32,
253    ) -> Result<(u64, u64), i32> {
254        let buffer = session
255            .buffers
256            .iter_mut()
257            .find(|b| b.offset == offset)
258            .ok_or(libc::EINVAL)?;
259        let rw = (flags & VIRTIO_MEDIA_MMAP_FLAG_RW) != 0;
260        let fd = buffer.fd.as_file().as_fd();
261        let (guest_addr, size) = self
262            .mmap_manager
263            .create_mapping(offset, fd, rw)
264            .map_err(|_| libc::EINVAL)?;
265
266        // TODO: would be nice to enable this, but how do we find the buffer again during munmap?
267        //
268        // Maybe keep a guest_addr -> session map in the device...
269        // buffer.v4l2_buffer.set_flags(buffer.v4l2_buffer.flags() | BufferFlags::MAPPED);
270
271        Ok((guest_addr, size))
272    }
273
274    fn do_munmap(&mut self, guest_addr: u64) -> Result<(), i32> {
275        self.mmap_manager
276            .remove_mapping(guest_addr)
277            .map(|_| ())
278            .map_err(|_| libc::EINVAL)
279    }
280}
281
282const PIXELFORMAT: u32 = PixelFormat::from_fourcc(b"YU12").to_u32();
283const WIDTH: u32 = 640;
284const HEIGHT: u32 = 480;
285const FRAME_RATE: u32 = 30;
286const BUFFER_SIZE: u32 = WIDTH * HEIGHT * 3 / 2;
287
288const INPUTS: [bindings::v4l2_input; 1] = [bindings::v4l2_input {
289    index: 0,
290    name: *b"Default\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
291    type_: bindings::V4L2_INPUT_TYPE_CAMERA,
292    ..unsafe { std::mem::zeroed() }
293}];
294
295fn default_fmtdesc(queue: QueueType) -> v4l2_fmtdesc {
296    v4l2_fmtdesc {
297        index: 0,
298        type_: queue as u32,
299        pixelformat: PIXELFORMAT,
300        ..Default::default()
301    }
302}
303
304fn default_fmt(queue: QueueType) -> v4l2_format {
305    let pix_mp = bindings::v4l2_pix_format_mplane {
306        width: WIDTH,
307        height: HEIGHT,
308        pixelformat: PIXELFORMAT,
309        field: bindings::v4l2_field_V4L2_FIELD_NONE,
310        colorspace: bindings::v4l2_colorspace_V4L2_COLORSPACE_SRGB,
311        num_planes: 3,
312        plane_fmt: [
313            bindings::v4l2_plane_pix_format {
314                sizeimage: WIDTH * HEIGHT,
315                bytesperline: WIDTH,
316                ..Default::default()
317            },
318            bindings::v4l2_plane_pix_format {
319                sizeimage: WIDTH * HEIGHT / 4,
320                bytesperline: WIDTH / 2,
321                ..Default::default()
322            },
323            bindings::v4l2_plane_pix_format {
324                sizeimage: WIDTH * HEIGHT / 4,
325                bytesperline: WIDTH / 2,
326                ..Default::default()
327            },
328            Default::default(),
329            Default::default(),
330            Default::default(),
331            Default::default(),
332            Default::default(),
333        ],
334        ..Default::default()
335    };
336
337    v4l2_format {
338        type_: queue as u32,
339        fmt: bindings::v4l2_format__bindgen_ty_1 { pix_mp },
340    }
341}
342
343/// Implementations of the ioctls required by a CAPTURE device.
344impl<Q, HM> VirtioMediaIoctlHandler for SimpleCaptureDevice<Q, HM>
345where
346    Q: VirtioMediaEventQueue,
347    HM: VirtioMediaHostMemoryMapper,
348{
349    type Session = SimpleCaptureDeviceSession;
350
351    fn enum_fmt(
352        &mut self,
353        _session: &Self::Session,
354        queue: QueueType,
355        index: u32,
356    ) -> IoctlResult<v4l2_fmtdesc> {
357        if queue != QueueType::VideoCaptureMplane {
358            return Err(libc::EINVAL);
359        }
360        if index > 0 {
361            return Err(libc::EINVAL);
362        }
363
364        Ok(default_fmtdesc(queue))
365    }
366
367    fn g_fmt(&mut self, _session: &Self::Session, queue: QueueType) -> IoctlResult<v4l2_format> {
368        if queue != QueueType::VideoCaptureMplane {
369            return Err(libc::EINVAL);
370        }
371        Ok(default_fmt(queue))
372    }
373
374    fn s_fmt(
375        &mut self,
376        _session: &mut Self::Session,
377        queue: QueueType,
378        _format: v4l2_format,
379    ) -> IoctlResult<v4l2_format> {
380        if queue != QueueType::VideoCaptureMplane {
381            return Err(libc::EINVAL);
382        }
383        Ok(default_fmt(queue))
384    }
385
386    fn try_fmt(
387        &mut self,
388        _session: &Self::Session,
389        queue: QueueType,
390        _format: v4l2_format,
391    ) -> IoctlResult<v4l2_format> {
392        if queue != QueueType::VideoCaptureMplane {
393            return Err(libc::EINVAL);
394        }
395        Ok(default_fmt(queue))
396    }
397
398    fn g_parm(
399        &mut self,
400        _session: &Self::Session,
401        queue: QueueType,
402    ) -> IoctlResult<bindings::v4l2_streamparm> {
403        if queue != QueueType::VideoCaptureMplane {
404            return Err(libc::EINVAL);
405        }
406
407        let mut parm = bindings::v4l2_streamparm {
408            type_: queue as u32,
409            ..Default::default()
410        };
411
412        // SAFETY: The `parm` union is used for the capture type.
413        let capture = unsafe { &mut parm.parm.capture };
414        capture.capability = bindings::V4L2_CAP_TIMEPERFRAME;
415        capture.timeperframe = bindings::v4l2_fract {
416            numerator: 1,
417            denominator: FRAME_RATE,
418        };
419
420        Ok(parm)
421    }
422
423    fn s_parm(
424        &mut self,
425        _session: &mut Self::Session,
426        mut parm: bindings::v4l2_streamparm,
427    ) -> IoctlResult<bindings::v4l2_streamparm> {
428        if parm.type_ != QueueType::VideoCaptureMplane as u32 {
429            return Err(libc::EINVAL);
430        }
431
432        // We just return the fixed values, ignoring what the user set.
433        // SAFETY: The `parm` union is used for the capture type.
434        let capture = unsafe { &mut parm.parm.capture };
435        capture.capability = bindings::V4L2_CAP_TIMEPERFRAME;
436        capture.timeperframe = bindings::v4l2_fract {
437            numerator: 1,
438            denominator: FRAME_RATE,
439        };
440
441        Ok(parm)
442    }
443
444    fn reqbufs(
445        &mut self,
446        session: &mut Self::Session,
447        queue: QueueType,
448        memory: MemoryType,
449        count: u32,
450        _flags: MemoryConsistency,
451    ) -> IoctlResult<v4l2_requestbuffers> {
452        if queue != QueueType::VideoCaptureMplane {
453            return Err(libc::EINVAL);
454        }
455        if memory != MemoryType::Mmap {
456            return Err(libc::EINVAL);
457        }
458        if session.streaming {
459            return Err(libc::EBUSY);
460        }
461        // Buffers cannot be requested on a session if there is already another session with
462        // allocated buffers.
463        match self.active_session {
464            Some(id) if id != session.id => return Err(libc::EBUSY),
465            _ => (),
466        }
467
468        // Reqbufs(0) is an implicit streamoff.
469        if count == 0 {
470            self.active_session = None;
471            self.streamoff(session, queue)?;
472        } else {
473            // TODO factorize with streamoff.
474            session.queued_buffers.clear();
475            for buffer in session.buffers.iter_mut() {
476                buffer.set_state(BufferState::New);
477            }
478            self.active_session = Some(session.id);
479        }
480
481        let count = std::cmp::min(count, 32);
482
483        for buffer in &session.buffers {
484            self.mmap_manager.unregister_buffer(buffer.offset);
485        }
486
487        session.buffers = (0..count)
488            .map(|i| {
489                MemFdBuffer::new(BUFFER_SIZE as u64)
490                    .map_err(|e| {
491                        log::error!("failed to allocate MMAP buffers: {:#}", e);
492                        libc::ENOMEM
493                    })
494                    .and_then(|fd| {
495                        let offset = self
496                            .mmap_manager
497                            .register_buffer(None, BUFFER_SIZE)
498                            .map_err(|_| libc::EINVAL)?;
499
500                        let mut v4l2_buffer = V4l2Buffer::new(queue, i, MemoryType::Mmap);
501                        if let V4l2PlanesWithBackingMut::Mmap(mut planes) =
502                            v4l2_buffer.planes_with_backing_iter_mut()
503                        {
504                            // SAFETY: every buffer has at least one plane.
505                            let mut plane = planes.next().unwrap();
506                            plane.set_mem_offset(offset);
507                            *plane.length = BUFFER_SIZE;
508                        } else {
509                            // SAFETY: we have just set the buffer type to MMAP. Reaching this point means a bug in
510                            // the code.
511                            panic!()
512                        }
513                        v4l2_buffer.set_field(BufferField::None);
514                        v4l2_buffer.set_flags(BufferFlags::TIMESTAMP_MONOTONIC);
515
516                        Ok(Buffer {
517                            state: BufferState::New,
518                            v4l2_buffer,
519                            fd,
520                            offset,
521                        })
522                    })
523            })
524            .collect::<Result<_, _>>()?;
525
526        Ok(v4l2_requestbuffers {
527            count,
528            type_: queue as u32,
529            memory: memory as u32,
530            capabilities: (BufferCapabilities::SUPPORTS_MMAP
531                | BufferCapabilities::SUPPORTS_ORPHANED_BUFS)
532                .bits(),
533            // This device does not support V4L2_BUF_CAP_SUPPORTS_MMAP_CACHE_HINTS,
534            // so the flags field in v4l2_requestbuffers must be 0.
535            flags: 0,
536            ..Default::default()
537        })
538    }
539
540    fn querybuf(
541        &mut self,
542        session: &Self::Session,
543        queue: QueueType,
544        index: u32,
545    ) -> IoctlResult<v4l2r::ioctl::V4l2Buffer> {
546        if queue != QueueType::VideoCaptureMplane {
547            return Err(libc::EINVAL);
548        }
549        let buffer = session.buffers.get(index as usize).ok_or(libc::EINVAL)?;
550
551        Ok(buffer.v4l2_buffer.clone())
552    }
553
554    fn qbuf(
555        &mut self,
556        session: &mut Self::Session,
557        buffer: v4l2r::ioctl::V4l2Buffer,
558        _guest_regions: Vec<Vec<SgEntry>>,
559    ) -> IoctlResult<v4l2r::ioctl::V4l2Buffer> {
560        let host_buffer = session
561            .buffers
562            .get_mut(buffer.index() as usize)
563            .ok_or(libc::EINVAL)?;
564        // Attempt to queue already queued buffer.
565        if matches!(host_buffer.state, BufferState::Incoming) {
566            return Err(libc::EINVAL);
567        }
568
569        host_buffer.set_state(BufferState::Incoming);
570        session.queued_buffers.push_back(buffer.index() as usize);
571
572        let buffer = host_buffer.v4l2_buffer.clone();
573
574        if session.streaming {
575            session.process_queued_buffers(&mut self.evt_queue)?;
576        }
577
578        Ok(buffer)
579    }
580
581    fn streamon(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
582        if queue != QueueType::VideoCaptureMplane || session.buffers.is_empty() {
583            return Err(libc::EINVAL);
584        }
585        session.streaming = true;
586
587        session.process_queued_buffers(&mut self.evt_queue)?;
588
589        Ok(())
590    }
591
592    fn streamoff(&mut self, session: &mut Self::Session, queue: QueueType) -> IoctlResult<()> {
593        if queue != QueueType::VideoCaptureMplane {
594            return Err(libc::EINVAL);
595        }
596        session.streaming = false;
597        session.queued_buffers.clear();
598        for buffer in session.buffers.iter_mut() {
599            buffer.set_state(BufferState::New);
600        }
601
602        Ok(())
603    }
604
605    fn g_input(&mut self, _session: &Self::Session) -> IoctlResult<i32> {
606        Ok(0)
607    }
608
609    fn s_input(&mut self, _session: &mut Self::Session, input: i32) -> IoctlResult<i32> {
610        if input != 0 {
611            Err(libc::EINVAL)
612        } else {
613            Ok(0)
614        }
615    }
616
617    fn enuminput(
618        &mut self,
619        _session: &Self::Session,
620        index: u32,
621    ) -> IoctlResult<bindings::v4l2_input> {
622        match INPUTS.get(index as usize) {
623            Some(&input) => Ok(input),
624            None => Err(libc::EINVAL),
625        }
626    }
627
628    fn enum_framesizes(
629        &mut self,
630        _session: &Self::Session,
631        index: u32,
632        pixel_format: u32,
633    ) -> IoctlResult<bindings::v4l2_frmsizeenum> {
634        if pixel_format != PIXELFORMAT {
635            return Err(libc::EINVAL);
636        }
637        if index > 0 {
638            return Err(libc::EINVAL);
639        }
640
641        Ok(bindings::v4l2_frmsizeenum {
642            index,
643            pixel_format,
644            type_: bindings::v4l2_frmsizetypes_V4L2_FRMSIZE_TYPE_DISCRETE,
645            __bindgen_anon_1: bindings::v4l2_frmsizeenum__bindgen_ty_1 {
646                discrete: bindings::v4l2_frmsize_discrete {
647                    width: WIDTH,
648                    height: HEIGHT,
649                },
650            },
651            ..Default::default()
652        })
653    }
654
655    fn enum_frameintervals(
656        &mut self,
657        _session: &Self::Session,
658        index: u32,
659        pixel_format: u32,
660        width: u32,
661        height: u32,
662    ) -> IoctlResult<bindings::v4l2_frmivalenum> {
663        if pixel_format != PIXELFORMAT {
664            return Err(libc::EINVAL);
665        }
666        if width != WIDTH || height != HEIGHT {
667            return Err(libc::EINVAL);
668        }
669        if index > 0 {
670            return Err(libc::EINVAL);
671        }
672
673        Ok(bindings::v4l2_frmivalenum {
674            index,
675            pixel_format,
676            width,
677            height,
678            type_: bindings::v4l2_frmivaltypes_V4L2_FRMIVAL_TYPE_DISCRETE,
679            __bindgen_anon_1: bindings::v4l2_frmivalenum__bindgen_ty_1 {
680                discrete: bindings::v4l2_fract {
681                    numerator: 1,
682                    denominator: FRAME_RATE,
683                },
684            },
685            ..Default::default()
686        })
687    }
688}