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