@@ -96,62 +96,63 @@ public:
unsigned int pad;
unsigned int stream;
};
struct Route {
Route()
: flags(0)
{
}
Route(const Stream &snk, const Stream &src, uint32_t f)
: sink(snk), source(src), flags(f)
{
}
Stream sink;
Stream source;
uint32_t flags;
};
using Routing = std::vector<Route>;
explicit V4L2Subdevice(const MediaEntity *entity);
~V4L2Subdevice();
int open();
const MediaEntity *entity() const { return entity_; }
int getSelection(const Stream &stream, unsigned int target,
- Rectangle *rect);
- int getSelection(unsigned int pad, unsigned int target, Rectangle *rect)
+ std::optional<Rectangle> *rect);
+ int getSelection(unsigned int pad, unsigned int target,
+ std::optional<Rectangle> *rect)
{
return getSelection({ pad, 0 }, target, rect);
}
int setSelection(const Stream &stream, unsigned int target,
Rectangle *rect);
int setSelection(unsigned int pad, unsigned int target, Rectangle *rect)
{
return setSelection({ pad, 0 }, target, rect);
}
Formats formats(const Stream &stream);
Formats formats(unsigned int pad)
{
return formats({ pad, 0 });
}
int getFormat(const Stream &stream, V4L2SubdeviceFormat *format,
Whence whence = ActiveFormat);
int getFormat(unsigned int pad, V4L2SubdeviceFormat *format,
Whence whence = ActiveFormat)
{
return getFormat({ pad, 0 }, format, whence);
}
int setFormat(const Stream &stream, V4L2SubdeviceFormat *format,
Whence whence = ActiveFormat);
int setFormat(unsigned int pad, V4L2SubdeviceFormat *format,
Whence whence = ActiveFormat)
{
return setFormat({ pad, 0 }, format, whence);
}
@@ -1,44 +1,45 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* A V4L2-backed camera sensor
*/
#include <algorithm>
#include <cmath>
#include <float.h>
#include <iomanip>
#include <limits.h>
#include <map>
#include <memory>
+#include <optional>
#include <string.h>
#include <string>
#include <vector>
#include <libcamera/base/class.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/geometry.h>
#include <libcamera/orientation.h>
#include <libcamera/property_ids.h>
#include <libcamera/transform.h>
#include <libcamera/ipa/core_ipa_interface.h>
#include "libcamera/internal/bayer_format.h"
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/camera_sensor_properties.h"
#include "libcamera/internal/formats.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/sysfs.h"
#include "libcamera/internal/v4l2_subdevice.h"
namespace libcamera {
class BayerFormat;
@@ -363,103 +364,117 @@ int CameraSensorLegacy::validateSensorDriver()
}
/* Verify if sensor supports horizontal/vertical flips. */
auto queryFlip = [&](uint32_t id, const char *name) -> std::pair<bool, bool> {
const struct v4l2_query_ext_ctrl *flipInfo = subdev_->controlInfo(id);
if (!flipInfo || flipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) {
LOG(CameraSensor, Debug)
<< "Camera sensor does not support " << name << " flip";
return { false, false };
}
return { true, flipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT };
};
auto hflip = queryFlip(V4L2_CID_HFLIP, "horizontal");
auto vflip = queryFlip(V4L2_CID_VFLIP, "vertical");
if (hflip.first && vflip.first && (hflip.second != vflip.second)) {
LOG(CameraSensor, Error)
<< "Flips with differing alter bayer order properties"
<< " are unsupported. Disabling flipping.";
} else {
supportHFlips_ = hflip.first;
supportVFlips_ = vflip.first;
flipsAlterBayerOrder_ = hflip.second || vflip.second;
}
/*
* Make sure the required selection targets are supported.
*
- * Failures in reading any of the targets are not deemed to be fatal,
+ * Unavailable selection targets are not deemed to be fatal,
* but some properties and features, like constructing a
* IPACameraSensorInfo for the IPA module, won't be supported.
*
* \todo Make support for selection targets mandatory as soon as all
* test platforms have been updated.
*/
- int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &pixelArrayArea_);
- if (ret) {
+ std::optional<Rectangle> rect;
+ int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &rect);
+ if (ret)
+ return ret;
+ if (!rect) {
/*
* Default the pixel array size to the largest size supported
* by the sensor. The sizes_ vector is sorted in ascending
* order, the largest size is thus the last element.
*/
pixelArrayArea_ = Rectangle(sizes_.back());
LOG(CameraSensor, Warning)
<< "The PixelArrayArea property has been defaulted to "
<< pixelArrayArea_;
err = -EINVAL;
+ } else {
+ pixelArrayArea_ = *rect;
}
- ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &readableArea_);
- if (ret) {
+ ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &rect);
+ if (ret)
+ return ret;
+ if (!rect) {
readableArea_ = pixelArrayArea_;
LOG(CameraSensor, Warning)
<< "The PixelArrayReadableArea property has been defaulted to "
<< readableArea_;
err = -EINVAL;
+ } else {
+ readableArea_ = *rect;
}
- ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &activeArea_);
- if (ret) {
+ ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &rect);
+ if (ret)
+ return ret;
+ if (!rect) {
activeArea_ = readableArea_;
LOG(CameraSensor, Warning)
<< "The PixelArrayActiveAreas property has been defaulted to "
<< activeArea_;
err = -EINVAL;
+ } else {
+ activeArea_ = *rect;
}
- Rectangle rect;
ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &rect);
- if (ret) {
+ if (ret)
+ return ret;
+ if (!rect) {
LOG(CameraSensor, Warning)
<< "Failed to retrieve the sensor crop rectangle";
err = -EINVAL;
}
if (err) {
LOG(CameraSensor, Warning)
<< "The sensor kernel driver needs to be fixed";
LOG(CameraSensor, Warning)
<< "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information";
}
if (!bayerFormat_)
return 0;
/*
* For raw sensors, make sure the sensor driver supports the controls
* required by the CameraSensor class.
*/
static constexpr uint32_t mandatoryControls[] = {
V4L2_CID_ANALOGUE_GAIN,
V4L2_CID_EXPOSURE,
V4L2_CID_HBLANK,
V4L2_CID_PIXEL_RATE,
V4L2_CID_VBLANK,
};
err = 0;
for (uint32_t ctrl : mandatoryControls) {
if (!controls.count(ctrl)) {
@@ -871,65 +886,70 @@ int CameraSensorLegacy::applyConfiguration(const SensorConfiguration &config,
/* \todo Handle AnalogCrop. Most sensors do not support set_selection */
/* \todo Handle scaling in the digital domain. */
return 0;
}
V4L2Subdevice::Stream CameraSensorLegacy::imageStream() const
{
return { pad_, 0 };
}
int CameraSensorLegacy::sensorInfo(IPACameraSensorInfo *info) const
{
if (!bayerFormat_)
return -EINVAL;
info->model = model();
info->pixelArrayArea = pixelArrayArea_;
/*
* The active area is a static property, while the crop rectangle needs
* to be re-read as it depends on the sensor configuration.
*/
info->activeArea = activeArea_;
/*
* \todo Support for retrieving the crop rectangle is scheduled to
* become mandatory. For the time being use the default value if it has
* been initialized at sensor driver validation time.
*/
- int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &info->analogCrop);
- if (ret) {
+ std::optional<Rectangle> crop;
+ int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &crop);
+ if (ret)
+ return ret;
+ if (!crop) {
info->analogCrop = activeArea_;
LOG(CameraSensor, Warning)
<< "The analogue crop rectangle has been defaulted to the active area size";
+ } else {
+ info->analogCrop = *crop;
}
/* The bit depth and image size depend on the currently applied format. */
V4L2SubdeviceFormat format{};
ret = subdev_->getFormat(pad_, &format);
if (ret)
return ret;
info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;
info->outputSize = format.size;
std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);
info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB;
/*
* Retrieve the pixel rate, line length and minimum/maximum frame
* duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,
* V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.
*/
static constexpr uint32_t cids[] = {
V4L2_CID_PIXEL_RATE,
V4L2_CID_HBLANK,
V4L2_CID_VBLANK,
};
ControlList ctrls = subdev_->getControls(cids);
if (ctrls.empty()) {
LOG(CameraSensor, Error)
<< "Failed to retrieve camera info controls";
return -EINVAL;
}
@@ -397,92 +397,93 @@ std::optional<int> CameraSensorRaw::init()
/* Drop non-raw formats (in case we have a hybrid sensor). */
const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);
if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)
continue;
mbusCodes_.push_back(code);
std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),
[](const SizeRange &range) { return range.max; });
}
if (mbusCodes_.empty()) {
LOG(CameraSensor, Debug) << "No raw image formats found";
return { 0 };
}
std::sort(mbusCodes_.begin(), mbusCodes_.end());
std::sort(sizes_.begin(), sizes_.end());
/*
* Remove duplicate sizes. There are no duplicate media bus codes as
* they are the keys in the formats map.
*/
auto last = std::unique(sizes_.begin(), sizes_.end());
sizes_.erase(last, sizes_.end());
/*
* 3. Query selection rectangles. Retrieve properties, and verify that
* all the expected selection rectangles are supported.
*/
- Rectangle rect;
+ std::optional<Rectangle> rect;
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,
&rect);
- if (ret) {
+ if (ret || !rect) {
LOG(CameraSensor, Error) << "No pixel array crop bounds";
- return { ret };
+ return { ret ? ret : -EINVAL };
}
/*
* \todo Implement querying the physical sensor size based on pad 1,
* stream 0. See
* https://lore.kernel.org/linux-media/20260409201501.975242-23-sakari.ailus@linux.intel.com/
*/
LOG(CameraSensor, Warning) << "PixelArrayArea might be incorrect";
pixelArrayArea_ = Rectangle(0, 0,
- rect.x + rect.width,
- rect.y + rect.height);
+ rect->x + rect->width,
+ rect->y + rect->height);
- readableArea_ = rect;
+ readableArea_ = *rect;
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,
- &activeArea_);
- if (ret) {
+ &rect);
+ if (ret || !rect) {
LOG(CameraSensor, Error) << "No pixel array crop default";
- return { ret };
+ return { ret ? ret : -EINVAL };
}
+ activeArea_ = *rect;
ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,
&rect);
- if (ret) {
+ if (ret || !rect) {
LOG(CameraSensor, Error) << "No pixel array crop rectangle";
- return { ret };
+ return { ret ? ret : -EINVAL };
}
/*
* 4. Verify that all required controls are present.
*/
const ControlIdMap &controls = subdev_->controls().idmap();
static constexpr uint32_t mandatoryControls[] = {
V4L2_CID_ANALOGUE_GAIN,
V4L2_CID_CAMERA_ORIENTATION,
V4L2_CID_EXPOSURE,
V4L2_CID_HBLANK,
V4L2_CID_PIXEL_RATE,
V4L2_CID_VBLANK,
};
ret = 0;
for (uint32_t ctrl : mandatoryControls) {
if (!controls.count(ctrl)) {
LOG(CameraSensor, Error)
<< "Mandatory V4L2 control " << utils::hex(ctrl)
<< " not available";
ret = -EINVAL;
}
}
if (ret) {
LOG(CameraSensor, Error)
@@ -987,64 +988,70 @@ int CameraSensorRaw::setEmbeddedDataEnabled(bool enable)
* source stream, as there would be something seriously wrong
* otherwise.
*/
bool enabled = false;
for (const V4L2Subdevice::Route &route : routing) {
if (route.source != streams_.edata->source)
continue;
enabled = route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE;
break;
}
if (enabled != enable)
return enabled ? -EISCONN : -ENOSTR;
return 0;
}
int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const
{
info->model = model();
info->pixelArrayArea = pixelArrayArea_;
/*
* The active area is a static property, while the crop rectangle needs
* to be re-read as it depends on the sensor configuration.
*/
info->activeArea = activeArea_;
+ std::optional<Rectangle> crop;
int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,
- &info->analogCrop);
+ &crop);
if (ret)
return ret;
+ if (!crop) {
+ LOG(CameraSensor, Error) << "No pixel array crop rectangle";
+ return -EINVAL;
+ }
+ info->analogCrop = *crop;
/* The bit depth and image size depend on the currently applied format. */
V4L2SubdeviceFormat format{};
ret = subdev_->getFormat(streams_.image.source, &format);
if (ret)
return ret;
info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;
info->outputSize = format.size;
std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);
info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB;
/*
* Retrieve the pixel rate, line length and minimum/maximum frame
* duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,
* V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.
*/
static constexpr uint32_t cids[] = {
V4L2_CID_PIXEL_RATE,
V4L2_CID_HBLANK,
V4L2_CID_VBLANK,
};
ControlList ctrls = subdev_->getControls(cids);
if (ctrls.empty()) {
LOG(CameraSensor, Error)
<< "Failed to retrieve camera info controls";
return -EINVAL;
}
@@ -1082,163 +1082,181 @@ std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route)
/**
* \typedef V4L2Subdevice::Routing
* \brief V4L2 subdevice routing table
*
* This class stores a subdevice routing table as a vector of routes.
*/
/**
* \brief Insert a text representation of a V4L2Subdevice::Routing into an
* output stream
* \param[in] out The output stream
* \param[in] routing The V4L2Subdevice::Routing
* \return The output stream \a out
*/
std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing)
{
for (const auto &[i, route] : utils::enumerate(routing)) {
out << "[" << i << "] " << route;
if (i != routing.size() - 1)
out << ", ";
}
return out;
}
/**
* \brief Create a V4L2 subdevice from a MediaEntity using its device node
* path
*/
V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity)
- : V4L2Device(entity->deviceNode()), entity_(entity)
+ : V4L2Device(entity->deviceNode()), entity_(entity), caps_{}
{
}
V4L2Subdevice::~V4L2Subdevice()
{
close();
}
/**
* \brief Open a V4L2 subdevice
* \return 0 on success or a negative error code otherwise
*/
int V4L2Subdevice::open()
{
int ret = V4L2Device::open(O_RDWR);
if (ret)
return ret;
/*
* Try to query the subdev capabilities. The VIDIOC_SUBDEV_QUERYCAP API
* was introduced in kernel v5.8, ENOTTY errors must be ignored to
* support older kernels.
*/
caps_ = {};
ret = ioctl(VIDIOC_SUBDEV_QUERYCAP, &caps_);
if (ret < 0 && errno != ENOTTY) {
ret = -errno;
LOG(V4L2, Error)
<< "Unable to query capabilities: " << strerror(-ret);
return ret;
}
/* If the subdev supports streams, enable the streams API. */
if (caps_.hasStreams()) {
struct v4l2_subdev_client_capability clientCaps{};
clientCaps.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS;
ret = ioctl(VIDIOC_SUBDEV_S_CLIENT_CAP, &clientCaps);
if (ret < 0) {
ret = -errno;
LOG(V4L2, Error)
<< "Unable to set client capabilities: "
<< strerror(-ret);
return ret;
}
}
return 0;
}
/**
* \fn V4L2Subdevice::entity()
* \brief Retrieve the media entity associated with the subdevice
* \return The subdevice's associated media entity.
*/
/**
* \brief Get selection rectangle \a rect for \a target
* \param[in] stream The stream the rectangle is retrieved from
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
- * \param[out] rect The retrieved selection rectangle
+ * \param[out] rect The retrieved selection rectangle, if available
+ *
+ * If the ioctl returns EINVAL or ENOTTY, no rectangle is reported and \a rect
+ * is empty. EINVAL can indicate an unsupported target or unavailable stream
+ * state; an empty result does not distinguish these cases.
+ *
+ * Invalid pad indices and nonzero streams on devices without streams support
+ * are rejected with -EINVAL before issuing the ioctl. All other ioctl errors
+ * are returned unchanged. The output \a rect is cleared on entry.
*
* \todo Define a V4L2SelectionTarget enum for the selection target
*
- * \return 0 on success or a negative error code otherwise
+ * \return 0 if the rectangle was retrieved or is unavailable, or a negative
+ * error code otherwise
*/
int V4L2Subdevice::getSelection(const Stream &stream, unsigned int target,
- Rectangle *rect)
+ std::optional<Rectangle> *rect)
{
+ rect->reset();
+
+ if (stream.pad >= entity_->pads().size() ||
+ (stream.stream && !caps_.hasStreams())) {
+ LOG(V4L2, Error) << "Invalid selection stream " << stream;
+ return -EINVAL;
+ }
+
struct v4l2_subdev_selection sel = {};
sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
sel.pad = stream.pad;
sel.stream = stream.stream;
sel.target = target;
sel.flags = 0;
int ret = ioctl(VIDIOC_SUBDEV_G_SELECTION, &sel);
if (ret < 0) {
+ if (ret == -EINVAL || ret == -ENOTTY)
+ return 0;
+
LOG(V4L2, Error)
<< "Unable to get rectangle " << target << " on pad "
<< stream << ": " << strerror(-ret);
return ret;
}
- rect->x = sel.r.left;
- rect->y = sel.r.top;
- rect->width = sel.r.width;
- rect->height = sel.r.height;
+ *rect = Rectangle(sel.r.left, sel.r.top, sel.r.width, sel.r.height);
return 0;
}
/**
* \fn V4L2Subdevice::getSelection(unsigned int pad, unsigned int target,
- * Rectangle *rect)
+ * std::optional<Rectangle> *rect)
* \brief Get selection rectangle \a rect for \a target
* \param[in] pad The 0-indexed pad number the rectangle is retrieved from
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
- * \param[out] rect The retrieved selection rectangle
+ * \param[out] rect The retrieved selection rectangle, if available
*
- * \return 0 on success or a negative error code otherwise
+ * \return 0 if the rectangle was retrieved or is unavailable, or a negative
+ * error code otherwise
*/
/**
* \brief Set selection rectangle \a rect for \a target
* \param[in] stream The stream the rectangle is to be applied to
* \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags
* \param[inout] rect The selection rectangle to be applied
*
* \todo Define a V4L2SelectionTarget enum for the selection target
*
* \return 0 on success or a negative error code otherwise
*/
int V4L2Subdevice::setSelection(const Stream &stream, unsigned int target,
Rectangle *rect)
{
struct v4l2_subdev_selection sel = {};
sel.which = V4L2_SUBDEV_FORMAT_ACTIVE;
sel.pad = stream.pad;
sel.stream = stream.stream;
sel.target = target;
sel.flags = 0;
sel.r.left = rect->x;
sel.r.top = rect->y;
sel.r.width = rect->width;
sel.r.height = rect->height;
int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel);
if (ret < 0) {