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