/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* v4l2_videodevice.cpp - V4L2 Video Device
*/
#include "libcamera/internal/v4l2_videodevice.h"
#include <fcntl.h>
#include <iomanip>
#include <sstream>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
#include <linux/version.h>
#include <libcamera/event_notifier.h>
#include <libcamera/file_descriptor.h>
#include "libcamera/internal/log.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_object.h"
#include "libcamera/internal/utils.h"
/**
* \file v4l2_videodevice.h
* \brief V4L2 Video Device
*/
namespace libcamera {
LOG_DECLARE_CATEGORY(V4L2)
/**
* \struct V4L2Capability
* \brief struct v4l2_capability object wrapper and helpers
*
* The V4L2Capability structure manages the information returned by the
* VIDIOC_QUERYCAP ioctl.
*/
/**
* \fn V4L2Capability::driver()
* \brief Retrieve the driver module name
* \return The string containing the name of the driver module
*/
/**
* \fn V4L2Capability::card()
* \brief Retrieve the video device card name
* \return The string containing the video device name
*/
/**
* \fn V4L2Capability::bus_info()
* \brief Retrieve the location of the video device in the system
* \return The string containing the video device location
*/
/**
* \fn V4L2Capability::device_caps()
* \brief Retrieve the capabilities of the video device
* \return The video device specific capabilities if V4L2_CAP_DEVICE_CAPS is
* set or driver capabilities otherwise
*/
/**
* \fn V4L2Capability::isMultiplanar()
* \brief Identify if the video device implements the V4L2 multiplanar APIs
* \return True if the video device supports multiplanar APIs
*/
/**
* \fn V4L2Capability::isCapture()
* \brief Identify if the video device captures data
* \return True if the video device can capture data
*/
/**
* \fn V4L2Capability::isOutput()
* \brief Identify if the video device outputs data
* \return True if the video device can output data
*/
/**
* \fn V4L2Capability::isVideo()
* \brief Identify if the video device captures or outputs images
* \return True if the video device can capture or output images
*/
/**
* \fn V4L2Capability::isM2M()
* \brief Identify if the device is a Memory-to-Memory device
* \return True if the device can capture and output images using the M2M API
*/
/**
* \fn V4L2Capability::isMeta()
* \brief Identify if the video device captures or outputs image meta-data
* \return True if the video device can capture or output image meta-data
*/
/**
* \fn V4L2Capability::isVideoCapture()
* \brief Identify if the video device captures images
* \return True if the video device can capture images
*/
/**
* \fn V4L2Capability::isVideoOutput()
* \brief Identify if the video device outputs images
* \return True if the video device can output images
*/
/**
* \fn V4L2Capability::isMetaCapture()
* \brief Identify if the video device captures image meta-data
* \return True if the video device can capture image meta-data
*/
/**
* \fn V4L2Capability::isMetaOutput()
* \brief Identify if the video device outputs image meta-data
* \return True if the video device can output image meta-data
*/
/**
* \fn V4L2Capability::hasStreaming()
* \brief Determine if the video device can perform Streaming I/O
* \return True if the video device provides Streaming I/O IOCTLs
*/
/**
* \class V4L2BufferCache
* \brief Hot cache of associations between V4L2 buffer indexes and FrameBuffer
*
* When importing buffers, V4L2 performs lazy mapping of dmabuf instances at
* VIDIOC_QBUF (or VIDIOC_PREPARE_BUF) time and keeps the mapping associated
* with the V4L2 buffer, as identified by its index. If the same V4L2 buffer is
* then reused and queued with different dmabufs, the old dmabufs will be
* unmapped and the new ones mapped. To keep this process efficient, it is
* crucial to consistently use the same V4L2 buffer for given dmabufs through
* the whole duration of a capture cycle.
*
* The V4L2BufferCache class keeps a map of previous dmabufs to V4L2 buffer
* index associations to help selecting V4L2 buffers. It tracks, for every
* entry, if the V4L2 buffer is in use, and offers lookup of the best free V4L2
* buffer for a set of dmabufs.
*/
/**
* \brief Create an empty cache with \a numEntries entries
* \param[in] numEntries Number of entries to reserve in the cache
*
* Create a cache with \a numEntries entries all marked as unused. The entries
* will be populated as the cache is used. This is typically used to implement
* buffer import, with buffers added to the cache as they are queued.
*/
V4L2BufferCache::V4L2BufferCache(unsigned int numEntries)
: lastUsedCounter_(1), missCounter_(0)
{
cache_.resize(numEntries);
}
/**
* \brief Create a pre-populated cache
* \param[in] buffers Array of buffers to pre-populated with
*
* Create a cache pre-populated with \a buffers. This is typically used to
* implement buffer export, with all buffers added to the cache when they are
* allocated.
*/
V4L2BufferCache::V4L2BufferCache(const std::vector<std::unique_ptr<FrameBuffer>> &buffers)
: lastUsedCounter_(1), missCounter_(0)
{
for (const std::unique_ptr<FrameBuffer> &buffer : buffers)
cache_.emplace_back(true,
lastUsedCounter_.fetch_add(1, std::memory_order_acq_rel),
buffer->planes());
}
V4L2BufferCache::~V4L2BufferCache()
{
if (missCounter_ > cache_.size())
LOG(V4L2, Debug) << "Cache misses: " << missCounter_;
}
/**
* \brief Find the best V4L2 buffer for a FrameBuffer
* \param[in] buffer The FrameBuffer
*
* Find the best V4L2 buffer index to be used for the FrameBuffer \a buffer
* based on previous mappings of frame buffers to V4L2 buffers. If a free V4L2
* buffer previously used with the same dmabufs as \a buffer is found in the
* cache, return its index. Otherwise return the index of the first free V4L2
* buffer and record its association with the dmabufs of \a buffer.
*
* \return The index of the best V4L2 buffer, or -ENOENT if no free V4L2 buffer
* is available
*/
int V4L2BufferCache::get(const FrameBuffer &buffer)
{
bool hit = false;
int use = -1;
uint64_t oldest = UINT64_MAX;
for (unsigned int index = 0; index < cache_.size(); index++) {
const Entry &entry = cache_[index];
if (!entry.free)
continue;
|