// Copyright (c) 2023 Huawei Technologies Co.,Ltd. All rights reserved. // // StratoVirt is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. // You may obtain a copy of Mulan PSL v2 at: // http://license.coscl.org.cn/MulanPSL2 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. use std::fs::{File, OpenOptions}; use std::io::ErrorKind; use std::os::unix::prelude::{AsRawFd, OpenOptionsExt, RawFd}; use std::sync::{Arc, Mutex}; use anyhow::{bail, Context, Result}; use log::{debug, error}; use v4l2_sys_mit::{ v4l2_buffer, v4l2_capability, v4l2_fmtdesc, v4l2_format, v4l2_frmivalenum, v4l2_frmsizeenum, v4l2_requestbuffers, v4l2_streamparm, }; use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref}; use vmm_sys_util::{ioctl_ioc_nr, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr}; use crate::aio::Iovec; const VIDEO: u32 = 86; ioctl_ior_nr!(VIDIOC_QUERYCAP, VIDEO, 0, v4l2_capability); ioctl_iowr_nr!(VIDIOC_ENUM_FMT, VIDEO, 2, v4l2_fmtdesc); ioctl_iowr_nr!(VIDIOC_G_FMT, VIDEO, 4, v4l2_format); ioctl_iowr_nr!(VIDIOC_S_FMT, VIDEO, 5, v4l2_format); ioctl_iowr_nr!(VIDIOC_REQBUFS, VIDEO, 8, v4l2_requestbuffers); ioctl_iowr_nr!(VIDIOC_QUERYBUF, VIDEO, 9, v4l2_buffer); ioctl_iowr_nr!(VIDIOC_QBUF, VIDEO, 15, v4l2_buffer); ioctl_iowr_nr!(VIDIOC_DQBUF, VIDEO, 17, v4l2_buffer); ioctl_iow_nr!(VIDIOC_STREAMON, VIDEO, 18, std::os::raw::c_int); ioctl_iow_nr!(VIDIOC_STREAMOFF, VIDEO, 19, std::os::raw::c_int); ioctl_iowr_nr!(VIDIOC_S_PARM, VIDEO, 22, v4l2_streamparm); ioctl_iowr_nr!(VIDIOC_ENUM_FRAMESIZES, VIDEO, 74, v4l2_frmsizeenum); ioctl_iowr_nr!(VIDIOC_ENUM_FRAMEINTERVALS, VIDEO, 75, v4l2_frmivalenum); pub struct V4l2Backend { /// V4L2 backend path, such as /dev/video0. path: String, /// V4L2 backend device fd. fd: File, /// V4L2 image buffer. pub buffer: Arc<Mutex<Vec<Iovec>>>, } impl Drop for V4l2Backend { fn drop(&mut self) { debug!("Drop v4l2 backend fd {}", self.as_raw_fd()); if let Err(e) = self.release_buffers() { error!("Failed to release buffer for {}, {:?}", self.path, e); } } } impl V4l2Backend { pub fn new(path: String, buf_cnt: usize) -> Result<Self> { let fd = OpenOptions::new() .read(true) .write(true) .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) .open(&path) .with_context(|| format!("Failed to open v4l2 backend {}.", &path))?; Ok(Self { path, fd, buffer: Arc::new(Mutex::new(vec![Iovec::new(0, 0); buf_cnt])), }) } pub fn query_cap(&self) -> Result<v4l2_capability> { let mut cap = new_init::<v4l2_capability>(); // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_mut_ref(self, VIDIOC_QUERYCAP(), &mut cap) }; if ret < 0 { bail!( "Failed to query cap, error {:?}", std::io::Error::last_os_error() ); } Ok(cap) } pub fn set_format(&self, fmt: &v4l2_format) -> Result<()> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_S_FMT(), fmt) }; if ret < 0 { bail!( "Failed to set format, error {:?}", std::io::Error::last_os_error() ); } Ok(()) } pub fn request_buffers(&self, bufs: &mut v4l2_requestbuffers) -> Result<()> { // Ensure that there are no residual buffers. self.release_buffers()?; let mut locked_buf = self.buffer.lock().unwrap(); let cnt = locked_buf.len() as u32; // Ensure the count is equal to the length of buffer. bufs.count = cnt; // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_REQBUFS(), bufs) }; if ret < 0 { bail!( "Failed to request buffers, error {:?}", std::io::Error::last_os_error() ); } for i in 0..cnt { let mut buf = new_init::<v4l2_buffer>(); buf.index = i; buf.type_ = bufs.type_; buf.memory = bufs.memory; // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_QUERYBUF(), &buf) }; if ret < 0 { bail!( "Failed to query buffer {}, error {:?}", i, std::io::Error::last_os_error() ); } // SAFETY: // 1. self.fd is created in function new(). // 2. buf can be guaranteed not be null. let ret = unsafe { libc::mmap( std::ptr::null_mut() as *mut libc::c_void, buf.length as libc::size_t, libc::PROT_WRITE | libc::PROT_READ, libc::MAP_SHARED, self.as_raw_fd(), buf.m.offset.into(), ) }; if ret == libc::MAP_FAILED { bail!( "Failed to mmap for buffer {}, error {:?}", i, std::io::Error::last_os_error() ); } locked_buf[i as usize].iov_base = ret as u64; locked_buf[i as usize].iov_len = buf.length as u64; // Queue buffer to get data. self.queue_buffer(&buf)?; } Ok(()) } pub fn release_buffers(&self) -> Result<()> { let mut locked_buf = self.buffer.lock().unwrap(); for buf in locked_buf.iter_mut() { if buf.is_none() { continue; } // SAFETY: buf can be guaranteed not be null. let ret = unsafe { libc::munmap( buf.iov_base as *mut libc::c_void, buf.iov_len as libc::size_t, ) }; if ret < 0 { bail!( "Failed to release buffers, error {:?}", std::io::Error::last_os_error() ); } buf.iov_base = 0; buf.iov_len = 0; } Ok(()) } pub fn stream_on(&self, vtype: std::os::raw::c_int) -> Result<()> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_STREAMON(), &vtype) }; if ret < 0 { bail!( "Failed to stream on, error {:?}", std::io::Error::last_os_error() ); } Ok(()) } pub fn stream_off(&self, vtype: std::os::raw::c_int) -> Result<()> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_STREAMOFF(), &vtype) }; if ret < 0 { bail!( "Failed to stream off, error {:?}", std::io::Error::last_os_error() ); } Ok(()) } pub fn queue_buffer(&self, buf: &v4l2_buffer) -> Result<()> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_QBUF(), buf) }; if ret < 0 { bail!( "Failed to queue buffer, error {:?}", std::io::Error::last_os_error() ); } Ok(()) } pub fn dequeue_buffer(&self, buf: &v4l2_buffer) -> Result<bool> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_DQBUF(), buf) }; if ret < 0 { if nix::errno::errno() == libc::EAGAIN { return Ok(false); } bail!( "Failed to dequeue buffer, error {:?}", std::io::Error::last_os_error() ); } Ok(true) } pub fn enum_format(&self, desc: &mut v4l2_fmtdesc) -> Result<bool> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_mut_ref(self, VIDIOC_ENUM_FMT(), desc) }; if ret < 0 { let err = std::io::Error::last_os_error(); if err.kind() == ErrorKind::InvalidInput { return Ok(true); } bail!("Failed to enumerate format, error {:?}", err); } Ok(false) } pub fn enum_frame_size(&self, frmsize: &mut v4l2_frmsizeenum) -> Result<bool> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_mut_ref(self, VIDIOC_ENUM_FRAMESIZES(), frmsize) }; if ret < 0 { let err = std::io::Error::last_os_error(); if err.kind() == ErrorKind::InvalidInput { return Ok(true); } bail!("Failed to enumerate frame size, error {:?}", err); } Ok(false) } pub fn enum_frame_interval(&self, frame_val: &mut v4l2_frmivalenum) -> Result<bool> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_mut_ref(self, VIDIOC_ENUM_FRAMEINTERVALS(), frame_val) }; if ret < 0 { let err = std::io::Error::last_os_error(); if err.kind() == ErrorKind::InvalidInput { return Ok(true); } bail!("Failed to enumerate frame interval, error {:?}", err); } Ok(false) } pub fn set_stream_parameter(&self, parm: &v4l2_streamparm) -> Result<()> { // SAFETY: self.fd is created in function new(). let ret = unsafe { ioctl_with_ref(self, VIDIOC_S_PARM(), parm) }; if ret < 0 { bail!( "Failed to set stream parameter, error {:?}", std::io::Error::last_os_error() ); } Ok(()) } } impl AsRawFd for V4l2Backend { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } pub fn new_init<T>() -> T { let mut s = ::std::mem::MaybeUninit::<T>::uninit(); // SAFETY: s can be guaranteed not be null. unsafe { ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); s.assume_init() } }