xref: /aosp_15_r20/external/virtio-media/device/src/lib.rs (revision 1b4853f54772485c5dd4001ae33a7a958bcc97a1)
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 //! This crate contains host-side helpers to write virtio-media devices and full devices
6 //! implementations.
7 //!
8 //! Both helpers and devices are VMM-independent and rely on a handful of traits being implemented
9 //! to operate on a given VMM. This means that implementing a specific device, and adding support
10 //! for all virtio-media devices on a given VMM, are two completely orthogonal tasks. Adding
11 //! support for a VMM makes all the devices relying on this crate available. Conversely, writing a
12 //! new device using this crate makes it available to all supported VMMs.
13 //!
14 //! # Traits to implement by the VMM
15 //!
16 //! * Descriptor chains must implement `Read` and `Write` on their device-readable and
17 //!   device-writable parts, respectively. This allows devices to read commands and writes
18 //!   responses.
19 //! * The event queue must implement the `VirtioMediaEventQueue` trait to allow devices to send
20 //!   events to the guest.
21 //! * The guest memory must be made accessible through an implementation of
22 //!   `VirtioMediaGuestMemoryMapper`.
23 //! * Optionally, .... can be implemented if the host supports mapping MMAP buffers into the guest
24 //!   address space.
25 //!
26 //! These traits allow any device that implements `VirtioMediaDevice` to run on any VMM that
27 //! implements them.
28 //!
29 //! # Anatomy of a device
30 //!
31 //! Devices implement `VirtioMediaDevice` to provide ways to create and close sessions, and to make
32 //! MMAP buffers visible to the guest (if supported). They also typically implement
33 //! `VirtioMediaIoctlHandler` and make use of `virtio_media_dispatch_ioctl` to handle ioctls
34 //! simply.
35 //!
36 //! The VMM then uses `VirtioMediaDeviceRunner` in order to ask it to process a command whenever
37 //! one arrives on the command queue.
38 //!
39 //! By following this pattern, devices never need to care about deserializing and validating the
40 //! virtio-media protocol. Instead, their relevant methods are invoked when needed, on validated
41 //! input, while protocol errors are handled upstream in a way that is consistent for all devices.
42 //!
43 //! The devices currently in this crate are:
44 //!
45 //! * A device that proxies any host V4L2 device into the guest, in the `crate::v4l2_device_proxy`
46 //!   module.
47 
48 pub mod devices;
49 pub mod ioctl;
50 pub mod memfd;
51 pub mod mmap;
52 pub mod poll;
53 pub mod protocol;
54 
55 use poll::SessionPoller;
56 pub use v4l2r;
57 
58 use std::collections::HashMap;
59 use std::io::Result as IoResult;
60 use std::os::fd::BorrowedFd;
61 
62 use anyhow::Context;
63 use log::error;
64 use zerocopy::AsBytes;
65 use zerocopy::FromBytes;
66 
67 use protocol::*;
68 
69 /// Trait for reading objects from a reader, e.g. the device-readable section of a descriptor
70 /// chain.
71 pub trait FromDescriptorChain {
read_from_chain<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> where Self: Sized72     fn read_from_chain<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self>
73     where
74         Self: Sized;
75 }
76 
77 /// Trait for writing objects to a writer, e.g. the device-writable section of a descriptor chain.
78 pub trait ToDescriptorChain {
write_to_chain<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()>79     fn write_to_chain<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()>;
80 }
81 
82 /// Trait for sending V4L2 events to the driver.
83 pub trait VirtioMediaEventQueue {
84     /// Wait until an event descriptor becomes available and send `event` to the guest.
send_event(&mut self, event: V4l2Event)85     fn send_event(&mut self, event: V4l2Event);
86 
87     /// Wait until an event descriptor becomes available and send `errno` as an error event to the
88     /// guest.
send_error(&mut self, session_id: u32, errno: i32)89     fn send_error(&mut self, session_id: u32, errno: i32) {
90         self.send_event(V4l2Event::Error(ErrorEvent::new(session_id, errno)));
91     }
92 }
93 
94 /// Trait for representing a range of guest memory that has been mapped linearly into the host's
95 /// address space.
96 pub trait GuestMemoryRange {
as_ptr(&self) -> *const u897     fn as_ptr(&self) -> *const u8;
as_mut_ptr(&mut self) -> *mut u898     fn as_mut_ptr(&mut self) -> *mut u8;
99 }
100 
101 /// Trait enabling guest memory linear access for the device.
102 ///
103 /// Although the host can access the guest memory, it sometimes need to have a linear view of
104 /// sparse areas. This trait provides a way to perform such mappings.
105 ///
106 /// Note to devices: [`VirtioMediaGuestMemoryMapper::GuestMemoryMapping`] instances must be held
107 /// for as long as the device might access the memory to avoid race conditions, as some
108 /// implementations might e.g. write back into the guest memory at destruction time.
109 pub trait VirtioMediaGuestMemoryMapper {
110     /// Host-side linear mapping of sparse guest memory.
111     type GuestMemoryMapping: GuestMemoryRange;
112 
113     /// Maps `sgs`, which contains a list of guest-physical SG entries into a linear mapping on the
114     /// host.
new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping>115     fn new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping>;
116 }
117 
118 /// Trait for mapping host buffers into the guest physical address space.
119 ///
120 /// An VMM-side implementation of this trait is needed in order to map `MMAP` buffers into the
121 /// guest.
122 ///
123 /// If the functionality is not needed, `()` can be passed in place of an implementor of this
124 /// trait. It will return `ENOTTY` to each `mmap` attempt, effectively disabling the ability to
125 /// map `MMAP` buffers into the guest.
126 pub trait VirtioMediaHostMemoryMapper {
127     /// Maps `length` bytes of host memory starting at `offset` and backed by `buffer` into the
128     /// guest's shared memory region.
129     ///
130     /// Returns the offset in the guest shared memory region of the start of the mapped memory on
131     /// success, or a `libc` error code in case of failure.
add_mapping( &mut self, buffer: BorrowedFd, length: u64, offset: u64, rw: bool, ) -> Result<u64, i32>132     fn add_mapping(
133         &mut self,
134         buffer: BorrowedFd,
135         length: u64,
136         offset: u64,
137         rw: bool,
138     ) -> Result<u64, i32>;
139 
140     /// Removes a guest mapping previously created at shared memory region offset `shm_offset`.
remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32>141     fn remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32>;
142 }
143 
144 /// No-op implementation of `VirtioMediaHostMemoryMapper`. Can be used for testing purposes or when
145 /// it is not needed to map `MMAP` buffers into the guest.
146 impl VirtioMediaHostMemoryMapper for () {
add_mapping(&mut self, _: BorrowedFd, _: u64, _: u64, _: bool) -> Result<u64, i32>147     fn add_mapping(&mut self, _: BorrowedFd, _: u64, _: u64, _: bool) -> Result<u64, i32> {
148         Err(libc::ENOTTY)
149     }
150 
remove_mapping(&mut self, _: u64) -> Result<(), i32>151     fn remove_mapping(&mut self, _: u64) -> Result<(), i32> {
152         Err(libc::ENOTTY)
153     }
154 }
155 
156 pub trait VirtioMediaDeviceSession {
157     /// Returns the file descriptor that the client can listen to in order to know when a session
158     /// event has occurred. The FD signals that it is readable when the device's `process_events`
159     /// should be called.
160     ///
161     /// If this method returns `None`, then the session does not need to be polled by the client,
162     /// and `process_events` does not need to be called either.
poll_fd(&self) -> Option<BorrowedFd>163     fn poll_fd(&self) -> Option<BorrowedFd>;
164 }
165 
166 /// Trait for implementing virtio-media devices.
167 ///
168 /// The preferred way to use this trait is to wrap implementations in a
169 /// [`VirtioMediaDeviceRunner`], which takes care of reading and dispatching commands. In addition,
170 /// [`ioctl::VirtioMediaIoctlHandler`] should also be used to automatically parse and dispatch
171 /// ioctls.
172 pub trait VirtioMediaDevice<Reader: std::io::Read, Writer: std::io::Write> {
173     type Session: VirtioMediaDeviceSession;
174 
175     /// Create a new session which ID is `session_id`.
176     ///
177     /// The error value returned is the error code to send back to the guest.
new_session(&mut self, session_id: u32) -> Result<Self::Session, i32>178     fn new_session(&mut self, session_id: u32) -> Result<Self::Session, i32>;
179     /// Close the passed session.
close_session(&mut self, session: Self::Session)180     fn close_session(&mut self, session: Self::Session);
181 
182     /// Perform the IOCTL command and write the response into `writer`.
183     ///
184     /// The flow for performing a given `ioctl` is to read the parameters from `reader`, perform
185     /// the operation, and then write the result on `writer`. Events triggered by a given ioctl can
186     /// be queued on `evt_queue`.
187     ///
188     /// Only returns an error if the response could not be properly written ; all other errors are
189     /// propagated to the guest and considered normal operation from the host's point of view.
190     ///
191     /// The recommended implementation of this method is to just invoke
192     /// `virtio_media_dispatch_ioctl` on an implementation of `VirtioMediaIoctlHandler`, so all the
193     /// details of ioctl parsing and validation are taken care of by this crate.
do_ioctl( &mut self, session: &mut Self::Session, ioctl: V4l2Ioctl, reader: &mut Reader, writer: &mut Writer, ) -> IoResult<()>194     fn do_ioctl(
195         &mut self,
196         session: &mut Self::Session,
197         ioctl: V4l2Ioctl,
198         reader: &mut Reader,
199         writer: &mut Writer,
200     ) -> IoResult<()>;
201 
202     /// Performs the MMAP command.
203     ///
204     /// Only returns an error if the response could not be properly written ; all other errors are
205     /// propagated to the guest.
206     //
207     // TODO: flags should be a dedicated enum?
do_mmap( &mut self, session: &mut Self::Session, flags: u32, offset: u32, ) -> Result<(u64, u64), i32>208     fn do_mmap(
209         &mut self,
210         session: &mut Self::Session,
211         flags: u32,
212         offset: u32,
213     ) -> Result<(u64, u64), i32>;
214     /// Performs the MUNMAP command.
215     ///
216     /// Only returns an error if the response could not be properly written ; all other errors are
217     /// propagated to the guest.
do_munmap(&mut self, guest_addr: u64) -> Result<(), i32>218     fn do_munmap(&mut self, guest_addr: u64) -> Result<(), i32>;
219 
process_events(&mut self, _session: &mut Self::Session) -> Result<(), i32>220     fn process_events(&mut self, _session: &mut Self::Session) -> Result<(), i32> {
221         panic!("process_events needs to be implemented")
222     }
223 }
224 
225 /// Wrapping structure for a `VirtioMediaDevice` managing its sessions and providing methods for
226 /// processing its commands.
227 pub struct VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
228 where
229     Reader: std::io::Read,
230     Writer: std::io::Write,
231     Device: VirtioMediaDevice<Reader, Writer>,
232     Poller: SessionPoller,
233 {
234     pub device: Device,
235     poller: Poller,
236     pub sessions: HashMap<u32, Device::Session>,
237     // TODO: recycle session ids...
238     session_id_counter: u32,
239 }
240 
241 impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
242 where
243     Reader: std::io::Read,
244     Writer: std::io::Write,
245     Device: VirtioMediaDevice<Reader, Writer>,
246     Poller: SessionPoller,
247 {
new(device: Device, poller: Poller) -> Self248     pub fn new(device: Device, poller: Poller) -> Self {
249         Self {
250             device,
251             poller,
252             sessions: Default::default(),
253             session_id_counter: 0,
254         }
255     }
256 }
257 
258 /// Crate-local extension trait for reading objects from the device-readable section of a
259 /// descriptor chain.
260 trait ReadFromDescriptorChain {
read_obj<T: FromBytes>(&mut self) -> std::io::Result<T>261     fn read_obj<T: FromBytes>(&mut self) -> std::io::Result<T>;
262 }
263 
264 /// Any implementor of `Read` can be used to read virtio-media commands.
265 impl<R> ReadFromDescriptorChain for R
266 where
267     R: std::io::Read,
268 {
read_obj<T: FromBytes>(&mut self) -> std::io::Result<T>269     fn read_obj<T: FromBytes>(&mut self) -> std::io::Result<T> {
270         // We use `zeroed` instead of `uninit` because `read_exact` cannot be called with
271         // uninitialized memory. Since `T` implements `FromBytes`, its zeroed form is valid and
272         // initialized.
273         let mut obj = std::mem::MaybeUninit::zeroed();
274         // Safe because the slice boundaries cover `obj`, and the slice doesn't outlive it.
275         let slice = unsafe {
276             std::slice::from_raw_parts_mut(obj.as_mut_ptr() as *mut u8, std::mem::size_of::<T>())
277         };
278 
279         self.read_exact(slice)?;
280 
281         // Safe because obj can be initialized from an array of bytes.
282         Ok(unsafe { obj.assume_init() })
283     }
284 }
285 
286 /// Crate-local extension trait for writing objects and responses into the device-writable section
287 /// of a descriptor chain.
288 trait WriteToDescriptorChain {
289     /// Write an arbitrary object to the guest.
write_obj<T: AsBytes>(&mut self, obj: &T) -> IoResult<()>290     fn write_obj<T: AsBytes>(&mut self, obj: &T) -> IoResult<()>;
291 
292     /// Write a command response to the guest.
write_response<T: AsBytes>(&mut self, response: T) -> IoResult<()>293     fn write_response<T: AsBytes>(&mut self, response: T) -> IoResult<()> {
294         self.write_obj(&response)
295     }
296 
297     /// Send `code` as the error code of an error response.
write_err_response(&mut self, code: libc::c_int) -> IoResult<()>298     fn write_err_response(&mut self, code: libc::c_int) -> IoResult<()> {
299         self.write_response(RespHeader::err(code))
300     }
301 }
302 
303 /// Any implementor of `Write` can be used to write virtio-media responses.
304 impl<W> WriteToDescriptorChain for W
305 where
306     W: std::io::Write,
307 {
write_obj<T: AsBytes>(&mut self, obj: &T) -> IoResult<()>308     fn write_obj<T: AsBytes>(&mut self, obj: &T) -> IoResult<()> {
309         self.write_all(obj.as_bytes())
310     }
311 }
312 
313 impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
314 where
315     Reader: std::io::Read,
316     Writer: std::io::Write,
317     Device: VirtioMediaDevice<Reader, Writer>,
318     Poller: SessionPoller,
319 {
320     /// Handle a single command from the virtio queue.
321     ///
322     /// `reader` and `writer` are the device-readable and device-writable sections of the
323     /// descriptor chain containing the command. After this method has returned, the caller is
324     /// responsible for returning the used descriptor chain to the guest.
325     ///
326     /// This method never returns an error, as doing so would halt the worker thread. All errors
327     /// are propagated to the guest, with the exception of errors triggered while writing the
328     /// response which are logged on the host side.
handle_command(&mut self, reader: &mut Reader, writer: &mut Writer)329     pub fn handle_command(&mut self, reader: &mut Reader, writer: &mut Writer) {
330         let hdr: CmdHeader = match reader.read_obj() {
331             Ok(hdr) => hdr,
332             Err(e) => {
333                 error!("error while reading command header: {:#}", e);
334                 let _ = writer.write_err_response(libc::EINVAL);
335                 return;
336             }
337         };
338 
339         let res = match hdr.cmd {
340             VIRTIO_MEDIA_CMD_OPEN => {
341                 let session_id = self.session_id_counter;
342 
343                 match self.device.new_session(session_id) {
344                     Ok(session) => {
345                         if let Some(fd) = session.poll_fd() {
346                             match self.poller.add_session(fd, session_id) {
347                                 Ok(()) => {
348                                     self.sessions.insert(session_id, session);
349                                     self.session_id_counter += 1;
350                                     writer.write_response(OpenResp::ok(session_id))
351                                 }
352                                 Err(e) => {
353                                     log::error!(
354                                         "failed to register poll FD for new session: {}",
355                                         e
356                                     );
357                                     self.device.close_session(session);
358                                     writer.write_err_response(e)
359                                 }
360                             }
361                         } else {
362                             self.sessions.insert(session_id, session);
363                             self.session_id_counter += 1;
364                             writer.write_response(OpenResp::ok(session_id))
365                         }
366                     }
367                     Err(e) => writer.write_err_response(e),
368                 }
369                 .context("while writing response for OPEN command")
370             }
371             .context("while writing response for OPEN command"),
372             VIRTIO_MEDIA_CMD_CLOSE => reader
373                 .read_obj()
374                 .context("while reading CLOSE command")
375                 .map(|CloseCmd { session_id, .. }| {
376                     if let Some(session) = self.sessions.remove(&session_id) {
377                         if let Some(fd) = session.poll_fd() {
378                             self.poller.remove_session(fd);
379                         }
380                         self.device.close_session(session);
381                     }
382                 }),
383             VIRTIO_MEDIA_CMD_IOCTL => reader
384                 .read_obj()
385                 .context("while reading IOCTL command")
386                 .and_then(|IoctlCmd { session_id, code }| {
387                     match self.sessions.get_mut(&session_id) {
388                         Some(session) => match V4l2Ioctl::n(code) {
389                             Some(ioctl) => self.device.do_ioctl(session, ioctl, reader, writer),
390                             None => {
391                                 error!("unknown ioctl code {}", code);
392                                 writer.write_err_response(libc::ENOTTY)
393                             }
394                         },
395                         None => writer.write_err_response(libc::EINVAL),
396                     }
397                     .context("while writing response for IOCTL command")
398                 }),
399             VIRTIO_MEDIA_CMD_MMAP => reader
400                 .read_obj()
401                 .context("while reading MMAP command")
402                 .and_then(
403                     |MmapCmd {
404                          session_id,
405                          flags,
406                          offset,
407                      }| {
408                         match self
409                             .sessions
410                             .get_mut(&session_id)
411                             .ok_or(libc::EINVAL)
412                             .and_then(|session| self.device.do_mmap(session, flags, offset))
413                         {
414                             Ok((guest_addr, size)) => {
415                                 writer.write_response(MmapResp::ok(guest_addr, size))
416                             }
417                             Err(e) => writer.write_err_response(e),
418                         }
419                         .context("while writing response for MMAP command")
420                     },
421                 ),
422             VIRTIO_MEDIA_CMD_MUNMAP => reader
423                 .read_obj()
424                 .context("while reading UNMMAP command")
425                 .and_then(|MunmapCmd { guest_addr }| {
426                     match self.device.do_munmap(guest_addr) {
427                         Ok(()) => writer.write_response(MunmapResp::ok()),
428                         Err(e) => writer.write_err_response(e),
429                     }
430                     .context("while writing response for MUNMAP command")
431                 }),
432             _ => writer
433                 .write_err_response(libc::ENOTTY)
434                 .context("while writing error response for invalid command"),
435         };
436 
437         if let Err(e) = res {
438             error!("error while processing command: {:#}", e);
439             let _ = writer.write_err_response(libc::EINVAL);
440         }
441     }
442 
443     /// Returns the device this runner has been created from.
into_device(self) -> Device444     pub fn into_device(self) -> Device {
445         self.device
446     }
447 }
448