| Message ID | 20251125162851.2301793-4-stefan.klug@ideasonboard.com |
|---|---|
| State | Accepted |
| Headers | show |
| Series |
|
| Related | show |
Quoting Stefan Klug (2025-11-25 16:28:15) > 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 an fd that is allocated via > MEDIA_IOC_REQUEST_ALLOC and then passed to the various V4L2 functions. > > Implement a V4L2Request class to wrap such an fd and add the > corresponding utility functions. > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> > Mostly minor comments below. Only my question on safety of usage of the vector on allocate stops me putting a tag in... if someone confirms that is safe then I could add one ;-) > --- > > Changes in v3: > - Replaced int by UniqueFD > - Fixed lots if typos from review > > Changes in v2: > - Added documentation > --- > include/libcamera/internal/media_device.h | 8 ++ > include/libcamera/internal/meson.build | 1 + > include/libcamera/internal/v4l2_device.h | 5 +- > include/libcamera/internal/v4l2_request.h | 44 ++++++ > include/libcamera/internal/v4l2_videodevice.h | 3 +- > src/libcamera/media_device.cpp | 47 +++++++ > src/libcamera/meson.build | 1 + > src/libcamera/v4l2_device.cpp | 30 +++- > src/libcamera/v4l2_request.cpp | 128 ++++++++++++++++++ > src/libcamera/v4l2_videodevice.cpp | 10 +- > 10 files changed, 268 insertions(+), 9 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..2eb3ad988b09 100644 > --- a/include/libcamera/internal/media_device.h > +++ b/include/libcamera/internal/media_device.h > @@ -8,6 +8,7 @@ > #pragma once > > #include <map> > +#include <optional> > #include <string> > #include <vector> > > @@ -18,6 +19,7 @@ > #include <libcamera/base/unique_fd.h> > > #include "libcamera/internal/media_object.h" > +#include "libcamera/internal/v4l2_request.h" > > namespace libcamera { > > @@ -57,6 +59,11 @@ public: > > std::vector<MediaEntity *> locateEntities(unsigned int function); > > + int allocateRequests(unsigned int count, > + std::vector<std::unique_ptr<V4L2Request>> *requests); > + > + bool supportsRequests(); > + > protected: > std::string logPrefix() const override; > > @@ -87,6 +94,7 @@ private: > UniqueFD fd_; > bool valid_; > bool acquired_; > + std::optional<bool> supportsRequests_; std::optional is intriguing here... I'd default false until known otherwise... > std::map<unsigned int, MediaObject *> objects_; > std::vector<MediaEntity *> 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 <libcamera/controls.h> > > #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<const uint32_t> ids); > - int setControls(ControlList *ctrls); > + ControlList getControls(Span<const uint32_t> 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..376c79ceedba > --- /dev/null > +++ b/include/libcamera/internal/v4l2_request.h > @@ -0,0 +1,44 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2025, Ideas On Board > + * > + * V4L2 requests > + */ > + > +#pragma once > + > +#include <string> > + > +#include <linux/videodev2.h> > + > +#include <libcamera/base/event_notifier.h> > +#include <libcamera/base/log.h> > +#include <libcamera/base/signal.h> > +#include <libcamera/base/unique_fd.h> > + > +namespace libcamera { > + > +class V4L2Request : protected Loggable > +{ > +public: > + bool isValid() const { return fd_.isValid(); } > + int fd() const { return fd_.get(); } > + > + int reinit(); > + int queue(); > + > + V4L2Request(UniqueFD &&fd); > + > + Signal<V4L2Request *> 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 6caafc4dcf08..57db0036db6b 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<FrameBuffer *> bufferReady; > > int streamOn(); > diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp > index 353f34a81eca..2a848ebed998 100644 > --- a/src/libcamera/media_device.cpp > +++ b/src/libcamera/media_device.cpp > @@ -20,6 +20,7 @@ > #include <linux/media.h> > > #include <libcamera/base/log.h> > +#include "libcamera/internal/v4l2_request.h" > > /** > * \file media_device.h > @@ -851,4 +852,50 @@ std::vector<MediaEntity *> 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, > + * an 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<std::unique_ptr<V4L2Request>> *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<V4L2Request>(UniqueFD(requestFd)); Is this line safe on a vector? Is the size pre-determined before coming in here? Or should this be something like: requests->push_back(std::make_unique<V4L2Request>(UniqueFD(requestFd)); I'm sure that's how we use vectors elsewhere... > + } > + > + 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()) Ohhh now I see why it's an optional. Indeed. > + return supportsRequests_.value(); > + > + std::vector<std::unique_ptr<V4L2Request>> 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..8dcd5e618938 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<const uint32_t> ids) > +ControlList V4L2Device::getControls(Span<const uint32_t> ids, const V4L2Request *request) > { > if (ids.empty()) > return {}; > @@ -242,10 +245,16 @@ ControlList V4L2Device::getControls(Span<const uint32_t> 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<const uint32_t> ids) > /** > * \brief Write controls to the device > * \param[in] ctrls The list of controls to write > + * \param[in] request An 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<const uint32_t> 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 -EINVAL One of the controls is not supported or not accessible Missing adding \retval -EACCESS the device does not support requests > * \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..9441d7c4017b > --- /dev/null > +++ b/src/libcamera/v4l2_request.cpp > @@ -0,0 +1,128 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2025, Ideas On Board > + * > + * V4L2 Request API > + */ > + > +#include "libcamera/internal/v4l2_request.h" > + > +#include <fcntl.h> > +#include <stdlib.h> > +#include <sys/ioctl.h> > +#include <sys/syscall.h> > +#include <unistd.h> > + > +#include <linux/media.h> > + > +#include <libcamera/base/event_notifier.h> > +#include <libcamera/base/log.h> > + > +/** > + * \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(UniqueFD &&fd) > + : fd_(std::move(fd)), fdNotifier_(fd_.get(), EventNotifier::Exception) > +{ > + if (!fd_.isValid()) > + return; > + > + fdNotifier_.activated.connect(this, &V4L2Request::requestReady); > + fdNotifier_.setEnabled(false); > +} > + > +/** > + * \fn V4L2Request::isValid() > + * \brief Check if the request is valid > + * > + * Checks if the underlying fd is valid. > + * > + * \return True if the request is valid, false otherwise > + */ > + > +/** > + * \fn V4L2Request::fd() > + * \brief Get the file descriptor > + * > + * \return The file descriptor wrapped by this V4L2Request > + */ > + > +/** > + * \var V4L2Request::requestDone > + * \brief Signal that is emitted when the request is done > + */ > + > +/** > + * \brief Reinit the request > + * > + * Calls MEDIA_REQUEST_IOC_REINIT on 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 Queue the request > + * > + * Calls MEDIA_REQUEST_IOC_QUEUE on 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 7b48d911db73..25b61d049a0e 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<const FrameBuffer::Plane> planes = buffer->planes(); > -- > 2.51.0 >
Quoting Kieran Bingham (2025-11-25 17:03:00) > Quoting Stefan Klug (2025-11-25 16:28:15) > > 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 an fd that is allocated via > > MEDIA_IOC_REQUEST_ALLOC and then passed to the various V4L2 functions. > > > > Implement a V4L2Request class to wrap such an fd and add the > > corresponding utility functions. > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> > > > > Mostly minor comments below. > > Only my question on safety of usage of the vector on allocate stops me > putting a tag in... if someone confirms that is safe then I could add > one ;-) > > > > > --- > > > > Changes in v3: > > - Replaced int by UniqueFD > > - Fixed lots if typos from review > > > > Changes in v2: > > - Added documentation > > --- > > include/libcamera/internal/media_device.h | 8 ++ > > include/libcamera/internal/meson.build | 1 + > > include/libcamera/internal/v4l2_device.h | 5 +- > > include/libcamera/internal/v4l2_request.h | 44 ++++++ > > include/libcamera/internal/v4l2_videodevice.h | 3 +- > > src/libcamera/media_device.cpp | 47 +++++++ > > src/libcamera/meson.build | 1 + > > src/libcamera/v4l2_device.cpp | 30 +++- > > src/libcamera/v4l2_request.cpp | 128 ++++++++++++++++++ > > src/libcamera/v4l2_videodevice.cpp | 10 +- > > 10 files changed, 268 insertions(+), 9 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..2eb3ad988b09 100644 > > --- a/include/libcamera/internal/media_device.h > > +++ b/include/libcamera/internal/media_device.h > > @@ -8,6 +8,7 @@ > > #pragma once > > > > #include <map> > > +#include <optional> > > #include <string> > > #include <vector> > > > > @@ -18,6 +19,7 @@ > > #include <libcamera/base/unique_fd.h> > > > > #include "libcamera/internal/media_object.h" > > +#include "libcamera/internal/v4l2_request.h" > > > > namespace libcamera { > > > > @@ -57,6 +59,11 @@ public: > > > > std::vector<MediaEntity *> locateEntities(unsigned int function); > > > > + int allocateRequests(unsigned int count, > > + std::vector<std::unique_ptr<V4L2Request>> *requests); > > + > > + bool supportsRequests(); > > + > > protected: > > std::string logPrefix() const override; > > > > @@ -87,6 +94,7 @@ private: > > UniqueFD fd_; > > bool valid_; > > bool acquired_; > > + std::optional<bool> supportsRequests_; > > std::optional is intriguing here... I'd default false until known > otherwise... > > > std::map<unsigned int, MediaObject *> objects_; > > std::vector<MediaEntity *> 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 <libcamera/controls.h> > > > > #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<const uint32_t> ids); > > - int setControls(ControlList *ctrls); > > + ControlList getControls(Span<const uint32_t> 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..376c79ceedba > > --- /dev/null > > +++ b/include/libcamera/internal/v4l2_request.h > > @@ -0,0 +1,44 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2025, Ideas On Board > > + * > > + * V4L2 requests > > + */ > > + > > +#pragma once > > + > > +#include <string> > > + > > +#include <linux/videodev2.h> > > + > > +#include <libcamera/base/event_notifier.h> > > +#include <libcamera/base/log.h> > > +#include <libcamera/base/signal.h> > > +#include <libcamera/base/unique_fd.h> > > + > > +namespace libcamera { > > + > > +class V4L2Request : protected Loggable > > +{ > > +public: > > + bool isValid() const { return fd_.isValid(); } > > + int fd() const { return fd_.get(); } > > + > > + int reinit(); > > + int queue(); > > + > > + V4L2Request(UniqueFD &&fd); > > + > > + Signal<V4L2Request *> 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 6caafc4dcf08..57db0036db6b 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<FrameBuffer *> bufferReady; > > > > int streamOn(); > > diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp > > index 353f34a81eca..2a848ebed998 100644 > > --- a/src/libcamera/media_device.cpp > > +++ b/src/libcamera/media_device.cpp > > @@ -20,6 +20,7 @@ > > #include <linux/media.h> > > > > #include <libcamera/base/log.h> > > +#include "libcamera/internal/v4l2_request.h" > > > > /** > > * \file media_device.h > > @@ -851,4 +852,50 @@ std::vector<MediaEntity *> 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, > > + * an 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<std::unique_ptr<V4L2Request>> *requests) > > +{ > > + requests->resize(count); There it is - it was hiding in plain sight. I missed it. Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > + 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<V4L2Request>(UniqueFD(requestFd)); > > Is this line safe on a vector? Is the size pre-determined before coming > in here? Or should this be something like: > > requests->push_back(std::make_unique<V4L2Request>(UniqueFD(requestFd)); > > I'm sure that's how we use vectors elsewhere... -- Kieran
diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h index b3a48b98d64b..2eb3ad988b09 100644 --- a/include/libcamera/internal/media_device.h +++ b/include/libcamera/internal/media_device.h @@ -8,6 +8,7 @@ #pragma once #include <map> +#include <optional> #include <string> #include <vector> @@ -18,6 +19,7 @@ #include <libcamera/base/unique_fd.h> #include "libcamera/internal/media_object.h" +#include "libcamera/internal/v4l2_request.h" namespace libcamera { @@ -57,6 +59,11 @@ public: std::vector<MediaEntity *> locateEntities(unsigned int function); + int allocateRequests(unsigned int count, + std::vector<std::unique_ptr<V4L2Request>> *requests); + + bool supportsRequests(); + protected: std::string logPrefix() const override; @@ -87,6 +94,7 @@ private: UniqueFD fd_; bool valid_; bool acquired_; + std::optional<bool> supportsRequests_; std::map<unsigned int, MediaObject *> objects_; std::vector<MediaEntity *> 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 <libcamera/controls.h> #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<const uint32_t> ids); - int setControls(ControlList *ctrls); + ControlList getControls(Span<const uint32_t> 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..376c79ceedba --- /dev/null +++ b/include/libcamera/internal/v4l2_request.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board + * + * V4L2 requests + */ + +#pragma once + +#include <string> + +#include <linux/videodev2.h> + +#include <libcamera/base/event_notifier.h> +#include <libcamera/base/log.h> +#include <libcamera/base/signal.h> +#include <libcamera/base/unique_fd.h> + +namespace libcamera { + +class V4L2Request : protected Loggable +{ +public: + bool isValid() const { return fd_.isValid(); } + int fd() const { return fd_.get(); } + + int reinit(); + int queue(); + + V4L2Request(UniqueFD &&fd); + + Signal<V4L2Request *> 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 6caafc4dcf08..57db0036db6b 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<FrameBuffer *> bufferReady; int streamOn(); diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp index 353f34a81eca..2a848ebed998 100644 --- a/src/libcamera/media_device.cpp +++ b/src/libcamera/media_device.cpp @@ -20,6 +20,7 @@ #include <linux/media.h> #include <libcamera/base/log.h> +#include "libcamera/internal/v4l2_request.h" /** * \file media_device.h @@ -851,4 +852,50 @@ std::vector<MediaEntity *> 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, + * an 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<std::unique_ptr<V4L2Request>> *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<V4L2Request>(UniqueFD(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<std::unique_ptr<V4L2Request>> 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..8dcd5e618938 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<const uint32_t> ids) +ControlList V4L2Device::getControls(Span<const uint32_t> ids, const V4L2Request *request) { if (ids.empty()) return {}; @@ -242,10 +245,16 @@ ControlList V4L2Device::getControls(Span<const uint32_t> 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<const uint32_t> ids) /** * \brief Write controls to the device * \param[in] ctrls The list of controls to write + * \param[in] request An 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<const uint32_t> 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 -EINVAL One of the controls 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..9441d7c4017b --- /dev/null +++ b/src/libcamera/v4l2_request.cpp @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board + * + * V4L2 Request API + */ + +#include "libcamera/internal/v4l2_request.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <linux/media.h> + +#include <libcamera/base/event_notifier.h> +#include <libcamera/base/log.h> + +/** + * \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(UniqueFD &&fd) + : fd_(std::move(fd)), fdNotifier_(fd_.get(), EventNotifier::Exception) +{ + if (!fd_.isValid()) + return; + + fdNotifier_.activated.connect(this, &V4L2Request::requestReady); + fdNotifier_.setEnabled(false); +} + +/** + * \fn V4L2Request::isValid() + * \brief Check if the request is valid + * + * Checks if the underlying fd is valid. + * + * \return True if the request is valid, false otherwise + */ + +/** + * \fn V4L2Request::fd() + * \brief Get the file descriptor + * + * \return The file descriptor wrapped by this V4L2Request + */ + +/** + * \var V4L2Request::requestDone + * \brief Signal that is emitted when the request is done + */ + +/** + * \brief Reinit the request + * + * Calls MEDIA_REQUEST_IOC_REINIT on 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 Queue the request + * + * Calls MEDIA_REQUEST_IOC_QUEUE on 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 7b48d911db73..25b61d049a0e 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<const FrameBuffer::Plane> planes = buffer->planes();
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 an fd that is allocated via MEDIA_IOC_REQUEST_ALLOC and then passed to the various V4L2 functions. Implement a V4L2Request class to wrap such an fd and add the corresponding utility functions. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> --- Changes in v3: - Replaced int by UniqueFD - Fixed lots if typos from review Changes in v2: - Added documentation --- include/libcamera/internal/media_device.h | 8 ++ include/libcamera/internal/meson.build | 1 + include/libcamera/internal/v4l2_device.h | 5 +- include/libcamera/internal/v4l2_request.h | 44 ++++++ include/libcamera/internal/v4l2_videodevice.h | 3 +- src/libcamera/media_device.cpp | 47 +++++++ src/libcamera/meson.build | 1 + src/libcamera/v4l2_device.cpp | 30 +++- src/libcamera/v4l2_request.cpp | 128 ++++++++++++++++++ src/libcamera/v4l2_videodevice.cpp | 10 +- 10 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 include/libcamera/internal/v4l2_request.h create mode 100644 src/libcamera/v4l2_request.cpp