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