• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 io;
50 pub mod ioctl;
51 pub mod memfd;
52 pub mod mmap;
53 pub mod poll;
54 pub mod protocol;
55 
56 use io::ReadFromDescriptorChain;
57 use io::WriteToDescriptorChain;
58 use poll::SessionPoller;
59 pub use v4l2r;
60 
61 use std::collections::HashMap;
62 use std::io::Result as IoResult;
63 use std::os::fd::BorrowedFd;
64 
65 use anyhow::Context;
66 use log::error;
67 
68 use protocol::*;
69 
70 /// Trait for sending V4L2 events to the driver.
71 pub trait VirtioMediaEventQueue {
72     /// Wait until an event descriptor becomes available and send `event` to the guest.
send_event(&mut self, event: V4l2Event)73     fn send_event(&mut self, event: V4l2Event);
74 
75     /// Wait until an event descriptor becomes available and send `errno` as an error event to the
76     /// guest.
send_error(&mut self, session_id: u32, errno: i32)77     fn send_error(&mut self, session_id: u32, errno: i32) {
78         self.send_event(V4l2Event::Error(ErrorEvent::new(session_id, errno)));
79     }
80 }
81 
82 /// Trait for representing a range of guest memory that has been mapped linearly into the host's
83 /// address space.
84 pub trait GuestMemoryRange {
as_ptr(&self) -> *const u885     fn as_ptr(&self) -> *const u8;
as_mut_ptr(&mut self) -> *mut u886     fn as_mut_ptr(&mut self) -> *mut u8;
87 }
88 
89 /// Trait enabling guest memory linear access for the device.
90 ///
91 /// Although the host can access the guest memory, it sometimes need to have a linear view of
92 /// sparse areas. This trait provides a way to perform such mappings.
93 ///
94 /// Note to devices: [`VirtioMediaGuestMemoryMapper::GuestMemoryMapping`] instances must be held
95 /// for as long as the device might access the memory to avoid race conditions, as some
96 /// implementations might e.g. write back into the guest memory at destruction time.
97 pub trait VirtioMediaGuestMemoryMapper {
98     /// Host-side linear mapping of sparse guest memory.
99     type GuestMemoryMapping: GuestMemoryRange;
100 
101     /// Maps `sgs`, which contains a list of guest-physical SG entries into a linear mapping on the
102     /// host.
new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping>103     fn new_mapping(&self, sgs: Vec<SgEntry>) -> anyhow::Result<Self::GuestMemoryMapping>;
104 }
105 
106 /// Trait for mapping host buffers into the guest physical address space.
107 ///
108 /// An VMM-side implementation of this trait is needed in order to map `MMAP` buffers into the
109 /// guest.
110 ///
111 /// If the functionality is not needed, `()` can be passed in place of an implementor of this
112 /// trait. It will return `ENOTTY` to each `mmap` attempt, effectively disabling the ability to
113 /// map `MMAP` buffers into the guest.
114 pub trait VirtioMediaHostMemoryMapper {
115     /// Maps `length` bytes of host memory starting at `offset` and backed by `buffer` into the
116     /// guest's shared memory region.
117     ///
118     /// Returns the offset in the guest shared memory region of the start of the mapped memory on
119     /// 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>120     fn add_mapping(
121         &mut self,
122         buffer: BorrowedFd,
123         length: u64,
124         offset: u64,
125         rw: bool,
126     ) -> Result<u64, i32>;
127 
128     /// Removes a guest mapping previously created at shared memory region offset `shm_offset`.
remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32>129     fn remove_mapping(&mut self, shm_offset: u64) -> Result<(), i32>;
130 }
131 
132 /// No-op implementation of `VirtioMediaHostMemoryMapper`. Can be used for testing purposes or when
133 /// it is not needed to map `MMAP` buffers into the guest.
134 impl VirtioMediaHostMemoryMapper for () {
add_mapping(&mut self, _: BorrowedFd, _: u64, _: u64, _: bool) -> Result<u64, i32>135     fn add_mapping(&mut self, _: BorrowedFd, _: u64, _: u64, _: bool) -> Result<u64, i32> {
136         Err(libc::ENOTTY)
137     }
138 
remove_mapping(&mut self, _: u64) -> Result<(), i32>139     fn remove_mapping(&mut self, _: u64) -> Result<(), i32> {
140         Err(libc::ENOTTY)
141     }
142 }
143 
144 pub trait VirtioMediaDeviceSession {
145     /// Returns the file descriptor that the client can listen to in order to know when a session
146     /// event has occurred. The FD signals that it is readable when the device's `process_events`
147     /// should be called.
148     ///
149     /// If this method returns `None`, then the session does not need to be polled by the client,
150     /// and `process_events` does not need to be called either.
poll_fd(&self) -> Option<BorrowedFd>151     fn poll_fd(&self) -> Option<BorrowedFd>;
152 }
153 
154 /// Trait for implementing virtio-media devices.
155 ///
156 /// The preferred way to use this trait is to wrap implementations in a
157 /// [`VirtioMediaDeviceRunner`], which takes care of reading and dispatching commands. In addition,
158 /// [`ioctl::VirtioMediaIoctlHandler`] should also be used to automatically parse and dispatch
159 /// ioctls.
160 pub trait VirtioMediaDevice<Reader: ReadFromDescriptorChain, Writer: WriteToDescriptorChain> {
161     type Session: VirtioMediaDeviceSession;
162 
163     /// Create a new session which ID is `session_id`.
164     ///
165     /// 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>166     fn new_session(&mut self, session_id: u32) -> Result<Self::Session, i32>;
167     /// Close the passed session.
close_session(&mut self, session: Self::Session)168     fn close_session(&mut self, session: Self::Session);
169 
170     /// Perform the IOCTL command and write the response into `writer`.
171     ///
172     /// The flow for performing a given `ioctl` is to read the parameters from `reader`, perform
173     /// the operation, and then write the result on `writer`. Events triggered by a given ioctl can
174     /// be queued on `evt_queue`.
175     ///
176     /// Only returns an error if the response could not be properly written ; all other errors are
177     /// propagated to the guest and considered normal operation from the host's point of view.
178     ///
179     /// The recommended implementation of this method is to just invoke
180     /// `virtio_media_dispatch_ioctl` on an implementation of `VirtioMediaIoctlHandler`, so all the
181     /// 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<()>182     fn do_ioctl(
183         &mut self,
184         session: &mut Self::Session,
185         ioctl: V4l2Ioctl,
186         reader: &mut Reader,
187         writer: &mut Writer,
188     ) -> IoResult<()>;
189 
190     /// Performs the MMAP command.
191     ///
192     /// Only returns an error if the response could not be properly written ; all other errors are
193     /// propagated to the guest.
194     //
195     // TODO: flags should be a dedicated enum?
do_mmap( &mut self, session: &mut Self::Session, flags: u32, offset: u32, ) -> Result<(u64, u64), i32>196     fn do_mmap(
197         &mut self,
198         session: &mut Self::Session,
199         flags: u32,
200         offset: u32,
201     ) -> Result<(u64, u64), i32>;
202     /// Performs the MUNMAP command.
203     ///
204     /// Only returns an error if the response could not be properly written ; all other errors are
205     /// propagated to the guest.
do_munmap(&mut self, guest_addr: u64) -> Result<(), i32>206     fn do_munmap(&mut self, guest_addr: u64) -> Result<(), i32>;
207 
process_events(&mut self, _session: &mut Self::Session) -> Result<(), i32>208     fn process_events(&mut self, _session: &mut Self::Session) -> Result<(), i32> {
209         panic!("process_events needs to be implemented")
210     }
211 }
212 
213 /// Wrapping structure for a `VirtioMediaDevice` managing its sessions and providing methods for
214 /// processing its commands.
215 pub struct VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
216 where
217     Reader: ReadFromDescriptorChain,
218     Writer: WriteToDescriptorChain,
219     Device: VirtioMediaDevice<Reader, Writer>,
220     Poller: SessionPoller,
221 {
222     pub device: Device,
223     poller: Poller,
224     pub sessions: HashMap<u32, Device::Session>,
225     // TODO: recycle session ids...
226     session_id_counter: u32,
227 }
228 
229 impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
230 where
231     Reader: ReadFromDescriptorChain,
232     Writer: WriteToDescriptorChain,
233     Device: VirtioMediaDevice<Reader, Writer>,
234     Poller: SessionPoller,
235 {
new(device: Device, poller: Poller) -> Self236     pub fn new(device: Device, poller: Poller) -> Self {
237         Self {
238             device,
239             poller,
240             sessions: Default::default(),
241             session_id_counter: 0,
242         }
243     }
244 }
245 
246 impl<Reader, Writer, Device, Poller> VirtioMediaDeviceRunner<Reader, Writer, Device, Poller>
247 where
248     Reader: ReadFromDescriptorChain,
249     Writer: WriteToDescriptorChain,
250     Device: VirtioMediaDevice<Reader, Writer>,
251     Poller: SessionPoller,
252 {
253     /// Handle a single command from the virtio queue.
254     ///
255     /// `reader` and `writer` are the device-readable and device-writable sections of the
256     /// descriptor chain containing the command. After this method has returned, the caller is
257     /// responsible for returning the used descriptor chain to the guest.
258     ///
259     /// This method never returns an error, as doing so would halt the worker thread. All errors
260     /// are propagated to the guest, with the exception of errors triggered while writing the
261     /// response which are logged on the host side.
handle_command(&mut self, reader: &mut Reader, writer: &mut Writer)262     pub fn handle_command(&mut self, reader: &mut Reader, writer: &mut Writer) {
263         let hdr = match reader.read_obj::<CmdHeader>() {
264             Ok(hdr) => hdr,
265             Err(e) => {
266                 error!("error while reading command header: {:#}", e);
267                 let _ = writer.write_err_response(libc::EINVAL);
268                 return;
269             }
270         };
271 
272         let res = match hdr.cmd {
273             VIRTIO_MEDIA_CMD_OPEN => {
274                 let session_id = self.session_id_counter;
275 
276                 match self.device.new_session(session_id) {
277                     Ok(session) => {
278                         if let Some(fd) = session.poll_fd() {
279                             match self.poller.add_session(fd, session_id) {
280                                 Ok(()) => {
281                                     self.sessions.insert(session_id, session);
282                                     self.session_id_counter += 1;
283                                     writer.write_response(OpenResp::ok(session_id))
284                                 }
285                                 Err(e) => {
286                                     log::error!(
287                                         "failed to register poll FD for new session: {}",
288                                         e
289                                     );
290                                     self.device.close_session(session);
291                                     writer.write_err_response(e)
292                                 }
293                             }
294                         } else {
295                             self.sessions.insert(session_id, session);
296                             self.session_id_counter += 1;
297                             writer.write_response(OpenResp::ok(session_id))
298                         }
299                     }
300                     Err(e) => writer.write_err_response(e),
301                 }
302                 .context("while writing response for OPEN command")
303             }
304             .context("while writing response for OPEN command"),
305             VIRTIO_MEDIA_CMD_CLOSE => reader
306                 .read_obj()
307                 .context("while reading CLOSE command")
308                 .map(|CloseCmd { session_id, .. }| {
309                     if let Some(session) = self.sessions.remove(&session_id) {
310                         if let Some(fd) = session.poll_fd() {
311                             self.poller.remove_session(fd);
312                         }
313                         self.device.close_session(session);
314                     }
315                 }),
316             VIRTIO_MEDIA_CMD_IOCTL => reader
317                 .read_obj()
318                 .context("while reading IOCTL command")
319                 .and_then(|IoctlCmd { session_id, code }| {
320                     match self.sessions.get_mut(&session_id) {
321                         Some(session) => match V4l2Ioctl::n(code) {
322                             Some(ioctl) => self.device.do_ioctl(session, ioctl, reader, writer),
323                             None => {
324                                 error!("unknown ioctl code {}", code);
325                                 writer.write_err_response(libc::ENOTTY)
326                             }
327                         },
328                         None => writer.write_err_response(libc::EINVAL),
329                     }
330                     .context("while writing response for IOCTL command")
331                 }),
332             VIRTIO_MEDIA_CMD_MMAP => reader
333                 .read_obj()
334                 .context("while reading MMAP command")
335                 .and_then(
336                     |MmapCmd {
337                          session_id,
338                          flags,
339                          offset,
340                      }| {
341                         match self
342                             .sessions
343                             .get_mut(&session_id)
344                             .ok_or(libc::EINVAL)
345                             .and_then(|session| self.device.do_mmap(session, flags, offset))
346                         {
347                             Ok((guest_addr, size)) => {
348                                 writer.write_response(MmapResp::ok(guest_addr, size))
349                             }
350                             Err(e) => writer.write_err_response(e),
351                         }
352                         .context("while writing response for MMAP command")
353                     },
354                 ),
355             VIRTIO_MEDIA_CMD_MUNMAP => reader
356                 .read_obj()
357                 .context("while reading UNMMAP command")
358                 .and_then(
359                     |MunmapCmd {
360                          driver_addr: guest_addr,
361                      }| {
362                         match self.device.do_munmap(guest_addr) {
363                             Ok(()) => writer.write_response(MunmapResp::ok()),
364                             Err(e) => writer.write_err_response(e),
365                         }
366                         .context("while writing response for MUNMAP command")
367                     },
368                 ),
369             _ => writer
370                 .write_err_response(libc::ENOTTY)
371                 .context("while writing error response for invalid command"),
372         };
373 
374         if let Err(e) = res {
375             error!("error while processing command: {:#}", e);
376             let _ = writer.write_err_response(libc::EINVAL);
377         }
378     }
379 
380     /// Returns the device this runner has been created from.
into_device(self) -> Device381     pub fn into_device(self) -> Device {
382         self.device
383     }
384 }
385