blob: c85ccdfc26ab8eb7bcbd4497efebfbbdde019c4c [file] [log] [blame]
//! Safe wrapper for the VIDIOC_(D)QBUF and VIDIOC_QUERYBUF ioctls.
use bitflags::bitflags;
use nix::errno::Errno;
use nix::sys::time::{TimeVal, TimeValLike};
use std::fmt::Debug;
use std::mem;
use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd;
use thiserror::Error;
use crate::bindings;
use crate::bindings::v4l2_buffer;
use crate::ioctl::is_multi_planar;
use crate::ioctl::QueryBuf;
use crate::ioctl::V4l2Buffer;
use crate::ioctl::V4l2BufferPlanes;
use crate::memory::Memory;
use crate::memory::PlaneHandle;
use crate::QueueType;
bitflags! {
/// Flags corresponding to the `flags` field of `struct v4l2_buffer`.
/// TODO split into two types, one for the user -> kernel and another for
/// the kernel -> user direction to filter invalid flags?
#[derive(Clone, Copy, Debug, Default)]
pub struct BufferFlags: u32 {
const MAPPED = bindings::V4L2_BUF_FLAG_MAPPED;
const QUEUED = bindings::V4L2_BUF_FLAG_QUEUED;
const DONE = bindings::V4L2_BUF_FLAG_DONE;
const ERROR = bindings::V4L2_BUF_FLAG_ERROR;
const LAST = bindings::V4L2_BUF_FLAG_LAST;
const REQUEST_FD = bindings::V4L2_BUF_FLAG_REQUEST_FD;
}
}
#[derive(Debug, Error)]
pub enum QBufError<Q: QueryBuf> {
#[error("error while converting from v4l2_buffer: {0}")]
ConversionError(Q::Error),
#[error("invalid number of planes specified for the buffer: got {0}, expected {1}")]
NumPlanesMismatch(usize, usize),
#[error("data offset specified while using the single-planar API")]
DataOffsetNotSupported,
#[error("ioctl error: {0}")]
IoctlError(Errno),
}
impl<Q: QueryBuf> From<Errno> for QBufError<Q> {
fn from(errno: Errno) -> Self {
Self::IoctlError(errno)
}
}
impl<Q: QueryBuf> From<QBufError<Q>> for Errno {
fn from(err: QBufError<Q>) -> Self {
match err {
QBufError::ConversionError(_) => Errno::EINVAL,
QBufError::NumPlanesMismatch(_, _) => Errno::EINVAL,
QBufError::DataOffsetNotSupported => Errno::EINVAL,
QBufError::IoctlError(e) => e,
}
}
}
/// Implementors can pass buffer data to the `qbuf` ioctl.
pub trait QBuf<Q: QueryBuf> {
/// Fill the buffer information into the single-planar `v4l2_buf`. Fail if
/// the number of planes is different from 1.
fn fill_splane_v4l2_buffer(self, v4l2_buf: &mut v4l2_buffer) -> Result<(), QBufError<Q>>;
/// Fill the buffer information into the multi-planar `v4l2_buf`, using
/// `v4l2_planes` to store the plane data. Fail if the number of planes is
/// not between 1 and `VIDEO_MAX_PLANES` included.
fn fill_mplane_v4l2_buffer(
self,
v4l2_buf: &mut v4l2_buffer,
v4l2_planes: &mut V4l2BufferPlanes,
) -> Result<(), QBufError<Q>>;
}
impl<Q: QueryBuf> QBuf<Q> for V4l2Buffer {
fn fill_splane_v4l2_buffer(self, v4l2_buf: &mut v4l2_buffer) -> Result<(), QBufError<Q>> {
*v4l2_buf = self.buffer;
Ok(())
}
fn fill_mplane_v4l2_buffer(
self,
v4l2_buf: &mut v4l2_buffer,
v4l2_planes: &mut V4l2BufferPlanes,
) -> Result<(), QBufError<Q>> {
*v4l2_buf = self.buffer;
for (dest, src) in v4l2_planes
.iter_mut()
.zip(self.planes.iter())
.take(v4l2_buf.length as usize)
{
*dest = *src;
}
Ok(())
}
}
/// Representation of a single plane of a V4L2 buffer.
pub struct QBufPlane(pub bindings::v4l2_plane);
impl QBufPlane {
// TODO remove as this is not safe - we should always specify a handle.
pub fn new(bytes_used: usize) -> Self {
QBufPlane(bindings::v4l2_plane {
bytesused: bytes_used as u32,
data_offset: 0,
..unsafe { mem::zeroed() }
})
}
pub fn new_from_handle<H: PlaneHandle>(handle: &H, bytes_used: usize) -> Self {
let mut plane = Self::new(bytes_used);
handle.fill_v4l2_plane(&mut plane.0);
plane
}
}
impl Debug for QBufPlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QBufPlane")
.field("bytesused", &self.0.bytesused)
.field("data_offset", &self.0.data_offset)
.finish()
}
}
/// Contains all the information that can be passed to the `qbuf` ioctl.
// TODO Change this to contain a v4l2_buffer, and create constructors/methods
// to change it? Then during qbuf we just need to set m.planes to planes
// (after resizing it to 8) and we are good to use it as-is.
// We could even turn the trait into AsRef<v4l2_buffer> for good measure.
#[derive(Debug)]
pub struct QBuffer<H: PlaneHandle> {
pub flags: BufferFlags,
pub field: u32,
pub sequence: u32,
pub timestamp: TimeVal,
pub planes: Vec<QBufPlane>,
pub request: Option<RawFd>,
pub _h: std::marker::PhantomData<H>,
}
impl<H: PlaneHandle> Default for QBuffer<H> {
fn default() -> Self {
QBuffer {
flags: Default::default(),
field: Default::default(),
sequence: Default::default(),
timestamp: TimeVal::zero(),
planes: Vec::new(),
request: None,
_h: std::marker::PhantomData,
}
}
}
impl<H: PlaneHandle> QBuffer<H> {
pub fn set_timestamp(mut self, sec: i64, usec: i64) -> Self {
self.timestamp = TimeVal::new(sec, usec);
self
}
fn fill_common_v4l2_data(&self, v4l2_buf: &mut v4l2_buffer) {
v4l2_buf.memory = H::Memory::MEMORY_TYPE as u32;
v4l2_buf.flags = self.flags.bits();
v4l2_buf.field = self.field;
v4l2_buf.sequence = self.sequence;
v4l2_buf.timestamp.tv_sec = self.timestamp.tv_sec();
v4l2_buf.timestamp.tv_usec = self.timestamp.tv_usec();
if let Some(request) = &self.request {
v4l2_buf.__bindgen_anon_1.request_fd = *request;
}
}
}
impl<H: PlaneHandle, Q: QueryBuf> QBuf<Q> for QBuffer<H> {
fn fill_splane_v4l2_buffer(self, v4l2_buf: &mut v4l2_buffer) -> Result<(), QBufError<Q>> {
if self.planes.len() != 1 {
return Err(QBufError::NumPlanesMismatch(self.planes.len(), 1));
}
self.fill_common_v4l2_data(v4l2_buf);
let plane = &self.planes[0];
if plane.0.data_offset != 0 {
return Err(QBufError::DataOffsetNotSupported);
}
v4l2_buf.bytesused = plane.0.bytesused;
H::fill_v4l2_splane_buffer(&plane.0, v4l2_buf);
Ok(())
}
fn fill_mplane_v4l2_buffer(
self,
v4l2_buf: &mut v4l2_buffer,
v4l2_planes: &mut V4l2BufferPlanes,
) -> Result<(), QBufError<Q>> {
if self.planes.is_empty() || self.planes.len() > v4l2_planes.len() {
return Err(QBufError::NumPlanesMismatch(
self.planes.len(),
v4l2_planes.len(),
));
}
self.fill_common_v4l2_data(v4l2_buf);
v4l2_buf.length = self.planes.len() as u32;
v4l2_planes
.iter_mut()
.zip(self.planes)
.for_each(|(v4l2_plane, plane)| {
*v4l2_plane = plane.0;
});
Ok(())
}
}
#[doc(hidden)]
mod ioctl {
use crate::bindings::v4l2_buffer;
nix::ioctl_readwrite!(vidioc_querybuf, b'V', 9, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_qbuf, b'V', 15, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_dqbuf, b'V', 17, v4l2_buffer);
nix::ioctl_readwrite!(vidioc_prepare_buf, b'V', 93, v4l2_buffer);
}
/// Safe wrapper around the `VIDIOC_QBUF` ioctl.
///
/// TODO: `qbuf` should be unsafe! The following invariants need to be guaranteed
/// by the caller:
///
/// For MMAP buffers, any mapping must not be accessed by the caller (or any
/// mapping must be unmapped before queueing?). Also if the buffer has been
/// DMABUF-exported, its consumers must likewise not access it.
///
/// For DMABUF buffers, the FD must not be duplicated and accessed anywhere else.
///
/// For USERPTR buffers, things are most tricky. Not only must the data not be
/// accessed by anyone else, the caller also needs to guarantee that the backing
/// memory won't be freed until the corresponding buffer is returned by either
/// `dqbuf` or `streamoff`.
pub fn qbuf<I: QBuf<O>, O: QueryBuf>(
fd: &impl AsRawFd,
queue: QueueType,
index: usize,
buf_data: I,
) -> Result<O, QBufError<O>> {
let mut v4l2_buf = v4l2_buffer {
index: index as u32,
type_: queue as u32,
..unsafe { mem::zeroed() }
};
if is_multi_planar(queue) {
let mut plane_data: V4l2BufferPlanes = Default::default();
buf_data.fill_mplane_v4l2_buffer(&mut v4l2_buf, &mut plane_data)?;
v4l2_buf.m.planes = plane_data.as_mut_ptr();
unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), &mut v4l2_buf) }?;
Ok(O::try_from_v4l2_buffer(v4l2_buf, Some(plane_data))
.map_err(QBufError::ConversionError)?)
} else {
buf_data.fill_splane_v4l2_buffer(&mut v4l2_buf)?;
unsafe { ioctl::vidioc_qbuf(fd.as_raw_fd(), &mut v4l2_buf) }?;
Ok(O::try_from_v4l2_buffer(v4l2_buf, None).map_err(QBufError::ConversionError)?)
}
}
/// Safe wrapper around the `VIDIOC_PREPARE_BUF` ioctl.
pub fn prepare_buf<I: QBuf<O>, O: QueryBuf>(
fd: &impl AsRawFd,
queue: QueueType,
index: usize,
buf_data: I,
) -> Result<O, QBufError<O>> {
let mut v4l2_buf = v4l2_buffer {
index: index as u32,
type_: queue as u32,
..unsafe { mem::zeroed() }
};
if is_multi_planar(queue) {
let mut plane_data: V4l2BufferPlanes = Default::default();
buf_data.fill_mplane_v4l2_buffer(&mut v4l2_buf, &mut plane_data)?;
v4l2_buf.m.planes = plane_data.as_mut_ptr();
unsafe { ioctl::vidioc_prepare_buf(fd.as_raw_fd(), &mut v4l2_buf) }?;
Ok(O::try_from_v4l2_buffer(v4l2_buf, Some(plane_data))
.map_err(QBufError::ConversionError)?)
} else {
buf_data.fill_splane_v4l2_buffer(&mut v4l2_buf)?;
unsafe { ioctl::vidioc_prepare_buf(fd.as_raw_fd(), &mut v4l2_buf) }?;
Ok(O::try_from_v4l2_buffer(v4l2_buf, None).map_err(QBufError::ConversionError)?)
}
}