From patchwork Tue Sep 30 12:26:27 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 24505 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 7CB34C324C for ; Tue, 30 Sep 2025 12:43:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 358256B5FB; Tue, 30 Sep 2025 14:43:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ThpcbrDH"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CC44362C35 for ; Tue, 30 Sep 2025 14:43:31 +0200 (CEST) Received: from ideasonboard.com (unknown [94.31.94.171]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 6686E220; Tue, 30 Sep 2025 14:42:03 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1759236123; bh=oOkmDPZr8uyu26EjbRBgIQZd5obUIF8cP7kLqdyUf48=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ThpcbrDHtYH6pFgMHwlfQ6+Bfu2dSdIFGFcitB8CugAEOCZnYRtJ3Pll/PzzBgwNS tz7t5lyYZUE+HKEo0hNSrLxNnFvTnBndj3URy+fOISKNfC9SRXAMWbeDE4HreWlQS1 fgNO1fmhIurwTYJP1JxqtroO9cu/cyyiltLcqWNQ= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1 06/33] libcamera: Add support for V4L2 requests Date: Tue, 30 Sep 2025 14:26:27 +0200 Message-ID: <20250930122726.1837524-7-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250930122726.1837524-1-stefan.klug@ideasonboard.com> References: <20250930122726.1837524-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The V4L2 requests API provides support to atomically tie controls to a set of buffers. This is especially common for m2m devices. Such a request is represented by a fd that is allocated vi MEDIA_IOC_REQUEST_ALLOC and then passed to various V4L2 function. Implement a V4L2Request class to wrap such an fd and add the corresponding utility functions. Signed-off-by: Stefan Klug --- include/libcamera/internal/media_device.h | 7 ++ include/libcamera/internal/meson.build | 1 + include/libcamera/internal/v4l2_device.h | 5 +- include/libcamera/internal/v4l2_request.h | 49 ++++++++ include/libcamera/internal/v4l2_videodevice.h | 3 +- src/libcamera/media_device.cpp | 47 ++++++++ src/libcamera/meson.build | 1 + src/libcamera/v4l2_device.cpp | 28 ++++- src/libcamera/v4l2_request.cpp | 107 ++++++++++++++++++ src/libcamera/v4l2_videodevice.cpp | 10 +- 10 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 include/libcamera/internal/v4l2_request.h create mode 100644 src/libcamera/v4l2_request.cpp diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h index b3a48b98d64b..74cb9ba1542d 100644 --- a/include/libcamera/internal/media_device.h +++ b/include/libcamera/internal/media_device.h @@ -18,6 +18,7 @@ #include #include "libcamera/internal/media_object.h" +#include "libcamera/internal/v4l2_request.h" namespace libcamera { @@ -57,6 +58,11 @@ public: std::vector locateEntities(unsigned int function); + int allocateRequests(unsigned int count, + std::vector> *requests); + + bool supportsRequests(); + protected: std::string logPrefix() const override; @@ -87,6 +93,7 @@ private: UniqueFD fd_; bool valid_; bool acquired_; + std::optional supportsRequests_; std::map objects_; std::vector entities_; diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 45c299f6a332..e9540a2f734f 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -44,6 +44,7 @@ libcamera_internal_headers = files([ 'sysfs.h', 'v4l2_device.h', 'v4l2_pixelformat.h', + 'v4l2_request.h', 'v4l2_subdevice.h', 'v4l2_videodevice.h', 'vector.h', diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h index 5bc9da96677d..dbbd118abd00 100644 --- a/include/libcamera/internal/v4l2_device.h +++ b/include/libcamera/internal/v4l2_device.h @@ -24,6 +24,7 @@ #include #include "libcamera/internal/formats.h" +#include "libcamera/internal/v4l2_request.h" namespace libcamera { @@ -37,8 +38,8 @@ public: const ControlInfoMap &controls() const { return controls_; } - ControlList getControls(Span ids); - int setControls(ControlList *ctrls); + ControlList getControls(Span ids, const V4L2Request *request = nullptr); + int setControls(ControlList *ctrls, const V4L2Request *request = nullptr); const struct v4l2_query_ext_ctrl *controlInfo(uint32_t id) const; diff --git a/include/libcamera/internal/v4l2_request.h b/include/libcamera/internal/v4l2_request.h new file mode 100644 index 000000000000..bf1bea3261af --- /dev/null +++ b/include/libcamera/internal/v4l2_request.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board + * + * V4L2 requests + */ + +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace libcamera { + +class V4L2Request : protected Loggable +{ +public: + bool isValid() const { return fd_.isValid(); } + int fd() const { return fd_.get(); } + + int reinit(); + int queue(); + + V4L2Request(int fd = -1); + ~V4L2Request() = default; + + Signal requestDone; + +private: + LIBCAMERA_DISABLE_COPY_AND_MOVE(V4L2Request) + + void requestReady(); + std::string logPrefix() const override; + + UniqueFD fd_; + EventNotifier fdNotifier_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/v4l2_videodevice.h b/include/libcamera/internal/v4l2_videodevice.h index 5a7dcfdda118..2d290971a0ee 100644 --- a/include/libcamera/internal/v4l2_videodevice.h +++ b/include/libcamera/internal/v4l2_videodevice.h @@ -33,6 +33,7 @@ #include "libcamera/internal/formats.h" #include "libcamera/internal/v4l2_device.h" #include "libcamera/internal/v4l2_pixelformat.h" +#include "libcamera/internal/v4l2_request.h" namespace libcamera { @@ -217,7 +218,7 @@ public: int importBuffers(unsigned int count); int releaseBuffers(); - int queueBuffer(FrameBuffer *buffer); + int queueBuffer(FrameBuffer *buffer, const V4L2Request *request = nullptr); Signal bufferReady; int streamOn(); diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp index 353f34a81eca..673c53fefdd7 100644 --- a/src/libcamera/media_device.cpp +++ b/src/libcamera/media_device.cpp @@ -20,6 +20,7 @@ #include #include +#include "libcamera/internal/v4l2_request.h" /** * \file media_device.h @@ -851,4 +852,50 @@ std::vector MediaDevice::locateEntities(unsigned int function) return found; } +/** + * \brief Allocate requests + * \param[in] count Number of requests to allocate + * \param[out] requests Vector to store allocated requests + * + * Allocates and stores \a count requests in \a requests. If allocation fails, + * and error is returned and \a requests is cleared. + * + * \return 0 on success or a negative error code otherwise + */ +int MediaDevice::allocateRequests(unsigned int count, + std::vector> *requests) +{ + requests->resize(count); + for (unsigned int i = 0; i < count; i++) { + int requestFd; + int ret = ::ioctl(fd_.get(), MEDIA_IOC_REQUEST_ALLOC, &requestFd); + if (ret < 0) { + requests->clear(); + return -errno; + } + (*requests)[i] = std::make_unique(requestFd); + } + + return 0; +} + +/** + * \brief Check if requests are supported + * + * Checks if the device supports V4L2 requests by trying to allocate a single + * request. The result is cached, so the allocation is only tried once. + * + * \return True if the device supports requests, false otherwise + */ +bool MediaDevice::supportsRequests() +{ + if (supportsRequests_.has_value()) + return supportsRequests_.value(); + + std::vector> requests; + supportsRequests_ = (allocateRequests(1, &requests) == 0); + + return supportsRequests_.value(); +} + } /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 5b9b86f211f1..34e20f557514 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -54,6 +54,7 @@ libcamera_internal_sources = files([ 'sysfs.cpp', 'v4l2_device.cpp', 'v4l2_pixelformat.cpp', + 'v4l2_request.cpp', 'v4l2_subdevice.cpp', 'v4l2_videodevice.cpp', 'vector.cpp', diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp index 8c78b8c424e5..7a669a0303c1 100644 --- a/src/libcamera/v4l2_device.cpp +++ b/src/libcamera/v4l2_device.cpp @@ -162,6 +162,7 @@ void V4L2Device::close() /** * \brief Read controls from the device * \param[in] ids The list of controls to read, specified by their ID + * \param[in] request An optional request * * This function reads the value of all controls contained in \a ids, and * returns their values as a ControlList. @@ -171,10 +172,12 @@ void V4L2Device::close() * during validation of the requested controls, no control is read and this * function returns an empty control list. * + * If \a request is specified the controls tied to that request are read. + * * \return The control values in a ControlList on success, or an empty list on * error */ -ControlList V4L2Device::getControls(Span ids) +ControlList V4L2Device::getControls(Span ids, const V4L2Request *request) { if (ids.empty()) return {}; @@ -242,10 +245,16 @@ ControlList V4L2Device::getControls(Span ids) } struct v4l2_ext_controls v4l2ExtCtrls = {}; - v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL; v4l2ExtCtrls.controls = v4l2Ctrls.data(); v4l2ExtCtrls.count = v4l2Ctrls.size(); + if (request) { + v4l2ExtCtrls.which = V4L2_CTRL_WHICH_REQUEST_VAL; + v4l2ExtCtrls.request_fd = request->fd(); + } else { + v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL; + } + int ret = ioctl(VIDIOC_G_EXT_CTRLS, &v4l2ExtCtrls); if (ret) { unsigned int errorIdx = v4l2ExtCtrls.error_idx; @@ -273,6 +282,7 @@ ControlList V4L2Device::getControls(Span ids) /** * \brief Write controls to the device * \param[in] ctrls The list of controls to write + * \param[in] request And optional request * * This function writes the value of all controls contained in \a ctrls, and * stores the values actually applied to the device in the corresponding @@ -288,11 +298,15 @@ ControlList V4L2Device::getControls(Span ids) * are written and their values are updated in \a ctrls, while all other * controls are not written and their values are not changed. * + * If \a request is set, the controls will be applied to that request. If the + * device doesn't support requests, -EACCESS will be returned. If \a request is + * invalid, -EINVAL will be returned. + * * \return 0 on success or an error code otherwise * \retval -EINVAL One of the control is not supported or not accessible * \retval i The index of the control that failed */ -int V4L2Device::setControls(ControlList *ctrls) +int V4L2Device::setControls(ControlList *ctrls, const V4L2Request *request) { if (ctrls->empty()) return 0; @@ -377,10 +391,16 @@ int V4L2Device::setControls(ControlList *ctrls) } struct v4l2_ext_controls v4l2ExtCtrls = {}; - v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL; v4l2ExtCtrls.controls = v4l2Ctrls.data(); v4l2ExtCtrls.count = v4l2Ctrls.size(); + if (request) { + v4l2ExtCtrls.which = V4L2_CTRL_WHICH_REQUEST_VAL; + v4l2ExtCtrls.request_fd = request->fd(); + } else { + v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL; + } + int ret = ioctl(VIDIOC_S_EXT_CTRLS, &v4l2ExtCtrls); if (ret) { unsigned int errorIdx = v4l2ExtCtrls.error_idx; diff --git a/src/libcamera/v4l2_request.cpp b/src/libcamera/v4l2_request.cpp new file mode 100644 index 000000000000..47a397ac47be --- /dev/null +++ b/src/libcamera/v4l2_request.cpp @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board + * + * V4L2 Request API + */ + +#include "libcamera/internal/v4l2_request.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +/** + * \file v4l2_request.h + * \brief V4L2 Request + */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(V4L2) + +/** + * \class V4L2Request + * \brief V4L2Request object and API + * + * The V4L2Request class wraps a V4L2 request fd and provides some convenience + * functions to handle request. + * + * It is usually constructed by calling \a MediaDevice::allocateRequests(). + * + * A request can then be passed to the V4L2Device::setControls(), + * V4L2Device::getControls() and V4L2VideoDevice::queueBuffer(). + */ + +/** + * \brief Construct a V4L2Request + * \param[in] fd The request fd + */ +V4L2Request::V4L2Request(int fd) + : fd_(fd), fdNotifier_(fd, EventNotifier::Exception) +{ + if (!fd_.isValid()) + return; + + fdNotifier_.activated.connect(this, &V4L2Request::requestReady); + fdNotifier_.setEnabled(false); +} + +/** + * \brief Reinit the request + * + * Calls MEDIA_REQUEST_IOC_REINIT om the request fd. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2Request::reinit() +{ + fdNotifier_.setEnabled(false); + + if (::ioctl(fd_.get(), MEDIA_REQUEST_IOC_REINIT) < 0) + return -errno; + + return 0; +} + +/** + * \brief Reinit the request + * + * Calls MEDIA_REQUEST_IOC_QUEUE om the request fd. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2Request::queue() +{ + if (::ioctl(fd_.get(), MEDIA_REQUEST_IOC_QUEUE) < 0) + return -errno; + + fdNotifier_.setEnabled(true); + + return 0; +} + +std::string V4L2Request::logPrefix() const +{ + return "Request [" + std::to_string(fd()) + "]"; +} + +/** + * \brief Slot to handle request done events + * + * When this slot is called, the request is done and the requestDone will be + * emitted. + */ +void V4L2Request::requestReady() +{ + requestDone.emit(this); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp index bb57c1b76a5b..8ce739f4bc65 100644 --- a/src/libcamera/v4l2_videodevice.cpp +++ b/src/libcamera/v4l2_videodevice.cpp @@ -30,6 +30,7 @@ #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/media_object.h" +#include "libcamera/internal/v4l2_request.h" /** * \file v4l2_videodevice.h @@ -1629,6 +1630,7 @@ int V4L2VideoDevice::releaseBuffers() /** * \brief Queue a buffer to the video device * \param[in] buffer The buffer to be queued + * \param[in] request An optional request * * For capture video devices the \a buffer will be filled with data by the * device. For output video devices the \a buffer shall contain valid data and @@ -1641,9 +1643,11 @@ int V4L2VideoDevice::releaseBuffers() * Note that queueBuffer() will fail if the device is in the process of being * stopped from a streaming state through streamOff(). * + * If \a request is specified, the buffer will be tied to that request. + * * \return 0 on success or a negative error code otherwise */ -int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer) +int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer, const V4L2Request *request) { struct v4l2_plane v4l2Planes[VIDEO_MAX_PLANES] = {}; struct v4l2_buffer buf = {}; @@ -1674,6 +1678,10 @@ int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer) buf.type = bufferType_; buf.memory = memoryType_; buf.field = V4L2_FIELD_NONE; + if (request) { + buf.flags = V4L2_BUF_FLAG_REQUEST_FD; + buf.request_fd = request->fd(); + } bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type); Span planes = buffer->planes();